From de5d9ea54c3480e17a6296338c1d2e4317927f7f Mon Sep 17 00:00:00 2001 From: ytChen <1650611030@qq.com> Date: Tue, 12 Mar 2024 17:09:47 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/boot/config/InterceptConfig.java | 6 +- .../admin/boot/controller/testController.java | 18 +- .../exception/GlobalExceptionHandler.java | 41 +++++ .../boot/interceptor/JwtInterceptor.java | 16 +- .../recovery/auth/aspect/WebLogAspect.java | 4 +- .../auth/config/SaTokenConfigure.java | 9 + .../auth/controller/AuthController.java | 29 +++- hoe-auth/src/main/resources/bootstrap.yml | 16 +- .../common/base/constant/Constants.java | 12 +- .../common/base/result/ResultCode.java | 1 + .../common/base/util/HspHostUtil.java | 3 + hoe-gateway/pom.xml | 6 +- .../gateway/config/SaTokenConfigure.java | 55 ++++-- .../security/ResourceServerConfig.java | 160 +++++++++--------- .../security/ResourceServerManager.java | 156 ++++++++--------- .../security/SecurityGlobalFilter.java | 126 +++++++------- pom.xml | 8 +- 17 files changed, 399 insertions(+), 267 deletions(-) diff --git a/hoe-admin/admin-boot/src/main/java/com/recovery/admin/boot/config/InterceptConfig.java b/hoe-admin/admin-boot/src/main/java/com/recovery/admin/boot/config/InterceptConfig.java index d24c8a9..c86bb72 100644 --- a/hoe-admin/admin-boot/src/main/java/com/recovery/admin/boot/config/InterceptConfig.java +++ b/hoe-admin/admin-boot/src/main/java/com/recovery/admin/boot/config/InterceptConfig.java @@ -2,6 +2,7 @@ package com.recovery.admin.boot.config; import com.recovery.admin.boot.interceptor.JwtInterceptor; +import com.recovery.common.base.config.redis.RedisCache; import com.recovery.common.base.util.RedisUtil; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; @@ -13,17 +14,16 @@ import javax.annotation.Resource; @Configuration public class InterceptConfig implements WebMvcConfigurer { @Resource - RedisUtil redisUtil; + RedisCache redisCache; @Override public void addInterceptors(InterceptorRegistry registry) { //添加拦截器 - registry.addInterceptor(new JwtInterceptor(redisUtil)) + registry.addInterceptor(new JwtInterceptor(redisCache)) //拦截的路径 需要进行token验证的路径 .addPathPatterns("/**") //放行的路径 .excludePathPatterns("/api/rest/users/getUserByUsername") .excludePathPatterns("/api/user/sendMsg") - .excludePathPatterns("/api/user/loginPhone") //放行swagger 测试验证 .excludePathPatterns("/api/user/get"); } diff --git a/hoe-admin/admin-boot/src/main/java/com/recovery/admin/boot/controller/testController.java b/hoe-admin/admin-boot/src/main/java/com/recovery/admin/boot/controller/testController.java index 7967cea..b54c512 100644 --- a/hoe-admin/admin-boot/src/main/java/com/recovery/admin/boot/controller/testController.java +++ b/hoe-admin/admin-boot/src/main/java/com/recovery/admin/boot/controller/testController.java @@ -1,7 +1,9 @@ package com.recovery.admin.boot.controller; +import cn.dev33.satoken.stp.StpUtil; import com.recovery.admin.boot.service.ISysUserService; +import com.recovery.common.base.constant.Constants; import com.recovery.common.base.dto.UserAuthDTO; import com.recovery.common.base.result.ApiResult; import com.recovery.common.base.util.HspHostUtil; @@ -33,7 +35,21 @@ public class testController { public ApiResult cs(@RequestParam String name, HttpServletRequest request) { UserAuthDTO authDTO = iSysUserService.getByUsername(name); log.info("测试库:"+authDTO.getStatus()); - log.info(redisUtil.get("123").toString()); + String user = StpUtil.getLoginIdDefaultNull()+""; + StpUtil.kickoutByTokenValue(Constants.LOGIN_USRE_TOKEN); +// StpUtil.logout(); return ApiResult.ok(authDTO); } + + + /** + * cs + */ + @GetMapping("/cs1") + public ApiResult ccss(HttpServletRequest request) { + String user = StpUtil.getLoginIdDefaultNull()+""; + StpUtil.kickout(StpUtil.getLoginId()); +// StpUtil.logout(); + return ApiResult.ok("踢下线"); + } } diff --git a/hoe-admin/admin-boot/src/main/java/com/recovery/admin/boot/exception/GlobalExceptionHandler.java b/hoe-admin/admin-boot/src/main/java/com/recovery/admin/boot/exception/GlobalExceptionHandler.java index 7d60485..d2e5082 100644 --- a/hoe-admin/admin-boot/src/main/java/com/recovery/admin/boot/exception/GlobalExceptionHandler.java +++ b/hoe-admin/admin-boot/src/main/java/com/recovery/admin/boot/exception/GlobalExceptionHandler.java @@ -1,6 +1,8 @@ package com.recovery.admin.boot.exception; +import cn.dev33.satoken.exception.NotLoginException; +import cn.dev33.satoken.util.SaResult; import com.recovery.common.base.result.ApiResult; import com.recovery.common.base.result.ResultCode; import lombok.extern.slf4j.Slf4j; @@ -28,6 +30,45 @@ public class GlobalExceptionHandler { return ApiResult.failed(ResultCode.SYSTEM_EXECUTION_ERROR.getCode(), e.getMessage()); } + // 全局异常拦截(拦截项目中的NotLoginException异常) + @ExceptionHandler(NotLoginException.class) + public ApiResult handlerNotLoginException(NotLoginException nle) + throws Exception { + + // 打印堆栈,以供调试 + nle.printStackTrace(); + + // 判断场景值,定制化异常信息 + String message = ""; + if(nle.getType().equals(NotLoginException.NOT_TOKEN)) { + message = "未能读取到有效 token"; + } + else if(nle.getType().equals(NotLoginException.INVALID_TOKEN)) { + message = "token 无效"; + } + else if(nle.getType().equals(NotLoginException.TOKEN_TIMEOUT)) { + message = "token 已过期"; + } + else if(nle.getType().equals(NotLoginException.BE_REPLACED)) { + message = "token 已被顶下线"; + } + else if(nle.getType().equals(NotLoginException.KICK_OUT)) { + message = "token 已被踢下线"; + } + else if(nle.getType().equals(NotLoginException.TOKEN_FREEZE)) { + message = "token 已被冻结"; + } + else if(nle.getType().equals(NotLoginException.NO_PREFIX)) { + message = "未按照指定前缀提交 token"; + } + else { + message = "当前会话未登录"; + } + + // 返回给前端 + return ApiResult.failed(message); + } + //Security // @ExceptionHandler(value = AccessDeniedException.class) // public void accessDeniedException(AccessDeniedException e) { diff --git a/hoe-admin/admin-boot/src/main/java/com/recovery/admin/boot/interceptor/JwtInterceptor.java b/hoe-admin/admin-boot/src/main/java/com/recovery/admin/boot/interceptor/JwtInterceptor.java index a032c55..bc6eace 100644 --- a/hoe-admin/admin-boot/src/main/java/com/recovery/admin/boot/interceptor/JwtInterceptor.java +++ b/hoe-admin/admin-boot/src/main/java/com/recovery/admin/boot/interceptor/JwtInterceptor.java @@ -1,11 +1,13 @@ package com.recovery.admin.boot.interceptor; +import cn.dev33.satoken.stp.StpUtil; import com.alibaba.fastjson.JSONObject; import com.auth0.jwt.interfaces.DecodedJWT; import com.recovery.admin.boot.exception.BusinessException; import com.recovery.admin.boot.filter.RequestWrapper; +import com.recovery.common.base.config.redis.RedisCache; import com.recovery.common.base.constant.Constants; import com.recovery.common.base.result.ResultCode; import com.recovery.common.base.util.RedisUtil; @@ -27,10 +29,10 @@ import java.util.HashMap; public class JwtInterceptor implements HandlerInterceptor { @Resource - RedisUtil redisUtil; + RedisCache redisCache; - public JwtInterceptor(RedisUtil redisUtil) { - this.redisUtil = redisUtil; + public JwtInterceptor(RedisCache redisCache) { + this.redisCache = redisCache; } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { @@ -45,7 +47,10 @@ public class JwtInterceptor implements HandlerInterceptor { if (StringUtils.isEmpty(token)) { throw new BusinessException(ResultCode.LOGIN_ERROR); } - boolean rest = redisUtil.hasKey("userToken:" +token); + if (!StpUtil.isLogin()) { + throw new BusinessException(ResultCode.IS_LOGIN_EXPIRE_ERROR); + } + boolean rest = redisCache.isExists(Constants.REDIS_USRE_TOKEN+token); if (!rest) { throw new BusinessException(ResultCode.LOGIN_EXPIRE_ERROR); } @@ -53,7 +58,8 @@ public class JwtInterceptor implements HandlerInterceptor { request.setAttribute(Constants.LOGIN_USRE_TOKEN,token); try { //如果验证成功放行请求 - DecodedJWT verify = JwtUtils.verifyToken(token); +// DecodedJWT verify = JwtUtils.verifyToken(token); + return true; } catch (Exception exception) diff --git a/hoe-auth/src/main/java/com/recovery/auth/aspect/WebLogAspect.java b/hoe-auth/src/main/java/com/recovery/auth/aspect/WebLogAspect.java index 4893570..d237083 100644 --- a/hoe-auth/src/main/java/com/recovery/auth/aspect/WebLogAspect.java +++ b/hoe-auth/src/main/java/com/recovery/auth/aspect/WebLogAspect.java @@ -59,7 +59,7 @@ public class WebLogAspect { //切割获取访问目标模块 String[] split = path.split("/"); String module = split[0]; - System.out.println(RedisUtils.getDBInfoByHostAndModule(stringRedisTemplate,hospitalHost,"admin")); + log.info(RedisUtils.getDBInfoByHostAndModule(stringRedisTemplate,hospitalHost,"admin")+""); //根据域名和请求的模块名查询目标数据库 HttpSession session = request.getSession(); /** @@ -71,6 +71,8 @@ public class WebLogAspect { //执行完切面后,将线程共享中的数据源名称清空 @After("webLog()") public void after(JoinPoint joinPoint){ + //清除 + HspHostUtil.clear(); DataSourceContextHolder.clearDBType(); } } diff --git a/hoe-auth/src/main/java/com/recovery/auth/config/SaTokenConfigure.java b/hoe-auth/src/main/java/com/recovery/auth/config/SaTokenConfigure.java index 68849d3..0f5fcd8 100644 --- a/hoe-auth/src/main/java/com/recovery/auth/config/SaTokenConfigure.java +++ b/hoe-auth/src/main/java/com/recovery/auth/config/SaTokenConfigure.java @@ -2,7 +2,10 @@ package com.recovery.auth.config; import cn.dev33.satoken.context.SaHolder; import cn.dev33.satoken.filter.SaServletFilter; +import cn.dev33.satoken.jwt.StpLogicJwtForMixin; +import cn.dev33.satoken.jwt.StpLogicJwtForSimple; import cn.dev33.satoken.same.SaSameUtil; +import cn.dev33.satoken.stp.StpLogic; import cn.dev33.satoken.util.SaResult; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -29,4 +32,10 @@ public class SaTokenConfigure implements WebMvcConfigurer { }) ; } + + // Sa-Token 整合 jwt (Simple 简单模式) + @Bean + public StpLogic getStpLogicJwt() { + return new StpLogicJwtForSimple(); + } } \ No newline at end of file diff --git a/hoe-auth/src/main/java/com/recovery/auth/controller/AuthController.java b/hoe-auth/src/main/java/com/recovery/auth/controller/AuthController.java index fab7eba..faba712 100644 --- a/hoe-auth/src/main/java/com/recovery/auth/controller/AuthController.java +++ b/hoe-auth/src/main/java/com/recovery/auth/controller/AuthController.java @@ -8,10 +8,14 @@ import com.recovery.auth.exception.BusinessException; import com.recovery.auth.feign.UserFeignClient; import com.recovery.auth.security.details.user.JwtAuthenticationRequest; import com.recovery.auth.service.AuthService; +import com.recovery.common.base.config.redis.RedisCache; +import com.recovery.common.base.constant.Constants; import com.recovery.common.base.dto.UserAuthDTO; import com.recovery.common.base.dto.UserAuthorityDto; import com.recovery.common.base.result.ApiResult; import com.recovery.common.base.result.ResultCode; +import com.recovery.common.base.util.EncryptUtil; +import com.recovery.common.base.util.RedisUtil; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.util.StringUtils; @@ -34,6 +38,8 @@ public class AuthController { AuthService authService; @Resource UserFeignClient userFeignClient; + @Resource + RedisCache redisCache; @PostMapping("/token") public ApiResult postAccessToken(@RequestBody JwtAuthenticationRequest authenticationRequest, HttpServletRequest request){ @@ -48,8 +54,8 @@ public class AuthController { return ApiResult.ok(map); } - @GetMapping("/doLogin") - public SaResult doLogin(@RequestBody JwtAuthenticationRequest authenticationRequest) { + @PostMapping("/doLogin") + public ApiResult doLogin(@RequestBody JwtAuthenticationRequest authenticationRequest) { if(StringUtils.isEmpty(authenticationRequest.getUsername())){ throw new BusinessException("账户不能为空"); } @@ -62,16 +68,25 @@ public class AuthController { if (ResultCode.SUCCESS.getCode().equals(result.getCode())) { userDetails = result.getData(); } - // 此处仅作模拟示例,真实项目需要从数据库中查询数据进行比对 - if (userDetails.getUserName().equals(authenticationRequest.getUsername()) && userDetails.getPassword().equals(authenticationRequest.getPassword())) { + // 数据库中查询数据进行比对 + if (userDetails.getUserName().equals(authenticationRequest.getUsername()) && userDetails.getPassword().equals(EncryptUtil.encrypt(authenticationRequest.getPassword()))) { log.info("密码校验成功!"); - StpUtil.login(userDetails.getUserName()); + StpUtil.login(userDetails.getUserId(),"PC"); }else { - return SaResult.error("密码错误"); + return ApiResult.failed("密码错误"); + } + //认证通过 使用userid 储存用户信息 + try { + redisCache.put_obj(Constants.REDIS_USRE_INFO + userDetails.getUserId(),userDetails , Constants.REDIS_TOKEN_TIME); + } catch (Exception e) { + log.error("redis储存token报错"+e.getMessage(),e); } // 第3步,返回给前端 SaTokenInfo tokenInfo = StpUtil.getTokenInfo(); - return SaResult.ok("登录成功").setData(tokenInfo); + HashMap map= new HashMap(); + map.put("token",tokenInfo.getTokenValue()); + map.put("user",userDetails); + return ApiResult.ok(map); } // @GetMapping("/public-key") diff --git a/hoe-auth/src/main/resources/bootstrap.yml b/hoe-auth/src/main/resources/bootstrap.yml index 16cac24..df92dee 100644 --- a/hoe-auth/src/main/resources/bootstrap.yml +++ b/hoe-auth/src/main/resources/bootstrap.yml @@ -9,8 +9,8 @@ spring: cloud: nacos: discovery: - # metadata: - # serviceGroup: ytChen + metadata: + serviceGroup: ytChen server-addr: localhost:8848 namespace: 11bfd099-10d6-4f2c-b969-58b76e435cce config: @@ -21,3 +21,15 @@ spring: namespace: 11bfd099-10d6-4f2c-b969-58b76e435cce server: port: 9001 + + + +sa-token: + # jwt秘钥 + jwt-secret-key: asdasdasifhueuiwyurfewbfjsdafjk123 + token-name: satoken + timeout: -1 + active-timeout: -1 + is-concurrent: false + is-share: false + is-log: true diff --git a/hoe-common/common-base/src/main/java/com/recovery/common/base/constant/Constants.java b/hoe-common/common-base/src/main/java/com/recovery/common/base/constant/Constants.java index e4664fa..8227cc0 100644 --- a/hoe-common/common-base/src/main/java/com/recovery/common/base/constant/Constants.java +++ b/hoe-common/common-base/src/main/java/com/recovery/common/base/constant/Constants.java @@ -22,7 +22,17 @@ public class Constants { public final static String UPLOAD_SIZE_ERR_MSG = "上传大小错误"; - public static final String LOGIN_USRE_TOKEN = "x-userToken"; + public static final String LOGIN_USRE_TOKEN = "satoken"; + + //token + public static final String REDIS_USRE_TOKEN = "satoken:login:token:"; + + //用户信息 + public static final String REDIS_USRE_INFO = "satoken:login:userInfo:"; + + //token过期时间 七天 + public static final Integer REDIS_TOKEN_TIME = 7*24*60*60; + public final static String WHOLE_DOMAIN_REDIS_KEY = "HOE_WHOLE_DOMAIN_REDIS_KEY"; diff --git a/hoe-common/common-base/src/main/java/com/recovery/common/base/result/ResultCode.java b/hoe-common/common-base/src/main/java/com/recovery/common/base/result/ResultCode.java index 7b8632a..846f04a 100644 --- a/hoe-common/common-base/src/main/java/com/recovery/common/base/result/ResultCode.java +++ b/hoe-common/common-base/src/main/java/com/recovery/common/base/result/ResultCode.java @@ -22,6 +22,7 @@ public enum ResultCode implements IResultCode, Serializable { LOGIN_ERROR("1001", "未登录"), LOGIN_EXPIRE_ERROR("1002", "登录过期,请重新登录!"), + IS_LOGIN_EXPIRE_ERROR("1003", "您已被踢下线,请重新登录!"), SYSTEM_EXECUTION_ERROR("999999", "系统执行出错"), USERNAME_OR_PASSWORD_ERROR("A00100", "用户名或密码错误"), USER_NOT_EXIST("A00101", "用户不存在"), diff --git a/hoe-common/common-base/src/main/java/com/recovery/common/base/util/HspHostUtil.java b/hoe-common/common-base/src/main/java/com/recovery/common/base/util/HspHostUtil.java index 3fc338d..b5701cc 100644 --- a/hoe-common/common-base/src/main/java/com/recovery/common/base/util/HspHostUtil.java +++ b/hoe-common/common-base/src/main/java/com/recovery/common/base/util/HspHostUtil.java @@ -28,6 +28,9 @@ public class HspHostUtil { public static void setHspHost(String hspHost) { HSP_HSOT.set(hspHost); } + public static void clear() { + HSP_HSOT.remove(); + } /** diff --git a/hoe-gateway/pom.xml b/hoe-gateway/pom.xml index 4af5e16..a60c4da 100644 --- a/hoe-gateway/pom.xml +++ b/hoe-gateway/pom.xml @@ -54,13 +54,11 @@ httpclient 4.5.13 - - - + cn.dev33 sa-token-reactor-spring-boot-starter - 1.34.0 + 1.37.0 diff --git a/hoe-gateway/src/main/java/com/recovery/gateway/config/SaTokenConfigure.java b/hoe-gateway/src/main/java/com/recovery/gateway/config/SaTokenConfigure.java index 9c949f7..f994091 100644 --- a/hoe-gateway/src/main/java/com/recovery/gateway/config/SaTokenConfigure.java +++ b/hoe-gateway/src/main/java/com/recovery/gateway/config/SaTokenConfigure.java @@ -5,6 +5,7 @@ import cn.dev33.satoken.reactor.filter.SaReactorFilter; import cn.dev33.satoken.router.SaRouter; import cn.dev33.satoken.stp.StpUtil; import cn.dev33.satoken.util.SaResult; +import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -12,31 +13,55 @@ import org.springframework.context.annotation.Configuration; * [Sa-Token 权限认证] 配置类 */ @Configuration +@Slf4j public class SaTokenConfigure { - // 注册 Sa-Token全局过滤器 + + /** + * 注册 [Sa-Token全局过滤器] + */ @Bean public SaReactorFilter getSaReactorFilter() { return new SaReactorFilter() - // 拦截地址 - .addInclude("/**") /* 拦截全部path */ - // 开放地址 + // 指定 [拦截路由] + .addInclude("/**") /* 拦截所有path */ + // 指定 [放行路由] .addExclude("/favicon.ico") - // 鉴权方法:每次访问进入 + // 指定[认证函数]: 每次请求执行 .setAuth(obj -> { - // 登录校验 -- 拦截所有路由,并排除/user/doLogin 用于开放登录 - SaRouter.match("/**", "/auth/oauth/doLogin", r -> StpUtil.checkLogin()); - - // 权限认证 -- 不同模块, 校验不同权限 - SaRouter.match("/api/test1", r -> StpUtil.checkPermission("api.test1")); - SaRouter.match("/api/test2", r -> StpUtil.checkPermission("api.test2")); - SaRouter.match("/api/test3", r -> StpUtil.checkRoleOr("admin", "super")); - - // 更多匹配 ... */ + log.info("---------- sa全局认证"); + // SaRouter.match("/test/test", () -> StpUtil.checkLogin()); }) - // 异常处理方法:每次setAuth函数出现异常时进入 + // 指定[异常处理函数]:每次[认证函数]发生异常时执行此函数 .setError(e -> { + log.info("---------- sa全局异常 "); return SaResult.error(e.getMessage()); }) ; } + // 注册 Sa-Token全局过滤器 +// @Bean +// public SaReactorFilter getSaReactorFilter() { +// return new SaReactorFilter() +// // 拦截地址 +// .addInclude("/**") /* 拦截全部path */ +// // 开放地址 +// .addExclude("/favicon.ico") +// // 鉴权方法:每次访问进入 +// .setAuth(obj -> { +// // 登录校验 -- 拦截所有路由,并排除/user/doLogin 用于开放登录 +// SaRouter.match("/**", "/auth/oauth/doLogin", r -> StpUtil.checkLogin()); +// +// // 权限认证 -- 不同模块, 校验不同权限 +// SaRouter.match("/api/test1", r -> StpUtil.checkPermission("api.test1")); +// SaRouter.match("/api/test2", r -> StpUtil.checkPermission("api.test2")); +// SaRouter.match("/api/test3", r -> StpUtil.checkRoleOr("admin", "super")); +// +// // 更多匹配 ... */ +// }) +// // 异常处理方法:每次setAuth函数出现异常时进入 +// .setError(e -> { +// return SaResult.error(e.getMessage()); +// }) +// ; +// } } diff --git a/hoe-gateway/src/main/java/com/recovery/gateway/security/ResourceServerConfig.java b/hoe-gateway/src/main/java/com/recovery/gateway/security/ResourceServerConfig.java index e2af47c..7a51d6b 100644 --- a/hoe-gateway/src/main/java/com/recovery/gateway/security/ResourceServerConfig.java +++ b/hoe-gateway/src/main/java/com/recovery/gateway/security/ResourceServerConfig.java @@ -1,80 +1,80 @@ -//package com.recovery.gateway.security; -// -// -//import com.recovery.common.base.constant.SecurityConstants; -//import com.recovery.common.base.result.ResultCode; -//import com.recovery.gateway.util.ResponseUtils; -//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: -// */ -//@AllArgsConstructor -//@Configuration -//@EnableWebFluxSecurity -//public class ResourceServerConfig { -// -// private final ResourceServerManager resourceServerManager; -// -// @Bean -// public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) { -// http.oauth2ResourceServer().jwt().jwtAuthenticationConverter(jwtAuthenticationConverter()); -// http.oauth2ResourceServer().authenticationEntryPoint(authenticationEntryPoint()); -// http.authorizeExchange() -// .anyExchange().access(resourceServerManager) -// .and() -// .exceptionHandling() -// .accessDeniedHandler(accessDeniedHandler()) // 处理未授权 -// .authenticationEntryPoint(authenticationEntryPoint()) //处理未认证 -// .and().csrf().disable(); -// return http.build(); -// } -// -// /** -// * 自定义未授权响应 -// */ -// @Bean -// ServerAccessDeniedHandler accessDeniedHandler() { -// return (exchange, denied) -> { -// Mono mono = Mono.defer(() -> Mono.just(exchange.getResponse())) -// .flatMap(response -> ResponseUtils.writeErrorInfo(response, ResultCode.ACCESS_UNAUTHORIZED)); -// return mono; -// }; -// } -// -// /** -// * token无效或者已过期自定义响应 -// */ -// @Bean -// ServerAuthenticationEntryPoint authenticationEntryPoint() { -// return (exchange, e) -> { -// Mono mono = Mono.defer(() -> Mono.just(exchange.getResponse())) -// .flatMap(response -> ResponseUtils.writeErrorInfo(response, ResultCode.TOKEN_INVALID_OR_EXPIRED)); -// return mono; -// }; -// } -// -// @Bean -// public Converter> jwtAuthenticationConverter() { -// JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter(); -// jwtGrantedAuthoritiesConverter.setAuthorityPrefix(SecurityConstants.AUTHORITY_PREFIX); -// jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName(SecurityConstants.JWT_AUTHORITIES_KEY); -// -// JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter(); -// jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter); -// return new ReactiveJwtAuthenticationConverterAdapter(jwtAuthenticationConverter); -// } -//} \ No newline at end of file +package com.recovery.gateway.security; + + +import com.recovery.common.base.constant.SecurityConstants; +import com.recovery.common.base.result.ResultCode; +import com.recovery.gateway.util.ResponseUtils; +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: + */ +@AllArgsConstructor +@Configuration +@EnableWebFluxSecurity +public class ResourceServerConfig { + + private final ResourceServerManager resourceServerManager; + + @Bean + public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) { + http.oauth2ResourceServer().jwt().jwtAuthenticationConverter(jwtAuthenticationConverter()); + http.oauth2ResourceServer().authenticationEntryPoint(authenticationEntryPoint()); + http.authorizeExchange() + .anyExchange().access(resourceServerManager) + .and() + .exceptionHandling() + .accessDeniedHandler(accessDeniedHandler()) // 处理未授权 + .authenticationEntryPoint(authenticationEntryPoint()) //处理未认证 + .and().csrf().disable(); + return http.build(); + } + + /** + * 自定义未授权响应 + */ + @Bean + ServerAccessDeniedHandler accessDeniedHandler() { + return (exchange, denied) -> { + Mono mono = Mono.defer(() -> Mono.just(exchange.getResponse())) + .flatMap(response -> ResponseUtils.writeErrorInfo(response, ResultCode.ACCESS_UNAUTHORIZED)); + return mono; + }; + } + + /** + * token无效或者已过期自定义响应 + */ + @Bean + ServerAuthenticationEntryPoint authenticationEntryPoint() { + return (exchange, e) -> { + Mono mono = Mono.defer(() -> Mono.just(exchange.getResponse())) + .flatMap(response -> ResponseUtils.writeErrorInfo(response, ResultCode.TOKEN_INVALID_OR_EXPIRED)); + return mono; + }; + } + + @Bean + public Converter> jwtAuthenticationConverter() { + JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter(); + jwtGrantedAuthoritiesConverter.setAuthorityPrefix(SecurityConstants.AUTHORITY_PREFIX); + jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName(SecurityConstants.JWT_AUTHORITIES_KEY); + + JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter(); + jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter); + return new ReactiveJwtAuthenticationConverterAdapter(jwtAuthenticationConverter); + } +} \ No newline at end of file diff --git a/hoe-gateway/src/main/java/com/recovery/gateway/security/ResourceServerManager.java b/hoe-gateway/src/main/java/com/recovery/gateway/security/ResourceServerManager.java index 5a0c519..7565d78 100644 --- a/hoe-gateway/src/main/java/com/recovery/gateway/security/ResourceServerManager.java +++ b/hoe-gateway/src/main/java/com/recovery/gateway/security/ResourceServerManager.java @@ -1,79 +1,79 @@ -//package com.recovery.gateway.security; -// -//import cn.hutool.core.collection.CollectionUtil; -//import cn.hutool.core.convert.Convert; -//import cn.hutool.core.util.StrUtil; -// -//import com.recovery.common.base.constant.GlobalConstants; -//import com.recovery.common.base.constant.SecurityConstants; -//import com.recovery.gateway.util.UrlPatternUtils; -//import lombok.RequiredArgsConstructor; -//import lombok.Setter; -//import lombok.extern.slf4j.Slf4j; -//import org.springframework.boot.context.properties.ConfigurationProperties; -//import org.springframework.data.redis.core.RedisTemplate; -//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.core.GrantedAuthority; -//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; -// -//import java.util.ArrayList; -//import java.util.List; -//import java.util.Map; -///** -// * Created with IntelliJ IDEA. -// * -// * @author: -// * @date: 2021/11/24 -// * @description: -// * @modifiedBy: -// * @version: 1.0 -// */ -//@Component -//@RequiredArgsConstructor -//@Slf4j -//@ConfigurationProperties(prefix = "security") -//public class ResourceServerManager implements ReactiveAuthorizationManager { -// private final RedisTemplate redisTemplate; -// -// @Setter -// private List ignoreUrls; -// -// @Override -// public Mono check(Mono mono, AuthorizationContext authorizationContext) { -// ServerHttpRequest request = authorizationContext.getExchange().getRequest(); -// if (request.getMethod() == HttpMethod.OPTIONS) { // 预检请求放行 -// return Mono.just(new AuthorizationDecision(true)); +package com.recovery.gateway.security; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.util.StrUtil; + +import com.recovery.common.base.constant.GlobalConstants; +import com.recovery.common.base.constant.SecurityConstants; +import com.recovery.gateway.util.UrlPatternUtils; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.data.redis.core.RedisTemplate; +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.core.GrantedAuthority; +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; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +/** + * Created with IntelliJ IDEA. + * + * @author: + * @date: 2021/11/24 + * @description: + * @modifiedBy: + * @version: 1.0 + */ +@Component +@RequiredArgsConstructor +@Slf4j +@ConfigurationProperties(prefix = "security") +public class ResourceServerManager implements ReactiveAuthorizationManager { + private final RedisTemplate redisTemplate; + + @Setter + private List ignoreUrls; + + @Override + public Mono check(Mono mono, AuthorizationContext authorizationContext) { + ServerHttpRequest request = authorizationContext.getExchange().getRequest(); + if (request.getMethod() == HttpMethod.OPTIONS) { // 预检请求放行 + return Mono.just(new AuthorizationDecision(true)); + } + PathMatcher pathMatcher = new AntPathMatcher(); + String method = request.getMethodValue(); + String path = request.getURI().getPath(); + + // 跳过token校验,放在这里去做是为了能够动态刷新 +// if (skipValid(path)) { + return Mono.just(new AuthorizationDecision(true)); // } -// PathMatcher pathMatcher = new AntPathMatcher(); -// String method = request.getMethodValue(); -// String path = request.getURI().getPath(); -// -// // 跳过token校验,放在这里去做是为了能够动态刷新 -//// if (skipValid(path)) { -// return Mono.just(new AuthorizationDecision(true)); -//// } -// } -// -// /** -// * 跳过校验 -// * -// * @param path -// * @return -// */ -// private boolean skipValid(String path) { -// for (String skipPath : ignoreUrls) { -// if (UrlPatternUtils.match(skipPath, path)) { -// return true; -// } -// } -// return false; -// } -//} \ No newline at end of file + } + + /** + * 跳过校验 + * + * @param path + * @return + */ + private boolean skipValid(String path) { + for (String skipPath : ignoreUrls) { + if (UrlPatternUtils.match(skipPath, path)) { + return true; + } + } + return false; + } +} \ No newline at end of file diff --git a/hoe-gateway/src/main/java/com/recovery/gateway/security/SecurityGlobalFilter.java b/hoe-gateway/src/main/java/com/recovery/gateway/security/SecurityGlobalFilter.java index ff3fa02..2cddae4 100644 --- a/hoe-gateway/src/main/java/com/recovery/gateway/security/SecurityGlobalFilter.java +++ b/hoe-gateway/src/main/java/com/recovery/gateway/security/SecurityGlobalFilter.java @@ -1,69 +1,57 @@ -//package com.recovery.gateway.security; -// -//import cn.dev33.satoken.same.SaSameUtil; -//import cn.hutool.core.util.StrUtil; -// -//import com.nimbusds.jose.JWSObject; -//import com.recovery.common.base.constant.SecurityConstants; -//import lombok.RequiredArgsConstructor; -//import lombok.SneakyThrows; -//import lombok.extern.slf4j.Slf4j; -//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.data.redis.core.RedisTemplate; -//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; -// -//import java.net.URLEncoder; -///** -// * Created with IntelliJ IDEA. -// * -// * @author: AI码师 关注公众号"AI码师"获取完整源码 -// * @date: 2021/11/24 -// * @description: -// * @modifiedBy: -// * @version: 1.0 -// */ -//@Component -//@Slf4j -//@RequiredArgsConstructor -//public class SecurityGlobalFilter implements GlobalFilter, Ordered { -// -// @SneakyThrows -// @Override -// public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { -// -// ServerHttpRequest request = exchange.getRequest(); -// log.info("接受到请求地址:"+request.getURI()); -// ServerHttpResponse response = exchange.getResponse(); -// // 不是正确的的JWT不做解析处理 -// String token = request.getHeaders().getFirst(SecurityConstants.AUTHORIZATION_KEY); -// if (StrUtil.isBlank(token) || !StrUtil.startWithIgnoreCase(token, SecurityConstants.JWT_PREFIX)) { -// return chain.filter(exchange); -// } -// // 为请求追加 Same-Token 参数 -// request.mutate() -// // 为请求追加 Same-Token 参数 -// .header(SaSameUtil.SAME_TOKEN, SaSameUtil.getToken()) -// .build(); -// //结束 -// // 解析JWT获取jti,以jti为key判断redis的黑名单列表是否存在,存在则拦截访问 -// token = StrUtil.replaceIgnoreCase(token, SecurityConstants.JWT_PREFIX, Strings.EMPTY); -// String payload = StrUtil.toString(JWSObject.parse(token).getPayload()); -// request = exchange.getRequest().mutate() -// .header(SecurityConstants.JWT_PAYLOAD_KEY, URLEncoder.encode(payload, "UTF-8")) -// .build(); -// exchange = exchange.mutate().request(request).build(); -// return chain.filter(exchange); -// } -// -// @Override -// public int getOrder() { -// return 0; -// } -//} \ No newline at end of file +package com.recovery.gateway.security; + +import cn.dev33.satoken.same.SaSameUtil; +import cn.hutool.core.util.StrUtil; + +import com.nimbusds.jose.JWSObject; +import com.recovery.common.base.constant.SecurityConstants; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +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.data.redis.core.RedisTemplate; +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; + +import java.net.URLEncoder; +/** + * Created with IntelliJ IDEA. + * + * @author: + * @date: 2021/11/24 + * @description: + * @modifiedBy: + * @version: 1.0 + */ +@Component +@Slf4j +@RequiredArgsConstructor +public class SecurityGlobalFilter implements GlobalFilter, Ordered { + + @SneakyThrows + @Override + public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { + + ServerHttpRequest request = exchange.getRequest(); + log.info("接受到请求地址:"+request.getURI()); + ServerHttpResponse response = exchange.getResponse(); + // 为请求追加 Same-Token 参数 + request.mutate() + // 为请求追加 Same-Token 参数 + .header(SaSameUtil.SAME_TOKEN, SaSameUtil.getToken()) + .build(); + exchange = exchange.mutate().request(request).build(); + return chain.filter(exchange); + } + + @Override + public int getOrder() { + return 0; + } +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index 2c85a3f..35bee7c 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ 2021.1 2.5.4 1.0.0 - 5.5.8 + 5.7.14 8.0.22 1.2.4 3.4.3 @@ -52,6 +52,12 @@ sa-token-redis-jackson 1.37.0 + + + cn.dev33 + sa-token-jwt + 1.37.0 + org.apache.commons commons-pool2