refactor: 将 bff 的gateway 迁移到 gateway 项目下,将 aggr 重命名为 bff
diff --git a/gateway/.gitignore b/gateway/.gitignore
new file mode 100644
index 0000000..b83d222
--- /dev/null
+++ b/gateway/.gitignore
@@ -0,0 +1 @@
+/target/
diff --git a/gateway/Dockerfile b/gateway/Dockerfile
new file mode 100644
index 0000000..372661c
--- /dev/null
+++ b/gateway/Dockerfile
@@ -0,0 +1,21 @@
+FROM harbor.supwisdom.com/institute/openjdk:8-jre
+
+ENV ENABLE_JMX_SSL=false
+ENV JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -Dspring.profiles.active=docker
+ENV SPRING_PROFILES_ACTIVE=docker
+
+ARG NAME
+ARG VERSION
+ARG JAR_FILE
+
+LABEL name=$NAME \
+ version=$VERSION
+
+EXPOSE 8080
+
+EXPOSE 8443
+
+COPY --chown=java-app:java-app target/${JAR_FILE} /home/java-app/lib/app.jar
+
+# COPY --chown=java-app:java-app target/doc /home/java-app/doc
+# COPY --chown=java-app:java-app target/api-docs /home/java-app/api-docs
diff --git a/gateway/pom.xml b/gateway/pom.xml
new file mode 100644
index 0000000..7f39a11
--- /dev/null
+++ b/gateway/pom.xml
@@ -0,0 +1,235 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>com.supwisdom.institute</groupId>
+ <artifactId>sw-backend-parent</artifactId>
+ <version>0.0.1-SNAPSHOT</version>
+ </parent>
+
+ <groupId>com.supwisdom.institute</groupId>
+ <artifactId>sw-backend-gateway</artifactId>
+ <version>0.0.1-SNAPSHOT</version>
+ <packaging>jar</packaging>
+
+ <name>Supwisdom Backend Framework Gateway</name>
+ <description>Supwisdom Backend Framework Gateway project</description>
+
+ <properties>
+ <start-class>com.supwisdom.institute.backend.gateway.Application</start-class>
+ </properties>
+
+ <dependencies>
+
+ <dependency>
+ <groupId>org.projectlombok</groupId>
+ <artifactId>lombok</artifactId>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+
+
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter</artifactId>
+ </dependency>
+
+ <!-- 微服务 健康监控 -->
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-actuator</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-webflux</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.springframework.cloud</groupId>
+ <artifactId>spring-cloud-starter-gateway</artifactId>
+ </dependency>
+
+
+ <!-- <dependency>
+ <groupId>com.supwisdom.infras</groupId>
+ <artifactId>infras-online-doc</artifactId>
+ </dependency> -->
+
+
+ <!-- <dependency>
+ <groupId>com.supwisdom.infras</groupId>
+ <artifactId>infras-mvc</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>com.supwisdom.infras</groupId>
+ <artifactId>infras-object-mapper</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>com.supwisdom.infras</groupId>
+ <artifactId>infras-i18n</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>com.supwisdom.infras</groupId>
+ <artifactId>infras-lang</artifactId>
+ </dependency> -->
+
+
+ <dependency>
+ <groupId>com.supwisdom.infras</groupId>
+ <artifactId>infras-security</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-security</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>io.jsonwebtoken</groupId>
+ <artifactId>jjwt</artifactId>
+ <version>0.9.1</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.springframework.security</groupId>
+ <artifactId>spring-security-cas</artifactId>
+ </dependency>
+
+
+ <dependency>
+ <groupId>com.supwisdom.institute</groupId>
+ <artifactId>sw-backend-common-framework</artifactId>
+ </dependency>
+
+
+ <dependency>
+ <groupId>com.alibaba</groupId>
+ <artifactId>fastjson</artifactId>
+ </dependency>
+
+
+ <dependency>
+ <groupId>io.springfox</groupId>
+ <artifactId>springfox-swagger2</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>io.springfox</groupId>
+ <artifactId>springfox-swagger-ui</artifactId>
+ </dependency>
+
+
+ <!-- 热部署,无需重启项目 -->
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-devtools</artifactId>
+ <scope>runtime</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-test</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ </dependencies>
+
+ <build>
+ <finalName>${project.artifactId}</finalName>
+
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-failsafe-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-release-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.jacoco</groupId>
+ <artifactId>jacoco-maven-plugin</artifactId>
+ </plugin>
+
+
+ <!-- <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-resources-plugin</artifactId>
+ <configuration>
+ <encoding>${project.build.sourceEncoding}</encoding>
+ </configuration>
+ <executions>
+ <execution>
+ <id>copy-doc-resources</id>
+ <phase>package</phase>
+ <goals>
+ <goal>copy-resources</goal>
+ </goals>
+ <configuration>
+ <encoding>utf-8</encoding>
+ <outputDirectory>${basedir}/target/doc</outputDirectory>
+ <overwrite>true</overwrite>
+ <resources>
+ <resource>
+ <directory>${basedir}/../../doc</directory>
+ </resource>
+ </resources>
+ </configuration>
+ </execution>
+ <execution>
+ <id>copy-certs-jwt-resources</id>
+ <phase>package</phase>
+ <goals>
+ <goal>copy-resources</goal>
+ </goals>
+ <configuration>
+ <encoding>utf-8</encoding>
+ <outputDirectory>${basedir}/target/certs/jwt</outputDirectory>
+ <overwrite>true</overwrite>
+ <resources>
+ <resource>
+ <directory>${basedir}/../../certs/jwt</directory>
+ </resource>
+ </resources>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin> -->
+
+
+ <plugin>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-maven-plugin</artifactId>
+ </plugin>
+
+ <plugin>
+ <groupId>com.spotify</groupId>
+ <artifactId>dockerfile-maven-plugin</artifactId>
+ <configuration>
+ <skip>false</skip>
+ </configuration>
+ </plugin>
+
+ </plugins>
+
+ </build>
+
+</project>
diff --git a/gateway/src/main/java/com/supwisdom/infras/security/reactive/InfrasSecurityReactiveAutoConfiguration.java b/gateway/src/main/java/com/supwisdom/infras/security/reactive/InfrasSecurityReactiveAutoConfiguration.java
new file mode 100644
index 0000000..1ea28f4
--- /dev/null
+++ b/gateway/src/main/java/com/supwisdom/infras/security/reactive/InfrasSecurityReactiveAutoConfiguration.java
@@ -0,0 +1,66 @@
+package com.supwisdom.infras.security.reactive;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
+import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
+import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.core.userdetails.User.UserBuilder;
+import org.springframework.security.crypto.factory.PasswordEncoderFactories;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.web.server.WebFilterChainProxy;
+
+import com.supwisdom.infras.security.api.InfrasApiUserController;
+
+import reactor.core.publisher.Flux;
+
+@Configuration
+@ConditionalOnClass({ Flux.class, EnableWebFluxSecurity.class, WebFilterChainProxy.class })
+public class InfrasSecurityReactiveAutoConfiguration {
+
+ private static final Logger logger = LoggerFactory.getLogger(InfrasSecurityReactiveAutoConfiguration.class);
+
+ @Bean
+ @ConditionalOnMissingBean({PasswordEncoder.class})
+ public PasswordEncoder passwordEncoder() {
+
+ PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
+ logger.debug("InfrasSecurityReactiveAutoConfiguration passwordEncoder is {}", passwordEncoder);
+
+ return passwordEncoder;
+ }
+
+ @Bean
+ @ConditionalOnMissingBean({ReactiveUserDetailsService.class})
+ public MapReactiveUserDetailsService reactiveUserDetailsService() {
+
+ // ensure the passwords are encoded properly
+ @SuppressWarnings("deprecation")
+ UserBuilder users = User.withDefaultPasswordEncoder();
+
+ MapReactiveUserDetailsService reactiveUserDetailsService = new MapReactiveUserDetailsService(
+ users.username("user").password("password").roles("USER").build(),
+ users.username("admin").password("password").roles("USER","ADMIN").build()
+ );
+ logger.debug("InfrasSecurityReactiveAutoConfiguration reactiveUserDetailsService is {}", reactiveUserDetailsService);
+
+ return reactiveUserDetailsService;
+ }
+
+
+
+ @Bean
+ @ConditionalOnMissingBean(InfrasApiUserController.class)
+ public InfrasApiUserController infrasApiUserController() {
+ InfrasApiUserController infrasApiUserController = new InfrasApiUserController();
+ logger.debug("InfrasSecurityReactiveAutoConfiguration infrasApiUserController is {}", infrasApiUserController);
+
+ return infrasApiUserController;
+ }
+
+}
diff --git a/gateway/src/main/java/com/supwisdom/infras/security/reactive/basic/BasicWebFluxSecurityConfiguration.java b/gateway/src/main/java/com/supwisdom/infras/security/reactive/basic/BasicWebFluxSecurityConfiguration.java
new file mode 100644
index 0000000..aa792e1
--- /dev/null
+++ b/gateway/src/main/java/com/supwisdom/infras/security/reactive/basic/BasicWebFluxSecurityConfiguration.java
@@ -0,0 +1,39 @@
+package com.supwisdom.infras.security.reactive.basic;
+
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.HttpMethod;
+import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
+import org.springframework.security.config.web.server.ServerHttpSecurity;
+import org.springframework.security.web.server.SecurityWebFilterChain;
+import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers;
+
+@Configuration
+@ConditionalOnProperty(name="infras.security.basic.enabled", havingValue="true")
+public class BasicWebFluxSecurityConfiguration {
+
+ @Bean
+ public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
+ http
+ .securityMatcher(ServerWebExchangeMatchers.pathMatchers("/api/**"))
+ .authorizeExchange()
+ .pathMatchers(HttpMethod.OPTIONS).permitAll()
+ .pathMatchers("/api/public/**", "/api/open/**").permitAll()
+ .pathMatchers("/api/v*/public/**", "/api/v*/open/**").permitAll()
+ .pathMatchers("/api/*/v*/public/**", "/api/*/v*/open/**").permitAll()
+ .pathMatchers("/api/**").authenticated()
+ .anyExchange().authenticated();
+
+ //http.addFilterAt(webFilter, SecurityWebFiltersOrder.LAST);
+
+ http.httpBasic();
+
+ http.csrf().disable();
+
+ http.formLogin().disable();
+
+ return http.build();
+ }
+
+}
diff --git a/gateway/src/main/java/com/supwisdom/infras/security/reactive/basic/EnableInfrasBasicWebFluxApi.java b/gateway/src/main/java/com/supwisdom/infras/security/reactive/basic/EnableInfrasBasicWebFluxApi.java
new file mode 100644
index 0000000..9658d27
--- /dev/null
+++ b/gateway/src/main/java/com/supwisdom/infras/security/reactive/basic/EnableInfrasBasicWebFluxApi.java
@@ -0,0 +1,17 @@
+package com.supwisdom.infras.security.reactive.basic;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.springframework.context.annotation.Import;
+
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Import(BasicWebFluxSecurityConfiguration.class)
+public @interface EnableInfrasBasicWebFluxApi {
+
+}
diff --git a/gateway/src/main/java/com/supwisdom/institute/backend/gateway/Application.java b/gateway/src/main/java/com/supwisdom/institute/backend/gateway/Application.java
new file mode 100644
index 0000000..aea520c
--- /dev/null
+++ b/gateway/src/main/java/com/supwisdom/institute/backend/gateway/Application.java
@@ -0,0 +1,83 @@
+package com.supwisdom.institute.backend.gateway;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Bean;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.http.server.reactive.ServerHttpResponse;
+import org.springframework.web.cors.reactive.CorsUtils;
+import org.springframework.web.server.ServerWebExchange;
+import org.springframework.web.server.WebFilter;
+import org.springframework.web.server.WebFilterChain;
+
+import reactor.core.publisher.Mono;
+
+import com.supwisdom.infras.security.reactive.basic.EnableInfrasBasicWebFluxApi;
+import com.supwisdom.institute.backend.common.framework.exception.EnableCustomExceptionHandler;
+
+import static org.springframework.web.cors.CorsConfiguration.ALL;
+
+@SpringBootApplication
+
+@EnableCustomExceptionHandler
+
+//@EnableInfrasOnlineDoc
+
+//@EnableInfrasCasSecurity
+
+//@EnableInfrasBasicApi
+//@EnableInfrasJWTApi
+
+@EnableInfrasBasicWebFluxApi
+public class Application {
+
+ public static void main(String[] args) {
+ SpringApplication.run(Application.class, args);
+ }
+
+// @Bean
+// public CorsFilter corsFilter() {
+// final CorsConfiguration config = new CorsConfiguration();
+// //config.setAllowCredentials(true);
+// config.addAllowedOrigin("*");
+// config.addAllowedHeader("*");
+// config.addAllowedMethod("*");
+//
+// final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
+// source.registerCorsConfiguration("/v2/api-docs", config);
+// source.registerCorsConfiguration("/api/**", config); // 对 /api/** 下的请求,支持 cors 跨域请求,如不需要可以注释
+//
+// return new CorsFilter(source);
+// }
+
+ @Bean
+ public WebFilter corsFilter() {
+ return (ServerWebExchange ctx, WebFilterChain chain) -> {
+ ServerHttpRequest request = ctx.getRequest();
+ if (!CorsUtils.isCorsRequest(request)) {
+ return chain.filter(ctx);
+ }
+ HttpHeaders requestHeaders = request.getHeaders();
+ ServerHttpResponse response = ctx.getResponse();
+ HttpMethod requestMethod = requestHeaders.getAccessControlRequestMethod();
+ HttpHeaders headers = response.getHeaders();
+ headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, requestHeaders.getOrigin());
+ headers.addAll(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, requestHeaders.getAccessControlRequestHeaders());
+ if (requestMethod != null) {
+ headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, requestMethod.name());
+ }
+ headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
+ headers.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, ALL);
+ // headers.add(HttpHeaders.ACCESS_CONTROL_MAX_AGE, MAX_AGE);
+ if (request.getMethod() == HttpMethod.OPTIONS) {
+ response.setStatusCode(HttpStatus.OK);
+ return Mono.empty();
+ }
+ return chain.filter(ctx);
+ };
+ }
+
+}
diff --git a/gateway/src/main/java/com/supwisdom/institute/backend/gateway/configuration/GlobalFilterConfig.java b/gateway/src/main/java/com/supwisdom/institute/backend/gateway/configuration/GlobalFilterConfig.java
new file mode 100644
index 0000000..2e71ac7
--- /dev/null
+++ b/gateway/src/main/java/com/supwisdom/institute/backend/gateway/configuration/GlobalFilterConfig.java
@@ -0,0 +1,16 @@
+package com.supwisdom.institute.backend.gateway.configuration;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import com.supwisdom.institute.backend.gateway.filter.SimpleUserTransmitGlobalFilter;
+
+@Configuration
+public class GlobalFilterConfig {
+
+ @Bean
+ public SimpleUserTransmitGlobalFilter simpleUserTransmitGlobalFilter() {
+ return new SimpleUserTransmitGlobalFilter();
+ }
+
+}
diff --git a/gateway/src/main/java/com/supwisdom/institute/backend/gateway/configuration/PasswordEncoderConfig.java b/gateway/src/main/java/com/supwisdom/institute/backend/gateway/configuration/PasswordEncoderConfig.java
new file mode 100644
index 0000000..f98770f
--- /dev/null
+++ b/gateway/src/main/java/com/supwisdom/institute/backend/gateway/configuration/PasswordEncoderConfig.java
@@ -0,0 +1,29 @@
+package com.supwisdom.institute.backend.gateway.configuration;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.crypto.factory.PasswordEncoderFactories;
+import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
+import org.springframework.security.crypto.password.NoOpPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@Configuration
+public class PasswordEncoderConfig {
+
+ @Bean
+ public PasswordEncoder passwordEncoder() {
+
+ PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
+
+ if (passwordEncoder instanceof DelegatingPasswordEncoder) {
+ ((DelegatingPasswordEncoder)passwordEncoder).setDefaultPasswordEncoderForMatches(NoOpPasswordEncoder.getInstance());
+ }
+
+ log.debug("PasswordEncoderConfig passwordEncoder is {}", passwordEncoder);
+ return passwordEncoder;
+ }
+
+}
diff --git a/gateway/src/main/java/com/supwisdom/institute/backend/gateway/configuration/UserDetailsServiceConfig.java b/gateway/src/main/java/com/supwisdom/institute/backend/gateway/configuration/UserDetailsServiceConfig.java
new file mode 100644
index 0000000..cafbc17
--- /dev/null
+++ b/gateway/src/main/java/com/supwisdom/institute/backend/gateway/configuration/UserDetailsServiceConfig.java
@@ -0,0 +1,31 @@
+package com.supwisdom.institute.backend.gateway.configuration;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import com.supwisdom.institute.backend.gateway.security.core.userdetails.InMemeryUserDetailsService;
+import com.supwisdom.institute.backend.gateway.security.core.userdetails.MyUserDetailsService;
+
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@Configuration
+public class UserDetailsServiceConfig {
+
+// @Bean
+// public MyUserDetailsService userDetailsService() throws Exception {
+// MyUserDetailsService myUserDetailsService = new MyUserDetailsService();
+// log.debug("UserDetailsServiceConfig myUserDetailsService is {}", myUserDetailsService);
+//
+// return myUserDetailsService;
+// }
+
+ @Bean
+ public InMemeryUserDetailsService userDetailsService() throws Exception {
+ InMemeryUserDetailsService inMemeryUserDetailsService = new InMemeryUserDetailsService();
+ log.debug("UserDetailsServiceConfig inMemeryUserDetailsService is {}", inMemeryUserDetailsService);
+
+ return inMemeryUserDetailsService;
+ }
+
+}
diff --git a/gateway/src/main/java/com/supwisdom/institute/backend/gateway/filter/SimpleUserTransmitGlobalFilter.java b/gateway/src/main/java/com/supwisdom/institute/backend/gateway/filter/SimpleUserTransmitGlobalFilter.java
new file mode 100644
index 0000000..4cd8faf
--- /dev/null
+++ b/gateway/src/main/java/com/supwisdom/institute/backend/gateway/filter/SimpleUserTransmitGlobalFilter.java
@@ -0,0 +1,60 @@
+package com.supwisdom.institute.backend.gateway.filter;
+
+import java.net.URLDecoder;
+
+import lombok.extern.slf4j.Slf4j;
+
+import org.springframework.cloud.gateway.filter.GatewayFilterChain;
+import org.springframework.cloud.gateway.filter.GlobalFilter;
+import org.springframework.core.Ordered;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.ReactiveSecurityContextHolder;
+import org.springframework.security.core.context.SecurityContext;
+import org.springframework.web.server.ServerWebExchange;
+
+import com.alibaba.fastjson.JSONObject;
+import com.supwisdom.institute.backend.gateway.security.core.userdetails.MyUser;
+import com.supwisdom.institute.backend.common.core.transmit.user.User;
+import com.supwisdom.institute.backend.common.core.transmit.user.UserContext;
+
+import reactor.core.publisher.Mono;
+
+@Slf4j
+public class SimpleUserTransmitGlobalFilter implements GlobalFilter, Ordered {
+
+ @Override
+ public int getOrder() {
+ return 0;
+ }
+
+ @Override
+ public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
+
+ return ReactiveSecurityContextHolder.getContext()
+ .filter(c -> c.getAuthentication() != null && c.getAuthentication().isAuthenticated() && c.getAuthentication().getPrincipal() instanceof MyUser)
+ .map(SecurityContext::getAuthentication)
+ .map(Authentication::getPrincipal)
+ .cast(MyUser.class)
+ .map(myUser -> {
+ try {
+ User user = new User(myUser.getUsername(), myUser.getRoles(), myUser.getAttributes());
+
+ String jsonUser = JSONObject.toJSONString(user);
+ log.info(jsonUser);
+ String headerValue = new String(URLDecoder.decode(jsonUser,"UTF-8"));
+ ServerHttpRequest request = exchange.getRequest().mutate()
+ .header(UserContext.KEY_USER_IN_HTTP_HEADER, headerValue)
+ .build();
+ log.debug("User set ok");
+ return exchange.mutate().request(request).build();
+ } catch (Exception e) {
+ log.warn("User set error", e);
+ }
+ return exchange;
+ })
+ .flatMap(ex -> chain.filter(ex))
+ ;
+ }
+
+}
diff --git a/gateway/src/main/java/com/supwisdom/institute/backend/gateway/security/core/userdetails/InMemeryUserDetailsService.java b/gateway/src/main/java/com/supwisdom/institute/backend/gateway/security/core/userdetails/InMemeryUserDetailsService.java
new file mode 100644
index 0000000..4e761a5
--- /dev/null
+++ b/gateway/src/main/java/com/supwisdom/institute/backend/gateway/security/core/userdetails/InMemeryUserDetailsService.java
@@ -0,0 +1,72 @@
+package com.supwisdom.institute.backend.gateway.security.core.userdetails;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.security.crypto.password.PasswordEncoder;
+
+import com.supwisdom.institute.backend.common.core.transmit.user.User;
+import com.supwisdom.institute.backend.common.core.transmit.user.UserContext;
+
+import reactor.core.publisher.Mono;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class InMemeryUserDetailsService implements UserDetailsService, ReactiveUserDetailsService {
+
+ @Autowired
+ PasswordEncoder passwordEncoder;
+
+ @Override
+ public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
+
+ log.debug("InMemeryUserDetailsService.loadUserByUsername({})", username);
+
+ List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
+ authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
+ authorities.add(new SimpleGrantedAuthority("administrator"));
+ authorities.add(new SimpleGrantedAuthority("user"));
+
+ Map<String, Object> attributes = new HashMap<String, Object>();
+
+ MyUser myUser = new MyUser(username, passwordEncoder.encode(username), authorities, attributes);
+ log.debug("myUser is {}", myUser);
+
+ return myUser;
+ }
+
+ @Override
+ public Mono<UserDetails> findByUsername(String username) {
+
+ log.debug("InMemeryUserDetailsService.findByUsername({})", username);
+
+ List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
+ authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
+ authorities.add(new SimpleGrantedAuthority("administrator"));
+ authorities.add(new SimpleGrantedAuthority("user"));
+
+ Map<String, Object> attributes = new HashMap<String, Object>();
+
+ MyUser myUser = new MyUser(username, passwordEncoder.encode(username), authorities, attributes);
+ log.debug("myUser is {}", myUser);
+
+ List<String> roles = new ArrayList<>();
+ roles.add("ROLE_ADMIN");
+ roles.add("administrator");
+ roles.add("user");
+ User user = new User(username, roles, attributes);
+ UserContext.setUser(user);
+
+ return Mono.just(myUser);
+ }
+
+}
diff --git a/gateway/src/main/java/com/supwisdom/institute/backend/gateway/security/core/userdetails/MyUser.java b/gateway/src/main/java/com/supwisdom/institute/backend/gateway/security/core/userdetails/MyUser.java
new file mode 100644
index 0000000..7148839
--- /dev/null
+++ b/gateway/src/main/java/com/supwisdom/institute/backend/gateway/security/core/userdetails/MyUser.java
@@ -0,0 +1,60 @@
+package com.supwisdom.institute.backend.gateway.security.core.userdetails;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.userdetails.User;
+
+public class MyUser extends User {
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = 3195151947212484499L;
+
+ public MyUser(String username, String password,
+ Collection<? extends GrantedAuthority> authorities,
+ Map<String, Object> attributes) {
+ this(username, password, true, true, true, true, authorities, attributes);
+ }
+
+ public MyUser(String username, String password,
+ boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked,
+ Collection<? extends GrantedAuthority> authorities,
+ Map<String, Object> attributes) {
+ super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);
+
+ this.attributes = attributes;
+ }
+
+ private final Map<String, Object> attributes;
+ public Map<String, Object> getAttributes() {
+ return this.attributes;
+ }
+
+ public List<String> getRoles() {
+ List<String> roles = new ArrayList<>();
+ for (GrantedAuthority grantedAuthority : this.getAuthorities()) {
+ roles.add(grantedAuthority.getAuthority());
+ }
+
+ return roles;
+ }
+
+
+ public Object getAttribute(String key) {
+ if (attributes == null) {
+ return null;
+ }
+
+ if (!attributes.containsKey(key)) {
+ return null;
+ }
+
+ return attributes.get(key);
+ }
+
+}
diff --git a/gateway/src/main/java/com/supwisdom/institute/backend/gateway/security/core/userdetails/MyUserDetailsService.java b/gateway/src/main/java/com/supwisdom/institute/backend/gateway/security/core/userdetails/MyUserDetailsService.java
new file mode 100644
index 0000000..e5bfec8
--- /dev/null
+++ b/gateway/src/main/java/com/supwisdom/institute/backend/gateway/security/core/userdetails/MyUserDetailsService.java
@@ -0,0 +1,79 @@
+package com.supwisdom.institute.backend.gateway.security.core.userdetails;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.security.crypto.password.PasswordEncoder;
+
+import reactor.core.publisher.Mono;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class MyUserDetailsService implements UserDetailsService, ReactiveUserDetailsService {
+
+ @Autowired
+ PasswordEncoder passwordEncoder;
+
+// @Autowired
+// AccountService accountService;
+
+ @Override
+ public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // TODO:
+
+ log.debug("MyUserDetailsService.loadUserByUsername({})", username);
+
+// Account account = accountService.loadByUsername(username);
+// if (account == null) {
+// throw new UsernameNotFoundException(String.format("%s not found", username));
+// }
+//
+// List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
+//// for (Role role : securityUser.getRoles()) {
+//// authorities.add(new SimpleGrantedAuthority(role.getCode()));
+//// }
+// // FIXME:
+// authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
+// authorities.add(new SimpleGrantedAuthority("administrator"));
+// authorities.add(new SimpleGrantedAuthority("user"));
+//
+// Map<String, Object> attributes = new HashMap<String, Object>();
+// attributes.put("userId", account.getUser().getId());
+// attributes.put("userUid", account.getUser().getUid());
+//
+// MyUser myUser = new MyUser(
+// account.getAccountName(),
+// account.getUser().getPassWord(),
+// account.getActivation() && "Normal".equals(account.getState()) ? true : false,
+// account.getAccountExpiryDate() == null || Calendar.getInstance().getTime().before(account.getAccountExpiryDate()) ? true : false,
+// true,
+// true,
+// authorities,
+// attributes);
+//
+// log.debug("myUser is {}", myUser);
+
+ MyUser myUser = null;
+
+ return myUser;
+ }
+
+ @Override
+ public Mono<UserDetails> findByUsername(String username) {
+ log.debug("MyUserDetailsService.findByUsername({})", username);
+
+ MyUser myUser = null;
+
+ return Mono.just(myUser);
+ }
+
+}
diff --git a/gateway/src/main/resources/application-docker.yml b/gateway/src/main/resources/application-docker.yml
new file mode 100644
index 0000000..b5ea861
--- /dev/null
+++ b/gateway/src/main/resources/application-docker.yml
@@ -0,0 +1,105 @@
+server:
+ port: ${SERVER_PORT:8443}
+ ssl:
+ enabled: ${SSL_ENABLED:true}
+ clientAuth: NEED
+ key-store: ${SSL_KEYSTORE_FILE:file:/certs/server/server.keystore}
+ key-store-password: ${SSL_KEYSTORE_PASSWORD:}
+ trust-store: ${SSL_TRUSTSTORE_FILE:file:/certs/server/server.truststore}
+ trust-store-password: ${SSL_TRUSTSTORE_PASSWORD:}
+ tomcat:
+ accesslog:
+ enabled: ${TOMCAT_ACCESSLOG_ENABLED:false}
+ buffered: ${TOMCAT_ACCESSLOG_BUFFERED:true}
+ directory: ${TOMCAT_ACCESSLOG_DIR:log}
+ prefix: ${TOMCAT_ACCESSLOG_PREFIX:sa-api-accesslog}
+ suffix: ${TOMCAT_ACCESSLOG_SUFFIX:.log}
+ file-date-format: ${TOMCAT_ACCESSLOG_FILE_DATE_FORMAT:.yyyy-MM-dd}
+ rotate: ${TOMCAT_ACCESSLOG_ROTATE:true}
+
+
+##
+# logging
+#
+logging:
+ level:
+ root: INFO
+ com.supwisdom: INFO
+
+
+spring:
+ jackson:
+ time-zone: ${JACKSON_TIME_ZONE:Asia/Shanghai}
+
+
+##
+# spring cloud gateway
+#
+ cloud:
+ gateway:
+ metrics:
+ enabled: true
+ routes:
+ - id: bff-api
+ uri: ${SW_BACKEND_AGGR_API_URI:https://sw-backend-admin-bff}
+ predicates:
+ - Path=/api/bff/**
+ filters:
+ - RewritePath=/api/bff/(?<suffix>.*), /$\{suffix}
+ - id: base-api
+ uri: ${SW_BACKEND_BASE_API_URI:https://sw-backend-admin-sa}
+ predicates:
+ - Path=/api/base/**
+ filters:
+ - RewritePath=/api/base/(?<suffix>.*), /$\{suffix}
+ - id: system-api
+ uri: ${SW_BACKEND_SYSTEM_API_URI:https://sw-backend-admin-sa}
+ predicates:
+ - Path=/api/system/**
+ filters:
+ - RewritePath=/api/system/(?<suffix>.*), /$\{suffix}
+ - id: biz-api
+ uri: ${SW_BACKEND_BIZ_API_URI:https://sw-backend-admin-sa}
+ predicates:
+ - Path=/api/biz/**
+ filters:
+ - RewritePath=/api/biz/(?<suffix>.*), /$\{suffix}
+
+
+##
+# infras.online-doc
+#
+infras.online-doc.enabled: ${INFRAS_ONLINE_DOC_ENABLED:false}
+infras.online-doc.md-docs.staitc.path: ${INFRAS_ONLINE_DOC_MD_DOCS_STATIC_PATH:/doc/}
+infras.online-doc.api-docs.staitc.path: ${INFRAS_ONLINE_DOC_API_DOCS_STATIC_PATH:/api-docs/}
+
+
+##
+# infras.security basic
+#
+infras.security.basic.enabled: ${INFRAS_SECURITY_BASIC_ENABLED:true}
+
+
+##
+# infras.security jwt
+#
+infras.security.jwt.enabled: ${INFRAS_SECURITY_JWT_ENABLED:false}
+
+infras.security.jwt.public-key-pem: ${INFRAS_SECURITY_JWT_PUBLIC_KEY_PEM:}
+infras.security.jwt.private-key-pem-pkcs8: ${INFRAS_SECURITY_JWT_PRIVATE_KEY_PEM_PKCS8:}
+
+
+##
+# infras.security cas
+#
+infras.security.cas.enabled: ${INFRAS_SECURITY_CAS_ENABLED:false}
+
+#应用访问地址
+app.server.host.url: ${APP_SERVER_HOST_URL:https://localhost:8443}
+#应用登录地址
+app.login.url: ${APP_LOGIN_URL:/cas/login}
+#应用登出地址
+app.logout.url: ${APP_LOGOUT_URL:/cas/logout}
+
+#CAS服务地址
+cas.server.host.url: ${CAS_SERVER_HOST_URL:https://cas-server/cas}
diff --git a/gateway/src/main/resources/application.yml b/gateway/src/main/resources/application.yml
new file mode 100644
index 0000000..e98b431
--- /dev/null
+++ b/gateway/src/main/resources/application.yml
@@ -0,0 +1,96 @@
+server:
+ port: 8080
+
+
+##
+# logging
+#
+logging:
+ level:
+ root: INFO
+ com.supwisdom: DEBUG
+# org.springframework.web: INFO
+# org.springframework.cloud.openfeign: INFO
+
+
+spring:
+ jackson:
+ time-zone: Asia/Shanghai
+
+##
+# spring cloud gateway
+#
+ cloud:
+ gateway:
+ metrics:
+ enabled: true
+ routes:
+ - id: bff-api
+ uri: http://localhost:8081
+ predicates:
+ - Path=/api/bff/**
+ filters:
+ - RewritePath=/api/bff/(?<suffix>.*), /$\{suffix}
+ - id: base-api
+ uri: http://localhost:8082
+ predicates:
+ - Path=/api/base/**
+ filters:
+ - RewritePath=/api/base/(?<suffix>.*), /$\{suffix}
+ - id: system-api
+ uri: http://localhost:8082
+ predicates:
+ - Path=/api/system/**
+ filters:
+ - RewritePath=/api/system/(?<suffix>.*), /$\{suffix}
+ - id: biz-api
+ uri: http://localhost:8082
+ predicates:
+ - Path=/api/biz/**
+ filters:
+ - RewritePath=/api/biz/(?<suffix>.*), /$\{suffix}
+
+
+##
+# infras.online-doc
+#
+infras.online-doc.enabled: false
+infras.online-doc.md-docs.staitc.path: /Users/loie/c/work/git/institute/sw-backend/doc/
+infras.online-doc.api-docs.staitc.path: /Users/loie/c/work/git/institute/sw-backend/api-docs/
+
+
+##
+# infras.security basic
+#
+infras.security.basic.enabled: true
+
+
+##
+# infras.security jwt
+#
+infras.security.jwt.enabled: false
+
+#infras.security.jwt.public-key-pem: |-
+# -----BEGIN PUBLIC KEY-----
+# MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDBQw6TmvJ+nOuRaLoHsZJGIBzRg/wbskNv6UevL3/nQioYooptPfdIHVzPiKRVT5+DW5+nqzav3DOxY+HYKjO9nFjYdj0sgvRae6iVpa5Ji1wbDKOvwIDNukgnKbqvFXX2Isfl0RxeN3uEKdjeFGGFdr38I3ADCNKFNxtbmfqvjQIDAQAB
+# -----END PUBLIC KEY-----
+#infras.security.jwt.private-key-pem-pkcs8: |-
+# -----BEGIN PRIVATE KEY-----
+# MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAMFDDpOa8n6c65FougexkkYgHNGD/BuyQ2/pR68vf+dCKhiiim0990gdXM+IpFVPn4Nbn6erNq/cM7Fj4dgqM72cWNh2PSyC9Fp7qJWlrkmLXBsMo6/AgM26SCcpuq8VdfYix+XRHF43e4Qp2N4UYYV2vfwjcAMI0oU3G1uZ+q+NAgMBAAECgYA7jA7UuhxXmMAYmJ0hO7xnMQPQJouqeP3AYK9+sfMF7WQNHR/r0vj7Vli/dUm1I4hxr+x8fAuomf+ve6gds7sm+v2JHLzEIyPPiogoC7IcBmjJ3yVzW/26cXeOmTiPC/fW2g4BpYxSM8HLDaSkrtqzy8e9ijlzMpHBvvwLikufnQJBAOXaqIPuZ7Vm/JwQHAmX2HV+Qk6GMi/H7mL8X0AaW68w+Iccdbz1hzmMBfdn5NMmx2AOwoBAVivgjt0a1OfksHMCQQDXPtXxwFy4dQ4TbPu8L38P8s/bPo9ib1YkEMp57yBw+IvxB7jnpA9rUYTfZM/HpVP7r9rfVEUylVXXzhz1qx//AkEApWJOTBdW8bQ3YEdLFS/3pJqDNSLjq3OMuBZkpqgQfh6bRAQbRynW8XYpuNk9URye6iPUmRkxp4J86ORseqoWtwJAJb5a/b1hhObhxP5DVkht23oUgLmDoxsq28AmASOxaJ3szCMyhUv7eDIfPp0K4lNXWrcHhkncqHYPS3xVD68mOQJAV4SRDdWpgAbQOUODotohE48RxrabHo0l228CJ/pnm0q7gplPs4iSNJ2eijFuOMXfKkq3z/vxiNSA59FcdoCOHQ==
+# -----END PRIVATE KEY-----
+
+
+##
+# infras.security cas
+#
+infras.security.cas.enabled: false
+
+#应用访问地址
+app.server.host.url: http://localhost:8080
+#应用登录地址
+app.login.url: /cas/login
+#应用登出地址
+app.logout.url: /cas/logout
+
+#CAS服务地址
+cas.server.host.url: https://cas.supwisdom.com/cas
diff --git a/gateway/src/main/resources/bootstrap.yml b/gateway/src/main/resources/bootstrap.yml
new file mode 100644
index 0000000..aa852b8
--- /dev/null
+++ b/gateway/src/main/resources/bootstrap.yml
@@ -0,0 +1,3 @@
+spring:
+ application:
+ name: sw-backend-gateway