diff --git a/xtoon-common/xtoon-common-core/src/main/java/com/xtoon/cloud/common/core/constant/AuthConstants.java b/xtoon-common/xtoon-common-core/src/main/java/com/xtoon/cloud/common/core/constant/AuthConstants.java index afd9698..3e8b36b 100644 --- a/xtoon-common/xtoon-common-core/src/main/java/com/xtoon/cloud/common/core/constant/AuthConstants.java +++ b/xtoon-common/xtoon-common-core/src/main/java/com/xtoon/cloud/common/core/constant/AuthConstants.java @@ -85,4 +85,6 @@ public interface AuthConstants { String GRANT_TYPE_KEY = "grant_type"; String REFRESH_TOKEN = "refresh_token"; + + } diff --git a/xtoon-common/xtoon-common-redis/src/main/java/com/xtoon/cloud/common/redis/util/RedisService.java b/xtoon-common/xtoon-common-redis/src/main/java/com/xtoon/cloud/common/redis/util/RedisService.java index d8d454f..2af2fb3 100644 --- a/xtoon-common/xtoon-common-redis/src/main/java/com/xtoon/cloud/common/redis/util/RedisService.java +++ b/xtoon-common/xtoon-common-redis/src/main/java/com/xtoon/cloud/common/redis/util/RedisService.java @@ -84,6 +84,21 @@ public class RedisService { return gson.toJson(object); } + /** + * 判断key是否存在 + * + * @param key 键 + * @return true 存在 false不存在 + */ + public boolean hasKey(String key) { + try { + return redisTemplate.hasKey(key); + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + /** * JSON数据,转成Object */ diff --git a/xtoon-ops/pom.xml b/xtoon-ops/pom.xml index 7cbfd62..e89b376 100644 --- a/xtoon-ops/pom.xml +++ b/xtoon-ops/pom.xml @@ -16,6 +16,7 @@ xtoon-register-server xtoon-sentinel-server xtoon-auth-server + xtoon-gateway-server diff --git a/xtoon-ops/xtoon-auth-server/src/main/java/com/xtoon/cloud/ops/auth/config/WebSecurityConfig.java b/xtoon-ops/xtoon-auth-server/src/main/java/com/xtoon/cloud/ops/auth/config/WebSecurityConfig.java index dc0c541..cd50199 100644 --- a/xtoon-ops/xtoon-auth-server/src/main/java/com/xtoon/cloud/ops/auth/config/WebSecurityConfig.java +++ b/xtoon-ops/xtoon-auth-server/src/main/java/com/xtoon/cloud/ops/auth/config/WebSecurityConfig.java @@ -17,7 +17,7 @@ import org.springframework.security.config.annotation.authentication.builders.Au import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.factory.PasswordEncoderFactories; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.AuthenticationEntryPoint; @@ -61,7 +61,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Bean public PasswordEncoder passwordEncoder() { - return new BCryptPasswordEncoder(); + return PasswordEncoderFactories.createDelegatingPasswordEncoder(); } /** diff --git a/xtoon-ops/xtoon-auth-server/src/main/java/com/xtoon/cloud/ops/auth/controller/AuthController.java b/xtoon-ops/xtoon-auth-server/src/main/java/com/xtoon/cloud/ops/auth/controller/AuthController.java index e99c6d4..4521916 100644 --- a/xtoon-ops/xtoon-auth-server/src/main/java/com/xtoon/cloud/ops/auth/controller/AuthController.java +++ b/xtoon-ops/xtoon-auth-server/src/main/java/com/xtoon/cloud/ops/auth/controller/AuthController.java @@ -29,8 +29,8 @@ public class AuthController { @PostMapping("/token") public OAuth2AccessToken postAccessToken( - Principal principal, - @RequestParam Map parameters + Principal principal, + @RequestParam Map parameters ) throws HttpRequestMethodNotSupportedException { OAuth2AccessToken oAuth2AccessToken; oAuth2AccessToken = tokenEndpoint.postAccessToken(principal, parameters).getBody(); diff --git a/xtoon-ops/xtoon-auth-server/src/main/java/com/xtoon/cloud/ops/auth/service/UserDetailsServiceImpl.java b/xtoon-ops/xtoon-auth-server/src/main/java/com/xtoon/cloud/ops/auth/service/UserDetailsServiceImpl.java index d454a6c..8346bcc 100644 --- a/xtoon-ops/xtoon-auth-server/src/main/java/com/xtoon/cloud/ops/auth/service/UserDetailsServiceImpl.java +++ b/xtoon-ops/xtoon-auth-server/src/main/java/com/xtoon/cloud/ops/auth/service/UserDetailsServiceImpl.java @@ -1,5 +1,6 @@ package com.xtoon.cloud.ops.auth.service; +import com.xtoon.cloud.common.core.constant.AuthConstants; import com.xtoon.cloud.ops.auth.domain.User; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -22,6 +23,6 @@ public class UserDetailsServiceImpl implements UserDetailsService { @Override public User loadUserByUsername(String s) throws UsernameNotFoundException { // TODO test - return new User(1L,"test", new BCryptPasswordEncoder().encode("123456"), true,"client",null); + return new User(1L, "test", AuthConstants.BCRYPT + new BCryptPasswordEncoder().encode("123456"), true, "client", null); } } diff --git a/xtoon-ops/xtoon-gateway-server/pom.xml b/xtoon-ops/xtoon-gateway-server/pom.xml new file mode 100644 index 0000000..6a909f0 --- /dev/null +++ b/xtoon-ops/xtoon-gateway-server/pom.xml @@ -0,0 +1,82 @@ + + + + xtoon-ops + com.xtoon.cloud + 1.0.0 + + 4.0.0 + + xtoon-gateway-server + + + + com.xtoon.cloud + xtoon-common-core + ${xtoon-cloud.version} + + + com.xtoon.cloud + xtoon-common-web + ${xtoon-cloud.version} + + + com.xtoon.cloud + xtoon-common-redis + ${xtoon-cloud.version} + + + + org.springframework.cloud + spring-cloud-starter-gateway + + + org.springframework.boot + spring-boot-starter-data-redis-reactive + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-discovery + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-config + + + + com.alibaba.cloud + spring-cloud-alibaba-sentinel-gateway + + + com.alibaba.cloud + spring-cloud-starter-alibaba-sentinel + + + com.alibaba.csp + sentinel-transport-simple-http + + + + org.springframework.cloud + spring-cloud-starter-loadbalancer + + + + + org.springframework.security + spring-security-config + + + org.springframework.security + spring-security-oauth2-resource-server + + + org.springframework.security + spring-security-oauth2-jose + + + \ No newline at end of file diff --git a/xtoon-ops/xtoon-gateway-server/src/main/java/com/xtoon/cloud/ops/gateway/XtoonCloudGatewayApplication.java b/xtoon-ops/xtoon-gateway-server/src/main/java/com/xtoon/cloud/ops/gateway/XtoonCloudGatewayApplication.java new file mode 100644 index 0000000..db84e02 --- /dev/null +++ b/xtoon-ops/xtoon-gateway-server/src/main/java/com/xtoon/cloud/ops/gateway/XtoonCloudGatewayApplication.java @@ -0,0 +1,20 @@ +package com.xtoon.cloud.ops.gateway; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.client.discovery.EnableDiscoveryClient; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; + +/** + * 网关服务 + * + * @author haoxin + * @date 2021-06-15 + **/ +@EnableDiscoveryClient +@SpringBootApplication +public class XtoonCloudGatewayApplication { + public static void main(String[] args) { + SpringApplication.run(XtoonCloudGatewayApplication.class, args); + } +} diff --git a/xtoon-ops/xtoon-gateway-server/src/main/java/com/xtoon/cloud/ops/gateway/config/ResourceServerConfig.java b/xtoon-ops/xtoon-gateway-server/src/main/java/com/xtoon/cloud/ops/gateway/config/ResourceServerConfig.java new file mode 100644 index 0000000..09eea67 --- /dev/null +++ b/xtoon-ops/xtoon-gateway-server/src/main/java/com/xtoon/cloud/ops/gateway/config/ResourceServerConfig.java @@ -0,0 +1,92 @@ +package com.xtoon.cloud.ops.gateway.config; + +import cn.hutool.core.util.ArrayUtil; +import com.xtoon.cloud.common.core.constant.AuthConstants; +import com.xtoon.cloud.common.web.constant.ResultCode; +import com.xtoon.cloud.ops.gateway.security.AuthorizationManager; +import com.xtoon.cloud.ops.gateway.util.WebUtils; +import lombok.AllArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.convert.converter.Converter; +import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; +import org.springframework.security.config.web.server.ServerHttpSecurity; +import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; +import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter; +import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverterAdapter; +import org.springframework.security.web.server.SecurityWebFilterChain; +import org.springframework.security.web.server.ServerAuthenticationEntryPoint; +import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler; +import reactor.core.publisher.Mono; + +/** + * 资源服务器配置 + * + * @author haoxin + * @date 2021-06-15 + **/ +@AllArgsConstructor +@Configuration +@EnableWebFluxSecurity +public class ResourceServerConfig { + + private WhiteListConfig whiteListConfig; + + private AuthorizationManager authorizationManager; + + @Bean + public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) { + http.oauth2ResourceServer().jwt() + .jwtAuthenticationConverter(jwtAuthenticationConverter()); + http.oauth2ResourceServer().authenticationEntryPoint(authenticationEntryPoint()); + http.authorizeExchange() + .pathMatchers(ArrayUtil.toArray(whiteListConfig.getUrls(), String.class)).permitAll() + .anyExchange().access(authorizationManager) + .and() + .exceptionHandling() + .accessDeniedHandler(accessDeniedHandler()) + .authenticationEntryPoint(authenticationEntryPoint()) + .and().csrf().disable(); + + return http.build(); + } + + /** + * 未授权 + * + * @return + */ + @Bean + ServerAccessDeniedHandler accessDeniedHandler() { + return (exchange, denied) -> { + Mono mono = Mono.defer(() -> Mono.just(exchange.getResponse())) + .flatMap(response -> WebUtils.getAuthFailResult(response, ResultCode.UNAUTHORIZED.getCode())); + return mono; + }; + } + + /** + * token无效或者已过期自定义响应 + */ + @Bean + ServerAuthenticationEntryPoint authenticationEntryPoint() { + return (exchange, e) -> { + Mono mono = Mono.defer(() -> Mono.just(exchange.getResponse())) + .flatMap(response -> WebUtils.getAuthFailResult(response, ResultCode.UNAUTHORIZED.getCode())); + return mono; + }; + } + + @Bean + public Converter> jwtAuthenticationConverter() { + JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter(); + jwtGrantedAuthoritiesConverter.setAuthorityPrefix(AuthConstants.AUTHORITY_PREFIX); + jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName(AuthConstants.JWT_AUTHORITIES_KEY); + + JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter(); + jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter); + return new ReactiveJwtAuthenticationConverterAdapter(jwtAuthenticationConverter); + } +} diff --git a/xtoon-ops/xtoon-gateway-server/src/main/java/com/xtoon/cloud/ops/gateway/config/WhiteListConfig.java b/xtoon-ops/xtoon-gateway-server/src/main/java/com/xtoon/cloud/ops/gateway/config/WhiteListConfig.java new file mode 100644 index 0000000..028d09f --- /dev/null +++ b/xtoon-ops/xtoon-gateway-server/src/main/java/com/xtoon/cloud/ops/gateway/config/WhiteListConfig.java @@ -0,0 +1,22 @@ +package com.xtoon.cloud.ops.gateway.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import java.util.List; + +/** + * 白名单配置 + * + * @author haoxin + * @date 2021-05-29 + **/ +@Data +@Configuration +@ConfigurationProperties(prefix = "whitelist") +public class WhiteListConfig { + + private List urls; + +} diff --git a/xtoon-ops/xtoon-gateway-server/src/main/java/com/xtoon/cloud/ops/gateway/controller/IndexApi.java b/xtoon-ops/xtoon-gateway-server/src/main/java/com/xtoon/cloud/ops/gateway/controller/IndexApi.java new file mode 100644 index 0000000..b1acbe5 --- /dev/null +++ b/xtoon-ops/xtoon-gateway-server/src/main/java/com/xtoon/cloud/ops/gateway/controller/IndexApi.java @@ -0,0 +1,25 @@ +package com.xtoon.cloud.ops.gateway.controller; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import reactor.core.publisher.Mono; + +/** + * 网关 + * + * @author haoxin + * @date 2021-05-28 + **/ +@RestController +public class IndexApi { + + /** + * 网关测试 + * + * @return + */ + @RequestMapping("/") + public Mono index() { + return Mono.just("gamma cloud gateway"); + } +} diff --git a/xtoon-ops/xtoon-gateway-server/src/main/java/com/xtoon/cloud/ops/gateway/filter/AuthGlobalFilter.java b/xtoon-ops/xtoon-gateway-server/src/main/java/com/xtoon/cloud/ops/gateway/filter/AuthGlobalFilter.java new file mode 100644 index 0000000..1167572 --- /dev/null +++ b/xtoon-ops/xtoon-gateway-server/src/main/java/com/xtoon/cloud/ops/gateway/filter/AuthGlobalFilter.java @@ -0,0 +1,67 @@ +package com.xtoon.cloud.ops.gateway.filter; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import com.nimbusds.jose.JWSObject; +import com.xtoon.cloud.common.core.constant.AuthConstants; +import lombok.SneakyThrows; +import org.apache.logging.log4j.util.Strings; +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.http.server.reactive.ServerHttpResponse; +import org.springframework.stereotype.Component; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; + +/** + * 全局过滤器 + * + * @author haoxin + * @date 2021-05-28 + **/ +@Component +public class AuthGlobalFilter implements GlobalFilter, Ordered { + +// @Autowired +// private RedisService redisService; + + @SneakyThrows + @Override + public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { + + ServerHttpRequest request = exchange.getRequest(); + ServerHttpResponse response = exchange.getResponse(); + + // 非JWT或者JWT为空不作处理 + String token = request.getHeaders().getFirst(AuthConstants.AUTHORIZATION_KEY); + if (StrUtil.isBlank(token) || !token.startsWith(AuthConstants.AUTHORIZATION_PREFIX)) { + return chain.filter(exchange); + } + + // 解析JWT获取jti,以jti为key判断redis的黑名单列表是否存在,存在拦截响应token失效 + token = token.replace(AuthConstants.AUTHORIZATION_PREFIX, Strings.EMPTY); + JWSObject jwsObject = JWSObject.parse(token); + String payload = jwsObject.getPayload().toString(); + JSONObject jsonObject = JSONUtil.parseObj(payload); +// String jti = jsonObject.getStr(AuthConstants.JWT_JTI); +// Boolean isBlack = redisService.hasKey(AuthConstants.TOKEN_BLACKLIST_PREFIX + jti); +// if (isBlack) { +// return WebUtils.getAuthFailResult(response, ResultCode.UNAUTHORIZED.getCode()); +// } + + // 存在token且不是黑名单,request写入JWT的载体信息 + request = exchange.getRequest().mutate() + .header(AuthConstants.JWT_PAYLOAD_KEY, payload) + .build(); + exchange = exchange.mutate().request(request).build(); + return chain.filter(exchange); + } + + @Override + public int getOrder() { + return 0; + } +} diff --git a/xtoon-ops/xtoon-gateway-server/src/main/java/com/xtoon/cloud/ops/gateway/filter/RequestFilter.java b/xtoon-ops/xtoon-gateway-server/src/main/java/com/xtoon/cloud/ops/gateway/filter/RequestFilter.java new file mode 100644 index 0000000..af449ff --- /dev/null +++ b/xtoon-ops/xtoon-gateway-server/src/main/java/com/xtoon/cloud/ops/gateway/filter/RequestFilter.java @@ -0,0 +1,46 @@ +package com.xtoon.cloud.ops.gateway.filter; + +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.stereotype.Component; +import org.springframework.util.StringUtils; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; + +import java.util.Arrays; +import java.util.stream.Collectors; + +import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR; +import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.addOriginalRequestUrl; + +/** + * request过滤器 + * + * @author haoxin + * @date 2021-05-28 + **/ +@Component +public class RequestFilter implements GlobalFilter, Ordered { + + @Override + public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { + ServerHttpRequest request = exchange.getRequest(); + addOriginalRequestUrl(exchange, request.getURI()); + String rawPath = request.getURI().getRawPath(); + String newPath = "/" + Arrays.stream(StringUtils.tokenizeToStringArray(rawPath, "/")) + .skip(1L).collect(Collectors.joining("/")); + ServerHttpRequest newRequest = request.mutate() + .path(newPath) + .build(); + exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, newRequest.getURI()); + return chain.filter(exchange.mutate().request(newRequest.mutate().build()).build()); + } + + @Override + public int getOrder() { + return -1000; + } + +} diff --git a/xtoon-ops/xtoon-gateway-server/src/main/java/com/xtoon/cloud/ops/gateway/security/AuthorizationManager.java b/xtoon-ops/xtoon-gateway-server/src/main/java/com/xtoon/cloud/ops/gateway/security/AuthorizationManager.java new file mode 100644 index 0000000..5e85880 --- /dev/null +++ b/xtoon-ops/xtoon-gateway-server/src/main/java/com/xtoon/cloud/ops/gateway/security/AuthorizationManager.java @@ -0,0 +1,40 @@ +package com.xtoon.cloud.ops.gateway.security; + +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpMethod; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.security.authorization.AuthorizationDecision; +import org.springframework.security.authorization.ReactiveAuthorizationManager; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.server.authorization.AuthorizationContext; +import org.springframework.stereotype.Component; +import org.springframework.util.AntPathMatcher; +import org.springframework.util.PathMatcher; +import reactor.core.publisher.Mono; + +/** + * 鉴权管理器 + * + * @author haoxin + * @date 2021-05-28 + **/ +@Component +@AllArgsConstructor +@Slf4j +public class AuthorizationManager implements ReactiveAuthorizationManager { + + @Override + public Mono check(Mono mono, AuthorizationContext authorizationContext) { + ServerHttpRequest request = authorizationContext.getExchange().getRequest(); + String restPath = request.getMethodValue() + "_" + request.getURI().getPath(); + log.info("请求路径:{}", restPath); + PathMatcher pathMatcher = new AntPathMatcher(); + // 对应跨域的预检请求直接放行 + if (request.getMethod() == HttpMethod.OPTIONS) { + return Mono.just(new AuthorizationDecision(true)); + } + return mono.map(auth -> new AuthorizationDecision(auth.isAuthenticated())).defaultIfEmpty(new AuthorizationDecision(false)); + + } +} diff --git a/xtoon-ops/xtoon-gateway-server/src/main/java/com/xtoon/cloud/ops/gateway/util/WebUtils.java b/xtoon-ops/xtoon-gateway-server/src/main/java/com/xtoon/cloud/ops/gateway/util/WebUtils.java new file mode 100644 index 0000000..b25c066 --- /dev/null +++ b/xtoon-ops/xtoon-gateway-server/src/main/java/com/xtoon/cloud/ops/gateway/util/WebUtils.java @@ -0,0 +1,29 @@ +package com.xtoon.cloud.ops.gateway.util; + +import com.google.gson.Gson; +import com.xtoon.cloud.common.web.constant.ResultCode; +import com.xtoon.cloud.common.web.util.Result; +import org.springframework.core.io.buffer.DataBuffer; +import org.springframework.http.HttpStatus; +import org.springframework.http.server.reactive.ServerHttpResponse; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.nio.charset.StandardCharsets; + +/** + * web工具类 + * + * @author haoxin + * @date 2021-05-29 + **/ +public class WebUtils { + + public static Mono getAuthFailResult(ServerHttpResponse response, Integer code) { + response.setStatusCode(HttpStatus.OK); + response.getHeaders().add("Content-Type", "application/json;charset=UTF-8"); + byte[] responseByte = new Gson().toJson(Result.error(code, ResultCode.getValue(code).getMsg())).getBytes(StandardCharsets.UTF_8); + DataBuffer buffer = response.bufferFactory().wrap(responseByte); + return response.writeWith(Flux.just(buffer)); + } +} diff --git a/xtoon-ops/xtoon-gateway-server/src/main/resources/bootstrap.yml b/xtoon-ops/xtoon-gateway-server/src/main/resources/bootstrap.yml new file mode 100644 index 0000000..86331d3 --- /dev/null +++ b/xtoon-ops/xtoon-gateway-server/src/main/resources/bootstrap.yml @@ -0,0 +1,29 @@ +spring: + application: + name: xtoon-gateway-server + cloud: + nacos: + config: + server-addr: localhost:8848 + file-extension: yaml + enabled: true + boot: + admin: + client: + url: http://localhost:5001 + username: admin + password: admin + instance: + prefer-ip: true + +management: + health: + redis: + enabled: false + endpoint: + health: + show-details: always + endpoints: + web: + exposure: + include: "*" \ No newline at end of file