From 4fadcb5b8eaacd6ba226f53e9ac274461c8cb3e4 Mon Sep 17 00:00:00 2001 From: Administrator Date: Tue, 31 Dec 2024 08:48:11 +0800 Subject: [PATCH] =?UTF-8?q?1.=20=E5=AE=8C=E5=96=84=E5=B0=8F=E7=A8=8B?= =?UTF-8?q?=E5=BA=8Ftoken=E9=AA=8C=E8=AF=81=202.=20gateway=E6=9C=8D?= =?UTF-8?q?=E5=8A=A1=E6=B7=BB=E5=8A=A0openfein=E5=AE=9E=E7=8E=B0=E6=9C=8D?= =?UTF-8?q?=E5=8A=A1=E8=B0=83=E7=94=A8=203.=20=E7=BB=9F=E4=B8=80=E5=B0=8F?= =?UTF-8?q?=E7=A8=8B=E5=BA=8Fuid=E5=92=8Ctoken=E7=94=9F=E6=88=90=E6=96=B9?= =?UTF-8?q?=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- base-commons/common-core/pom.xml | 6 ++ .../hd/common/core/constant/HDConstant.java | 3 + .../evotech/hd/common/core/enums/CodeMsg.java | 8 +- .../evotech/hd/common/core/utils/XCXUtil.java | 15 +++ gateway-server/pom.xml | 7 ++ .../hd/gateway/GatewayServerApplication.java | 3 +- .../hd/gateway/config/FeignConfig.java | 30 ++++++ .../gateway/oauth2/AuthorizationManager.java | 55 +++++++++-- .../gateway/oauth2/MyAccessDeniedHandler.java | 6 +- .../oauth2/MyAuthenticationEntryPoint.java | 4 +- .../gateway/oauth2/ResourceServerConfig.java | 5 +- .../oauth2/WechatAuthorizationManager.java | 95 +++++++++++++++++++ .../exception/GlobalExceptionAutoConfig.java | 1 + .../exception/GlobalExceptionHandler.java | 1 + .../gateway/oauth2/service/WechatService.java | 17 ++++ .../GatewayServerApplicationTests.java | 13 +++ .../controller/WechatAgreementController.java | 2 +- .../controller/WechatSwiperController.java | 2 +- wechat-server/pom.xml | 4 - .../hd/wechat/controller/LoginController.java | 11 ++- .../hd/wechat/service/LoginService.java | 8 +- .../wechat/service/impl/LoginServiceImpl.java | 57 ++++++++--- ...itional-spring-configuration-metadata.json | 5 + .../src/main/resources/application.yml | 5 +- 24 files changed, 325 insertions(+), 38 deletions(-) create mode 100644 base-commons/common-core/src/main/java/com/evotech/hd/common/core/utils/XCXUtil.java create mode 100644 gateway-server/src/main/java/com/evotech/hd/gateway/config/FeignConfig.java create mode 100644 gateway-server/src/main/java/com/evotech/hd/gateway/oauth2/WechatAuthorizationManager.java create mode 100644 gateway-server/src/main/java/com/evotech/hd/gateway/oauth2/service/WechatService.java diff --git a/base-commons/common-core/pom.xml b/base-commons/common-core/pom.xml index 69d967b..e5934e9 100644 --- a/base-commons/common-core/pom.xml +++ b/base-commons/common-core/pom.xml @@ -63,6 +63,12 @@ cn.hutool hutool-core + + + cn.hutool + hutool-crypto + + diff --git a/base-commons/common-core/src/main/java/com/evotech/hd/common/core/constant/HDConstant.java b/base-commons/common-core/src/main/java/com/evotech/hd/common/core/constant/HDConstant.java index 733a824..df9bc82 100644 --- a/base-commons/common-core/src/main/java/com/evotech/hd/common/core/constant/HDConstant.java +++ b/base-commons/common-core/src/main/java/com/evotech/hd/common/core/constant/HDConstant.java @@ -75,6 +75,7 @@ public interface HDConstant { * 微信服务请求头中权限验证字段 */ String WECHAT_SERVER_AUTHORIZATION_KEY = "WXUID"; + String WECHAT_SERVER_TOKEN_KEY = "WXTOKEN"; /** * 微信登录缓存数据前缀 @@ -82,6 +83,8 @@ public interface HDConstant { String openidPrefix = "hd:wechat:login:openid:"; String unionidPrefix = "hd:wechat:login:unionid:"; String sessionKeyPrefix = "hd:wechat:login:sessionKey:"; + String wxToken = "hd:wechat:login:token:"; + String tokenRandomStr = "hd:wechat:login:randomStr:"; } diff --git a/base-commons/common-core/src/main/java/com/evotech/hd/common/core/enums/CodeMsg.java b/base-commons/common-core/src/main/java/com/evotech/hd/common/core/enums/CodeMsg.java index 2025d83..c6d3485 100644 --- a/base-commons/common-core/src/main/java/com/evotech/hd/common/core/enums/CodeMsg.java +++ b/base-commons/common-core/src/main/java/com/evotech/hd/common/core/enums/CodeMsg.java @@ -76,9 +76,11 @@ public enum CodeMsg { REDIS_ERROR("D0410", "REDIS数据出错"), // 小程序 w - WECHAT_LOGIN_ERROR("W0400", "小程序登录出错"), - WECHAT_SERRION_ERROR("W0401", "小程序登录态错误"), - WECHAT_API_ERROR("W0402", "小程序接口调用异常"); + WECHAT_LOGIN_ERROR("W0400", "小程序登录异常"), + WECHAT_SERRION_ERROR("W0401", "小程序登录态异常"), + WECHAT_API_ERROR("W0402", "小程序接口调用异常"), + WECHAT_NOT_LOGIN("W0403", "用户未登陆!"), + WECHAT_TOKEN_INVALID("W0404", "无效token!"); String code; diff --git a/base-commons/common-core/src/main/java/com/evotech/hd/common/core/utils/XCXUtil.java b/base-commons/common-core/src/main/java/com/evotech/hd/common/core/utils/XCXUtil.java new file mode 100644 index 0000000..7cf3285 --- /dev/null +++ b/base-commons/common-core/src/main/java/com/evotech/hd/common/core/utils/XCXUtil.java @@ -0,0 +1,15 @@ +package com.evotech.hd.common.core.utils; + +import cn.hutool.crypto.digest.MD5; + +public class XCXUtil { + + public static String wechatUid(String appid, String openid) { + return MD5.create().digestHex(appid + openid + "HBYT"); + } + + public static String wechatToken(String wuid, String randomStr) { + return MD5.create().digestHex(wuid + randomStr + "HBYTXCX"); + } + +} diff --git a/gateway-server/pom.xml b/gateway-server/pom.xml index 2ddd9f0..ce71f3c 100644 --- a/gateway-server/pom.xml +++ b/gateway-server/pom.xml @@ -108,6 +108,13 @@ de.codecentric spring-boot-admin-starter-client + + + + org.springframework.cloud + spring-cloud-starter-openfeign + + diff --git a/gateway-server/src/main/java/com/evotech/hd/gateway/GatewayServerApplication.java b/gateway-server/src/main/java/com/evotech/hd/gateway/GatewayServerApplication.java index 34bd639..b3ccce2 100644 --- a/gateway-server/src/main/java/com/evotech/hd/gateway/GatewayServerApplication.java +++ b/gateway-server/src/main/java/com/evotech/hd/gateway/GatewayServerApplication.java @@ -3,12 +3,13 @@ package com.evotech.hd.gateway; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; +import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.context.annotation.ComponentScan; @SpringBootApplication @EnableDiscoveryClient @ComponentScan("com.evotech.hd.**") -//@EnableFeignClients +@EnableFeignClients public class GatewayServerApplication { public static void main(String[] args) { diff --git a/gateway-server/src/main/java/com/evotech/hd/gateway/config/FeignConfig.java b/gateway-server/src/main/java/com/evotech/hd/gateway/config/FeignConfig.java new file mode 100644 index 0000000..bd67916 --- /dev/null +++ b/gateway-server/src/main/java/com/evotech/hd/gateway/config/FeignConfig.java @@ -0,0 +1,30 @@ +package com.evotech.hd.gateway.config; + +import java.util.stream.Collectors; + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.http.HttpMessageConverters; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.converter.HttpMessageConverter; + + + +@Configuration +public class FeignConfig { + + + + /** + * Spring Cloud Gateway是基于WebFlux的,是ReactiveWeb,所以HttpMessageConverters不会自动注入。 + * @param converters + * @return + */ + @Bean + @ConditionalOnMissingBean + HttpMessageConverters messageConverters(ObjectProvider> converters) { + return new HttpMessageConverters(converters.orderedStream().collect(Collectors.toList())); + } + +} diff --git a/gateway-server/src/main/java/com/evotech/hd/gateway/oauth2/AuthorizationManager.java b/gateway-server/src/main/java/com/evotech/hd/gateway/oauth2/AuthorizationManager.java index fe8d347..723c047 100644 --- a/gateway-server/src/main/java/com/evotech/hd/gateway/oauth2/AuthorizationManager.java +++ b/gateway-server/src/main/java/com/evotech/hd/gateway/oauth2/AuthorizationManager.java @@ -2,6 +2,7 @@ package com.evotech.hd.gateway.oauth2; import java.util.Collection; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; import org.springframework.beans.factory.annotation.Value; @@ -15,13 +16,17 @@ import org.springframework.security.oauth2.core.OAuth2AuthorizationException; import org.springframework.security.oauth2.core.OAuth2Error; import org.springframework.security.web.server.authorization.AuthorizationContext; import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; import com.evotech.hd.common.core.constant.HDConstant; import com.evotech.hd.common.core.enums.CodeMsg; +import com.evotech.hd.common.core.utils.XCXUtil; import com.evotech.hd.common.redis.utils.RedisUtil; +import com.evotech.hd.gateway.oauth2.service.WechatService; import com.evotech.hd.gateway.utils.TokenUtil; import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONUtil; import jakarta.annotation.Resource; import reactor.core.publisher.Mono; @@ -36,6 +41,8 @@ public class AuthorizationManager implements ReactiveAuthorizationManager new AuthorizationDecision(checkAuthorities(token.substring(7), request, auth))) .defaultIfEmpty(new AuthorizationDecision(false)); } + + + + /** + * 微信token权限验证逻辑 + * @param request + * @param uri + * @return + */ + private Mono wechatTokenCheck(ServerHttpRequest request, String uri) { + String wuid = request.getHeaders().getFirst(HDConstant.WECHAT_SERVER_AUTHORIZATION_KEY); + String wxToken = request.getHeaders().getFirst(HDConstant.WECHAT_SERVER_TOKEN_KEY); + if (!StringUtils.hasText(wxToken) || !StringUtils.hasText(wuid)) { + throw new OAuth2AuthorizationException(new OAuth2Error(CodeMsg.WECHAT_NOT_LOGIN.getCode(), CodeMsg.WECHAT_NOT_LOGIN.getMsg(), "")); + } + // 验证wuid + if (!redisUtil.hasKey(HDConstant.openidPrefix + wuid)) { + throw new OAuth2AuthorizationException(new OAuth2Error(CodeMsg.WECHAT_LOGIN_ERROR.getCode(), CodeMsg.WECHAT_LOGIN_ERROR.getMsg(), uri)); + } + // 验证token正确 + if (!redisUtil.hasKey(HDConstant.wxToken + wuid)) { + // 缓存没了,过期了 + if (!wxToken.equals(XCXUtil.wechatToken(wuid, redisUtil.get(HDConstant.tokenRandomStr + wuid).toString()))) { + throw new OAuth2AuthorizationException(new OAuth2Error(CodeMsg.WECHAT_LOGIN_ERROR.getCode(), CodeMsg.WECHAT_LOGIN_ERROR.getMsg(), "token异常")); + } + Map m = wechatService.tokenBuilder(wuid); + throw new OAuth2AuthorizationException(new OAuth2Error(CodeMsg.WECHAT_TOKEN_INVALID.getCode(), CodeMsg.WECHAT_TOKEN_INVALID.getMsg(), JSONUtil.toJsonStr(m))); + } else { + // 缓存还有 + if (wxToken.equals(redisUtil.get(HDConstant.wxToken + wuid).toString())) { + return Mono.just(new AuthorizationDecision(true)); + } else { + // token对不上 + throw new OAuth2AuthorizationException(new OAuth2Error(CodeMsg.WECHAT_LOGIN_ERROR.getCode(), CodeMsg.WECHAT_LOGIN_ERROR.getMsg(), "token异常")); + } + } + } + + + /** * 校验权限和client状态 diff --git a/gateway-server/src/main/java/com/evotech/hd/gateway/oauth2/MyAccessDeniedHandler.java b/gateway-server/src/main/java/com/evotech/hd/gateway/oauth2/MyAccessDeniedHandler.java index 8276adb..6f8c05a 100644 --- a/gateway-server/src/main/java/com/evotech/hd/gateway/oauth2/MyAccessDeniedHandler.java +++ b/gateway-server/src/main/java/com/evotech/hd/gateway/oauth2/MyAccessDeniedHandler.java @@ -20,7 +20,9 @@ import reactor.core.publisher.Mono; /** - * 自定义没有权限访问时的异常,没用到,因为自定义的权限校验 + * 自定义 oauth2默认权限校验 异常时的处理, + * 没用到,因为没用默认权限校验,我们使用的自定义的权限校验 + * 自定义的权限校验 中的 异常用exception包中的全局异常处理的 * 参考{@link org.springframework.security.oauth2.server.resource.web.access.server.BearerTokenServerAccessDeniedHandler} * @author zrb * @date 2024年9月4日10:17:27 @@ -36,7 +38,7 @@ public class MyAccessDeniedHandler implements ServerAccessDeniedHandler { ServerHttpResponse response = exchange.getResponse(); response.setStatusCode(HttpStatus.OK); response.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); - // 为了解决token过期时,前端不出现跨域错误,添加了一些header,注意Access-Control-Allow-Origin的值 + // 为了解决token过期时,前端不出现跨域错误,添加了一些header,注意Access-Control-Allow-Origin的值 response.getHeaders().add("Access-Control-Allow-Origin", "*"); response.getHeaders().add("Access-Control-Allow-Credentials", "true"); response.getHeaders().add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, HEAD"); diff --git a/gateway-server/src/main/java/com/evotech/hd/gateway/oauth2/MyAuthenticationEntryPoint.java b/gateway-server/src/main/java/com/evotech/hd/gateway/oauth2/MyAuthenticationEntryPoint.java index ddb1e4b..749815e 100644 --- a/gateway-server/src/main/java/com/evotech/hd/gateway/oauth2/MyAuthenticationEntryPoint.java +++ b/gateway-server/src/main/java/com/evotech/hd/gateway/oauth2/MyAuthenticationEntryPoint.java @@ -25,8 +25,9 @@ import lombok.extern.slf4j.Slf4j; import reactor.core.publisher.Mono; /** - * 这个主要处理身份认证(authentication)的异常,权限验证时的异常这里是不处理的 * 自定义Token异常,主要处理token验证过程中的异常,因token验证是oauth2的,所以要改它的异常处理响应 + * 这个主要处理身份认证(authentication)的异常 + * 权限验证时的异常不经过这个类处理 * 参考{@link org.springframework.security.oauth2.server.resource.web.server.BearerTokenServerAuthenticationEntryPoint} * 重写了返回结果中的response内容: * 1. response的HttpStatus改成ok @@ -55,7 +56,6 @@ public class MyAuthenticationEntryPoint implements ServerAuthenticationEntryPoin } else if (cause instanceof BadJwtException || cause instanceof JwtEncodingException) { res = new Result().error(CodeMsg.TOKEN_INVALID.getCode(), CodeMsg.TOKEN_INVALID.getMsg(), cause.getMessage()); } else if (cause instanceof InvalidBearerTokenException) { - System.out.println("8888888"); String token = exchange.getRequest().getHeaders().getFirst("Authorization").substring(7); String dateTime = DateUtil.formatDateTime(TokenUtil.getExp(token)); res = new Result().bussinessException(CodeMsg.TOKEN_EXPIRED.getCode(), CodeMsg.TOKEN_EXPIRED.getMsg(), "Jwt expired at " + dateTime); diff --git a/gateway-server/src/main/java/com/evotech/hd/gateway/oauth2/ResourceServerConfig.java b/gateway-server/src/main/java/com/evotech/hd/gateway/oauth2/ResourceServerConfig.java index 70cba7f..e90b4d5 100644 --- a/gateway-server/src/main/java/com/evotech/hd/gateway/oauth2/ResourceServerConfig.java +++ b/gateway-server/src/main/java/com/evotech/hd/gateway/oauth2/ResourceServerConfig.java @@ -44,9 +44,9 @@ public class ResourceServerConfig { http.httpBasic(httpBasicSpec -> httpBasicSpec.disable()); // 资源服务器配置 http.oauth2ResourceServer(server -> server - // 权限不通过时,自定义返回 + // 默认的权限校验方法 不通过时,自定义返回,这个用不到,因为没有走默认权限校验 .accessDeniedHandler(new MyAccessDeniedHandler()) - // 未登录或者登陆验证失败时(token有问题),自定义返回 + // 凭据验证失败时(token有问题),自定义返回 .authenticationEntryPoint(new MyAuthenticationEntryPoint()) // 使用jwt默认配置 // .jwt(Customizer.withDefaults()) @@ -66,6 +66,7 @@ public class ResourceServerConfig { exchange .pathMatchers(Convert.toStrArray(ignoreUri.getIgnoreUris())).permitAll() .pathMatchers(ignoreFixedUris()).permitAll() + .pathMatchers("/gateway/wechat/*").access(authorizationManager) // .anyExchange().authenticated() // 其他走自定义逻辑 .anyExchange().access(authorizationManager) diff --git a/gateway-server/src/main/java/com/evotech/hd/gateway/oauth2/WechatAuthorizationManager.java b/gateway-server/src/main/java/com/evotech/hd/gateway/oauth2/WechatAuthorizationManager.java new file mode 100644 index 0000000..a1c22ef --- /dev/null +++ b/gateway-server/src/main/java/com/evotech/hd/gateway/oauth2/WechatAuthorizationManager.java @@ -0,0 +1,95 @@ +package com.evotech.hd.gateway.oauth2; + +import java.util.Map; +import org.springframework.beans.factory.annotation.Value; +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.oauth2.core.OAuth2AuthorizationException; +import org.springframework.security.oauth2.core.OAuth2Error; +import org.springframework.security.web.server.authorization.AuthorizationContext; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +import com.evotech.hd.common.core.constant.HDConstant; +import com.evotech.hd.common.core.enums.CodeMsg; +import com.evotech.hd.common.core.utils.XCXUtil; +import com.evotech.hd.common.redis.utils.RedisUtil; +import com.evotech.hd.gateway.oauth2.service.WechatService; +import cn.hutool.json.JSONUtil; +import jakarta.annotation.Resource; +import reactor.core.publisher.Mono; + +/** + * 微信服务token校验 + * 鉴权时统一抛出OAuth2AuthorizationException + */ +@Component +public class WechatAuthorizationManager implements ReactiveAuthorizationManager { + + @Resource + private RedisUtil redisUtil; + @Resource + private WechatService wechatService; + + @Value("${yt.gateway.is_pass:false}") + private boolean isPass; + + + @Override + public Mono check(Mono mono, AuthorizationContext authorizationContext) { + if (isPass) { + return Mono.just(new AuthorizationDecision(true)); + } + ServerHttpRequest request = authorizationContext.getExchange().getRequest(); + String uri = request.getURI().toString(); + + // 1. 对应跨域的预检请求直接放行 + if (request.getMethod() == HttpMethod.OPTIONS) { + return Mono.just(new AuthorizationDecision(true)); + } + + return wechatTokenCheck(request, uri); + } + + + + /** + * 微信token权限验证逻辑 + * @param request + * @param uri + * @return + */ + private Mono wechatTokenCheck(ServerHttpRequest request, String uri) { + String wuid = request.getHeaders().getFirst(HDConstant.WECHAT_SERVER_AUTHORIZATION_KEY); + String wxToken = request.getHeaders().getFirst(HDConstant.WECHAT_SERVER_TOKEN_KEY); + if (!StringUtils.hasText(wxToken) || !StringUtils.hasText(wuid)) { + throw new OAuth2AuthorizationException(new OAuth2Error(CodeMsg.WECHAT_NOT_LOGIN.getCode(), CodeMsg.WECHAT_NOT_LOGIN.getMsg(), "")); + } + // 验证wuid + if (!redisUtil.hasKey(HDConstant.openidPrefix + wuid)) { + throw new OAuth2AuthorizationException(new OAuth2Error(CodeMsg.WECHAT_LOGIN_ERROR.getCode(), CodeMsg.WECHAT_LOGIN_ERROR.getMsg(), uri)); + } + // 验证token正确 + if (!redisUtil.hasKey(HDConstant.wxToken + wuid)) { + // 缓存没了,过期了 + if (!wxToken.equals(XCXUtil.wechatToken(wuid, redisUtil.get(HDConstant.tokenRandomStr + wuid).toString()))) { + throw new OAuth2AuthorizationException(new OAuth2Error(CodeMsg.WECHAT_LOGIN_ERROR.getCode(), CodeMsg.WECHAT_LOGIN_ERROR.getMsg(), "token异常")); + } + Map m = wechatService.tokenBuilder(wuid); + throw new OAuth2AuthorizationException(new OAuth2Error(CodeMsg.WECHAT_TOKEN_INVALID.getCode(), CodeMsg.WECHAT_TOKEN_INVALID.getMsg(), JSONUtil.toJsonStr(m))); + } else { + // 缓存还有 + if (wxToken.equals(redisUtil.get(HDConstant.wxToken + wuid).toString())) { + return Mono.just(new AuthorizationDecision(true)); + } else { + // token对不上 + throw new OAuth2AuthorizationException(new OAuth2Error(CodeMsg.WECHAT_LOGIN_ERROR.getCode(), CodeMsg.WECHAT_LOGIN_ERROR.getMsg(), "token异常")); + } + } + } + + +} diff --git a/gateway-server/src/main/java/com/evotech/hd/gateway/oauth2/exception/GlobalExceptionAutoConfig.java b/gateway-server/src/main/java/com/evotech/hd/gateway/oauth2/exception/GlobalExceptionAutoConfig.java index 9799ba2..d02ca84 100644 --- a/gateway-server/src/main/java/com/evotech/hd/gateway/oauth2/exception/GlobalExceptionAutoConfig.java +++ b/gateway-server/src/main/java/com/evotech/hd/gateway/oauth2/exception/GlobalExceptionAutoConfig.java @@ -24,6 +24,7 @@ import org.springframework.web.reactive.result.view.ViewResolver; * 根据{@link}ErrorWebFluxAutoConfiguration的配置 重写 * 主要是重写errorWebExceptionHandler()的逻辑 * 里面不要DefaultErrorWebExceptionHandler了,用自己写的异常处理类替换 + * 这个全局异常 主要用来捕获 自定义权限验证{@link AuthorizationManager}类中抛出的异常 */ @Configuration(proxyBeanMethods = false) //@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE) diff --git a/gateway-server/src/main/java/com/evotech/hd/gateway/oauth2/exception/GlobalExceptionHandler.java b/gateway-server/src/main/java/com/evotech/hd/gateway/oauth2/exception/GlobalExceptionHandler.java index c8ec955..f4bc286 100644 --- a/gateway-server/src/main/java/com/evotech/hd/gateway/oauth2/exception/GlobalExceptionHandler.java +++ b/gateway-server/src/main/java/com/evotech/hd/gateway/oauth2/exception/GlobalExceptionHandler.java @@ -22,6 +22,7 @@ import reactor.core.publisher.Mono; /** * 异常处理操作,自定义异常中的内容 * 重写了{@link}DefaultErrorWebExceptionHandler部分内容 + * 这个全局异常 主要用来捕获 自定义权限验证{@link AuthorizationManager}类中抛出的异常 */ public class GlobalExceptionHandler extends DefaultErrorWebExceptionHandler { diff --git a/gateway-server/src/main/java/com/evotech/hd/gateway/oauth2/service/WechatService.java b/gateway-server/src/main/java/com/evotech/hd/gateway/oauth2/service/WechatService.java new file mode 100644 index 0000000..be59ef6 --- /dev/null +++ b/gateway-server/src/main/java/com/evotech/hd/gateway/oauth2/service/WechatService.java @@ -0,0 +1,17 @@ +package com.evotech.hd.gateway.oauth2.service; + +import java.util.Map; + +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; + +@FeignClient(value = "wechat-server") +public interface WechatService { + + @PostMapping(value = "/wechat/login/tokenbuild", + consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE}) + public Map tokenBuilder(@RequestParam String wuid); + +} diff --git a/gateway-server/src/test/java/com/evotech/hd/gateway/GatewayServerApplicationTests.java b/gateway-server/src/test/java/com/evotech/hd/gateway/GatewayServerApplicationTests.java index 26a1f76..288f5d1 100644 --- a/gateway-server/src/test/java/com/evotech/hd/gateway/GatewayServerApplicationTests.java +++ b/gateway-server/src/test/java/com/evotech/hd/gateway/GatewayServerApplicationTests.java @@ -1,13 +1,26 @@ package com.evotech.hd.gateway; +import java.util.Map; + import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; +import com.evotech.hd.gateway.oauth2.service.WechatService; + +import jakarta.annotation.Resource; + @SpringBootTest class GatewayServerApplicationTests { + + @Resource + private WechatService wechatService; @Test void contextLoads() { + Map m = wechatService.tokenBuilder("123"); + System.out.println("0000000000"); + System.out.println(m); + } } diff --git a/resource-server/src/main/java/com/evotech/hd/resource/controller/WechatAgreementController.java b/resource-server/src/main/java/com/evotech/hd/resource/controller/WechatAgreementController.java index f929bd2..296d249 100644 --- a/resource-server/src/main/java/com/evotech/hd/resource/controller/WechatAgreementController.java +++ b/resource-server/src/main/java/com/evotech/hd/resource/controller/WechatAgreementController.java @@ -56,7 +56,7 @@ public class WechatAgreementController { @Operation(summary = "查询") @GetMapping("/list") @ApiOperationSupport(order = 4) - public Result> list(@RequestParam String appid, @RequestParam Integer type, @RequestParam Integer status) { + public Result> list(@RequestParam(required = false) String appid, @RequestParam(required = false) Integer type, @RequestParam(required = false) Integer status) { return wechatAgreementService.list(appid, type, status); } diff --git a/resource-server/src/main/java/com/evotech/hd/resource/controller/WechatSwiperController.java b/resource-server/src/main/java/com/evotech/hd/resource/controller/WechatSwiperController.java index b948943..266b73d 100644 --- a/resource-server/src/main/java/com/evotech/hd/resource/controller/WechatSwiperController.java +++ b/resource-server/src/main/java/com/evotech/hd/resource/controller/WechatSwiperController.java @@ -55,7 +55,7 @@ public class WechatSwiperController { @Operation(summary = "查询") @GetMapping("/list") @ApiOperationSupport(order = 4) - public Result> list(@RequestParam String appid, @RequestParam Integer type, @RequestParam Integer status) { + public Result> list(@RequestParam(required = false) String appid, @RequestParam(required = false) Integer type, @RequestParam(required = false) Integer status) { return wechatSwiperService.list(appid, type, status); } diff --git a/wechat-server/pom.xml b/wechat-server/pom.xml index f5d86a5..8495ecc 100644 --- a/wechat-server/pom.xml +++ b/wechat-server/pom.xml @@ -71,10 +71,6 @@ cn.hutool hutool-json - - - cn.hutool - hutool-crypto diff --git a/wechat-server/src/main/java/com/evotech/hd/wechat/controller/LoginController.java b/wechat-server/src/main/java/com/evotech/hd/wechat/controller/LoginController.java index 8770b6e..113a6b4 100644 --- a/wechat-server/src/main/java/com/evotech/hd/wechat/controller/LoginController.java +++ b/wechat-server/src/main/java/com/evotech/hd/wechat/controller/LoginController.java @@ -1,5 +1,7 @@ package com.evotech.hd.wechat.controller; +import java.util.Map; + import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -26,14 +28,14 @@ public class LoginController { @Operation(summary = "登陆") @PostMapping("/xcxlogin") @Parameter(name="js_code", description = "登录时获取的 code,可通过wx.login获取", example = "") - public Result code2Session(String js_code) { + public Result> code2Session(String js_code) { return loginService.code2Session(js_code); } @Operation(summary = "检验小程序登录态") @PostMapping("/checksessionkey") - public Result checkSessionKey(String wuid) { + public Result> checkSessionKey(String wuid) { return loginService.checkSessionKey(wuid); } @@ -44,5 +46,10 @@ public class LoginController { return loginService.phoneNumber(wuid, code); } + @PostMapping("/tokenbuild") + public Map tokenBuilder(String wuid) { + return loginService.tokenBuilder(wuid); + } + } diff --git a/wechat-server/src/main/java/com/evotech/hd/wechat/service/LoginService.java b/wechat-server/src/main/java/com/evotech/hd/wechat/service/LoginService.java index f369f24..51e464b 100644 --- a/wechat-server/src/main/java/com/evotech/hd/wechat/service/LoginService.java +++ b/wechat-server/src/main/java/com/evotech/hd/wechat/service/LoginService.java @@ -1,13 +1,17 @@ package com.evotech.hd.wechat.service; +import java.util.Map; + import com.evotech.hd.common.core.entity.Result; public interface LoginService { - public Result code2Session(String js_code); + public Result> code2Session(String js_code); - public Result checkSessionKey(String wuid); + public Result> checkSessionKey(String wuid); public Result phoneNumber(String wuid, String code); + public Map tokenBuilder(String wuid); + } diff --git a/wechat-server/src/main/java/com/evotech/hd/wechat/service/impl/LoginServiceImpl.java b/wechat-server/src/main/java/com/evotech/hd/wechat/service/impl/LoginServiceImpl.java index 6029073..b0efe35 100644 --- a/wechat-server/src/main/java/com/evotech/hd/wechat/service/impl/LoginServiceImpl.java +++ b/wechat-server/src/main/java/com/evotech/hd/wechat/service/impl/LoginServiceImpl.java @@ -1,7 +1,10 @@ package com.evotech.hd.wechat.service.impl; import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; @@ -11,6 +14,7 @@ import com.evotech.hd.common.core.dao.wechat.WechatUserDao; import com.evotech.hd.common.core.entity.Result; import com.evotech.hd.common.core.entity.wechat.WechatUser; import com.evotech.hd.common.core.enums.CodeMsg; +import com.evotech.hd.common.core.utils.XCXUtil; import com.evotech.hd.common.redis.utils.RedisUtil; import com.evotech.hd.wechat.config.XcxProperties; import com.evotech.hd.wechat.service.AccessTokenService; @@ -18,10 +22,11 @@ import com.evotech.hd.wechat.service.LoginService; import com.evotech.hd.wechat.utils.xcx.LoginUtil; import com.evotech.hd.wechat.utils.xcx.PhoneNumberUtil; +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateUtil; import cn.hutool.crypto.digest.DigestUtil; import cn.hutool.crypto.digest.HMac; import cn.hutool.crypto.digest.HmacAlgorithm; -import cn.hutool.crypto.digest.MD5; import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; import jakarta.annotation.Resource; @@ -37,13 +42,21 @@ public class LoginServiceImpl implements LoginService { private WechatUserDao wechatUserDao; @Resource private AccessTokenService accessTokenService; + + @Value("${hbyt.xcx.token_exp_hour:2}") + private Integer tokenExpHour; + /** + * 登陆成功后会生成本地token,返回前端,前端请求携带 + * 在gateway中对微信服务的token重加一套验证逻辑, + * 在gateway验证时,如果是过期,会在那重新生成token返回 + */ @Override - public Result code2Session(String js_code) { + public Result> code2Session(String js_code) { String res = LoginUtil.code2Session(xcxProperties.getAppid(), xcxProperties.getAppSecret(), js_code); JSONObject jo = JSONUtil.parseObj(res); if (StringUtils.hasText(jo.getStr("errcode"))) { - return new Result().error(CodeMsg.WECHAT_LOGIN_ERROR, jo); + return new Result>().error(CodeMsg.WECHAT_LOGIN_ERROR, jo); } String openid = jo.getStr("openid"); String unionid = jo.getStr("unionid"); @@ -51,7 +64,7 @@ public class LoginServiceImpl implements LoginService { // 是否登陆过 WechatUser wuser = wechatUserDao.selectOne(new QueryWrapper().eq("openid", openid)); if (wuser == null) { - String wuid = MD5.create().digestHex(xcxProperties.getAppid() + openid + "HBYT"); + String wuid = XCXUtil.wechatUid(xcxProperties.getAppid(), openid); wuser = new WechatUser(); wuser.setAppid(xcxProperties.getAppid()); wuser.setCtime(new Date()); @@ -64,25 +77,34 @@ public class LoginServiceImpl implements LoginService { redisUtil.set(HDConstant.openidPrefix + wuser.getWuid(), openid); redisUtil.set(HDConstant.unionidPrefix + wuser.getWuid(), unionid); redisUtil.set(HDConstant.sessionKeyPrefix + wuser.getWuid(), sessionKey); - - return new Result().success(wuser.getWuid()); + // token + Map m = tokenBuilder(wuser.getWuid()); + return new Result>().success(m); } + + + /** + * 检查登录态,检查成功后刷新本地token + */ @Override - public Result checkSessionKey(String wuid) { + public Result> checkSessionKey(String wuid) { if (!redisUtil.hasKey(HDConstant.openidPrefix + wuid) || !redisUtil.hasKey(HDConstant.sessionKeyPrefix + wuid)) { - return new Result().error(CodeMsg.WECHAT_SERRION_ERROR); + return new Result>().error(CodeMsg.WECHAT_SERRION_ERROR); } String openid = redisUtil.get(HDConstant.openidPrefix + wuid).toString(); String sessionKey = redisUtil.get(HDConstant.sessionKeyPrefix + wuid).toString(); HMac hmac = DigestUtil.hmac(HmacAlgorithm.HmacSHA256, sessionKey.getBytes()); - String res = LoginUtil.code2Session(xcxProperties.getAppid(), openid, hmac.digestHex("")); + String res = LoginUtil.checkSessionKey(accessTokenService.getAccessToken(), openid, hmac.digestHex("")); JSONObject jo = JSONUtil.parseObj(res); if (jo.getInt("errcode") == 0) { - return new Result().success("OK"); + Map m = tokenBuilder(wuid); + return new Result>().success(m); } - return new Result().error(CodeMsg.WECHAT_SERRION_ERROR, jo); + return new Result>().error(CodeMsg.WECHAT_SERRION_ERROR, jo); } + + @Override public Result phoneNumber(String wuid, String code) { @@ -106,6 +128,19 @@ public class LoginServiceImpl implements LoginService { return new Result().error(CodeMsg.WECHAT_API_ERROR, jo); } + + @Override + public Map tokenBuilder(String wuid) { + String randomStr = DateUtil.format(new Date(), DatePattern.PURE_DATETIME_FORMATTER); + String wxToken = XCXUtil.wechatToken(wuid, randomStr); + redisUtil.set(HDConstant.wxToken + wuid, wxToken, tokenExpHour * 3600L); + redisUtil.set(HDConstant.tokenRandomStr + wuid, randomStr); + Map m = new HashMap(); + m.put("wuid", wuid); + m.put("wxToken", wxToken); + return m; + } + diff --git a/wechat-server/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/wechat-server/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 188b3d0..fad132b 100644 --- a/wechat-server/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/wechat-server/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -38,5 +38,10 @@ "name": "hbyt.xcx.refund_notify_url", "type": "java.lang.String", "description": "A description for 'hbyt.xcx.refund_notify_url'" + }, + { + "name": "hbyt.xcx.token_exp_hour", + "type": "java.lang.String", + "description": "A description for 'hbyt.xcx.token_exp_hour'" } ]} \ No newline at end of file diff --git a/wechat-server/src/main/resources/application.yml b/wechat-server/src/main/resources/application.yml index 3dbf965..d52859c 100644 --- a/wechat-server/src/main/resources/application.yml +++ b/wechat-server/src/main/resources/application.yml @@ -55,4 +55,7 @@ hbyt: # 支付回调地址 notify_url: https://api.evo-techina.com/wechat/wechatpay/prepayback/msg # 退款回调地址 - refund_notify_url: https://api.evo-techina.com/wechat/wechatpay/refundsback/msg \ No newline at end of file + refund_notify_url: https://api.evo-techina.com/wechat/wechatpay/refundsback/msg + # token缓存时效:小时 + token_exp_hour: 2 + \ No newline at end of file