通用模块开发

This commit is contained in:
haoxin963
2021-06-15 23:09:21 +08:00
parent 082a975d76
commit b2229f6ca5
16 changed files with 476 additions and 5 deletions

View File

@@ -85,4 +85,6 @@ public interface AuthConstants {
String GRANT_TYPE_KEY = "grant_type";
String REFRESH_TOKEN = "refresh_token";
}

View File

@@ -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
*/

View File

@@ -16,6 +16,7 @@
<module>xtoon-register-server</module>
<module>xtoon-sentinel-server</module>
<module>xtoon-auth-server</module>
<module>xtoon-gateway-server</module>
</modules>
<properties>

View File

@@ -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();
}
/**

View File

@@ -29,8 +29,8 @@ public class AuthController {
@PostMapping("/token")
public OAuth2AccessToken postAccessToken(
Principal principal,
@RequestParam Map<String, String> parameters
Principal principal,
@RequestParam Map<String, String> parameters
) throws HttpRequestMethodNotSupportedException {
OAuth2AccessToken oAuth2AccessToken;
oAuth2AccessToken = tokenEndpoint.postAccessToken(principal, parameters).getBody();

View File

@@ -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);
}
}

View File

@@ -0,0 +1,82 @@
<?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">
<parent>
<artifactId>xtoon-ops</artifactId>
<groupId>com.xtoon.cloud</groupId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>xtoon-gateway-server</artifactId>
<dependencies>
<dependency>
<groupId>com.xtoon.cloud</groupId>
<artifactId>xtoon-common-core</artifactId>
<version>${xtoon-cloud.version}</version>
</dependency>
<dependency>
<groupId>com.xtoon.cloud</groupId>
<artifactId>xtoon-common-web</artifactId>
<version>${xtoon-cloud.version}</version>
</dependency>
<dependency>
<groupId>com.xtoon.cloud</groupId>
<artifactId>xtoon-common-redis</artifactId>
<version>${xtoon-cloud.version}</version>
</dependency>
<!--gateway 网关依赖,内置webflux 依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<!--注册中心客户端-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--配置中心客户端-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--断路器依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-simple-http</artifactId>
</dependency>
<!-- LB 扩展 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<!-- OAuth2资源服务器-->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-jose</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -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);
}
}

View File

@@ -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<Void> 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<Void> mono = Mono.defer(() -> Mono.just(exchange.getResponse()))
.flatMap(response -> WebUtils.getAuthFailResult(response, ResultCode.UNAUTHORIZED.getCode()));
return mono;
};
}
@Bean
public Converter<Jwt, ? extends Mono<? extends AbstractAuthenticationToken>> 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);
}
}

View File

@@ -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<String> urls;
}

View File

@@ -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<String> index() {
return Mono.just("gamma cloud gateway");
}
}

View File

@@ -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<Void> 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;
}
}

View File

@@ -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<Void> 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;
}
}

View File

@@ -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<AuthorizationContext> {
@Override
public Mono<AuthorizationDecision> check(Mono<Authentication> 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));
}
}

View File

@@ -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<Void> 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));
}
}

View File

@@ -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: "*"