1. 完善小程序token验证
2. gateway服务添加openfein实现服务调用 3. 统一小程序uid和token生成方法
This commit is contained in:
parent
9354169741
commit
4fadcb5b8e
@ -63,6 +63,12 @@
|
|||||||
<groupId>cn.hutool</groupId>
|
<groupId>cn.hutool</groupId>
|
||||||
<artifactId>hutool-core</artifactId>
|
<artifactId>hutool-core</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.hutool</groupId>
|
||||||
|
<artifactId>hutool-crypto</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|||||||
@ -75,6 +75,7 @@ public interface HDConstant {
|
|||||||
* 微信服务请求头中权限验证字段
|
* 微信服务请求头中权限验证字段
|
||||||
*/
|
*/
|
||||||
String WECHAT_SERVER_AUTHORIZATION_KEY = "WXUID";
|
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 openidPrefix = "hd:wechat:login:openid:";
|
||||||
String unionidPrefix = "hd:wechat:login:unionid:";
|
String unionidPrefix = "hd:wechat:login:unionid:";
|
||||||
String sessionKeyPrefix = "hd:wechat:login:sessionKey:";
|
String sessionKeyPrefix = "hd:wechat:login:sessionKey:";
|
||||||
|
String wxToken = "hd:wechat:login:token:";
|
||||||
|
String tokenRandomStr = "hd:wechat:login:randomStr:";
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -76,9 +76,11 @@ public enum CodeMsg {
|
|||||||
REDIS_ERROR("D0410", "REDIS数据出错"),
|
REDIS_ERROR("D0410", "REDIS数据出错"),
|
||||||
|
|
||||||
// 小程序 w
|
// 小程序 w
|
||||||
WECHAT_LOGIN_ERROR("W0400", "小程序登录出错"),
|
WECHAT_LOGIN_ERROR("W0400", "小程序登录异常"),
|
||||||
WECHAT_SERRION_ERROR("W0401", "小程序登录态错误"),
|
WECHAT_SERRION_ERROR("W0401", "小程序登录态异常"),
|
||||||
WECHAT_API_ERROR("W0402", "小程序接口调用异常");
|
WECHAT_API_ERROR("W0402", "小程序接口调用异常"),
|
||||||
|
WECHAT_NOT_LOGIN("W0403", "用户未登陆!"),
|
||||||
|
WECHAT_TOKEN_INVALID("W0404", "无效token!");
|
||||||
|
|
||||||
String code;
|
String code;
|
||||||
|
|
||||||
|
|||||||
@ -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");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -108,6 +108,13 @@
|
|||||||
<groupId>de.codecentric</groupId>
|
<groupId>de.codecentric</groupId>
|
||||||
<artifactId>spring-boot-admin-starter-client</artifactId>
|
<artifactId>spring-boot-admin-starter-client</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- openfein -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-starter-openfeign</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|||||||
@ -3,12 +3,13 @@ package com.evotech.hd.gateway;
|
|||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
|
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
|
||||||
|
import org.springframework.cloud.openfeign.EnableFeignClients;
|
||||||
import org.springframework.context.annotation.ComponentScan;
|
import org.springframework.context.annotation.ComponentScan;
|
||||||
|
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
@EnableDiscoveryClient
|
@EnableDiscoveryClient
|
||||||
@ComponentScan("com.evotech.hd.**")
|
@ComponentScan("com.evotech.hd.**")
|
||||||
//@EnableFeignClients
|
@EnableFeignClients
|
||||||
public class GatewayServerApplication {
|
public class GatewayServerApplication {
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
|
|||||||
@ -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<HttpMessageConverter<?>> converters) {
|
||||||
|
return new HttpMessageConverters(converters.orderedStream().collect(Collectors.toList()));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -2,6 +2,7 @@ package com.evotech.hd.gateway.oauth2;
|
|||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
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.oauth2.core.OAuth2Error;
|
||||||
import org.springframework.security.web.server.authorization.AuthorizationContext;
|
import org.springframework.security.web.server.authorization.AuthorizationContext;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
import com.evotech.hd.common.core.constant.HDConstant;
|
import com.evotech.hd.common.core.constant.HDConstant;
|
||||||
import com.evotech.hd.common.core.enums.CodeMsg;
|
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.common.redis.utils.RedisUtil;
|
||||||
|
import com.evotech.hd.gateway.oauth2.service.WechatService;
|
||||||
import com.evotech.hd.gateway.utils.TokenUtil;
|
import com.evotech.hd.gateway.utils.TokenUtil;
|
||||||
|
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import cn.hutool.json.JSONUtil;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
@ -36,6 +41,8 @@ public class AuthorizationManager implements ReactiveAuthorizationManager<Author
|
|||||||
private RedisUtil redisUtil;
|
private RedisUtil redisUtil;
|
||||||
@Resource
|
@Resource
|
||||||
private RSAKeyPair rsaKeyPair;
|
private RSAKeyPair rsaKeyPair;
|
||||||
|
@Resource
|
||||||
|
private WechatService wechatService;
|
||||||
|
|
||||||
@Value("${yt.gateway.is_pass:false}")
|
@Value("${yt.gateway.is_pass:false}")
|
||||||
private boolean isPass;
|
private boolean isPass;
|
||||||
@ -55,12 +62,8 @@ public class AuthorizationManager implements ReactiveAuthorizationManager<Author
|
|||||||
}
|
}
|
||||||
// 微信服务将UID放入请求头,验证一下
|
// 微信服务将UID放入请求头,验证一下
|
||||||
if (uri.contains("/gateway/wechat/")) {
|
if (uri.contains("/gateway/wechat/")) {
|
||||||
String wuid = request.getHeaders().getFirst(HDConstant.WECHAT_SERVER_AUTHORIZATION_KEY);
|
return wechatTokenCheck(request, uri);
|
||||||
if (redisUtil.hasKey(HDConstant.sessionKeyPrefix + wuid) ) {
|
|
||||||
return Mono.just(new AuthorizationDecision(true));
|
|
||||||
} else {
|
|
||||||
throw new OAuth2AuthorizationException(new OAuth2Error(CodeMsg.TOKEN_INVALID.getCode(), CodeMsg.TOKEN_INVALID.getMsg(), uri));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// 2. token验证
|
// 2. token验证
|
||||||
/**
|
/**
|
||||||
@ -82,6 +85,46 @@ public class AuthorizationManager implements ReactiveAuthorizationManager<Author
|
|||||||
.defaultIfEmpty(new AuthorizationDecision(false));
|
.defaultIfEmpty(new AuthorizationDecision(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信token权限验证逻辑
|
||||||
|
* @param request
|
||||||
|
* @param uri
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private Mono<AuthorizationDecision> 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<String, String> 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状态
|
* 校验权限和client状态
|
||||||
* @param token
|
* @param token
|
||||||
|
|||||||
@ -20,7 +20,9 @@ import reactor.core.publisher.Mono;
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 自定义没有权限访问时的异常,没用到,因为自定义的权限校验
|
* 自定义 oauth2默认权限校验 异常时的处理,
|
||||||
|
* 没用到,因为没用默认权限校验,我们使用的自定义的权限校验
|
||||||
|
* 自定义的权限校验 中的 异常用exception包中的全局异常处理的
|
||||||
* 参考{@link org.springframework.security.oauth2.server.resource.web.access.server.BearerTokenServerAccessDeniedHandler}
|
* 参考{@link org.springframework.security.oauth2.server.resource.web.access.server.BearerTokenServerAccessDeniedHandler}
|
||||||
* @author zrb
|
* @author zrb
|
||||||
* @date 2024年9月4日10:17:27
|
* @date 2024年9月4日10:17:27
|
||||||
|
|||||||
@ -25,8 +25,9 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 这个主要处理身份认证(authentication)的异常,权限验证时的异常这里是不处理的
|
|
||||||
* 自定义Token异常,主要处理token验证过程中的异常,因token验证是oauth2的,所以要改它的异常处理响应
|
* 自定义Token异常,主要处理token验证过程中的异常,因token验证是oauth2的,所以要改它的异常处理响应
|
||||||
|
* 这个主要处理身份认证(authentication)的异常
|
||||||
|
* 权限验证时的异常不经过这个类处理
|
||||||
* 参考{@link org.springframework.security.oauth2.server.resource.web.server.BearerTokenServerAuthenticationEntryPoint}
|
* 参考{@link org.springframework.security.oauth2.server.resource.web.server.BearerTokenServerAuthenticationEntryPoint}
|
||||||
* 重写了返回结果中的response内容:
|
* 重写了返回结果中的response内容:
|
||||||
* 1. response的HttpStatus改成ok
|
* 1. response的HttpStatus改成ok
|
||||||
@ -55,7 +56,6 @@ public class MyAuthenticationEntryPoint implements ServerAuthenticationEntryPoin
|
|||||||
} else if (cause instanceof BadJwtException || cause instanceof JwtEncodingException) {
|
} else if (cause instanceof BadJwtException || cause instanceof JwtEncodingException) {
|
||||||
res = new Result<String>().error(CodeMsg.TOKEN_INVALID.getCode(), CodeMsg.TOKEN_INVALID.getMsg(), cause.getMessage());
|
res = new Result<String>().error(CodeMsg.TOKEN_INVALID.getCode(), CodeMsg.TOKEN_INVALID.getMsg(), cause.getMessage());
|
||||||
} else if (cause instanceof InvalidBearerTokenException) {
|
} else if (cause instanceof InvalidBearerTokenException) {
|
||||||
System.out.println("8888888");
|
|
||||||
String token = exchange.getRequest().getHeaders().getFirst("Authorization").substring(7);
|
String token = exchange.getRequest().getHeaders().getFirst("Authorization").substring(7);
|
||||||
String dateTime = DateUtil.formatDateTime(TokenUtil.getExp(token));
|
String dateTime = DateUtil.formatDateTime(TokenUtil.getExp(token));
|
||||||
res = new Result<String>().bussinessException(CodeMsg.TOKEN_EXPIRED.getCode(), CodeMsg.TOKEN_EXPIRED.getMsg(), "Jwt expired at " + dateTime);
|
res = new Result<String>().bussinessException(CodeMsg.TOKEN_EXPIRED.getCode(), CodeMsg.TOKEN_EXPIRED.getMsg(), "Jwt expired at " + dateTime);
|
||||||
|
|||||||
@ -44,9 +44,9 @@ public class ResourceServerConfig {
|
|||||||
http.httpBasic(httpBasicSpec -> httpBasicSpec.disable());
|
http.httpBasic(httpBasicSpec -> httpBasicSpec.disable());
|
||||||
// 资源服务器配置
|
// 资源服务器配置
|
||||||
http.oauth2ResourceServer(server -> server
|
http.oauth2ResourceServer(server -> server
|
||||||
// 权限不通过时,自定义返回
|
// 默认的权限校验方法 不通过时,自定义返回,这个用不到,因为没有走默认权限校验
|
||||||
.accessDeniedHandler(new MyAccessDeniedHandler())
|
.accessDeniedHandler(new MyAccessDeniedHandler())
|
||||||
// 未登录或者登陆验证失败时(token有问题),自定义返回
|
// 凭据验证失败时(token有问题),自定义返回
|
||||||
.authenticationEntryPoint(new MyAuthenticationEntryPoint())
|
.authenticationEntryPoint(new MyAuthenticationEntryPoint())
|
||||||
// 使用jwt默认配置
|
// 使用jwt默认配置
|
||||||
// .jwt(Customizer.withDefaults())
|
// .jwt(Customizer.withDefaults())
|
||||||
@ -66,6 +66,7 @@ public class ResourceServerConfig {
|
|||||||
exchange
|
exchange
|
||||||
.pathMatchers(Convert.toStrArray(ignoreUri.getIgnoreUris())).permitAll()
|
.pathMatchers(Convert.toStrArray(ignoreUri.getIgnoreUris())).permitAll()
|
||||||
.pathMatchers(ignoreFixedUris()).permitAll()
|
.pathMatchers(ignoreFixedUris()).permitAll()
|
||||||
|
.pathMatchers("/gateway/wechat/*").access(authorizationManager)
|
||||||
// .anyExchange().authenticated()
|
// .anyExchange().authenticated()
|
||||||
// 其他走自定义逻辑
|
// 其他走自定义逻辑
|
||||||
.anyExchange().access(authorizationManager)
|
.anyExchange().access(authorizationManager)
|
||||||
|
|||||||
@ -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<AuthorizationContext> {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private RedisUtil redisUtil;
|
||||||
|
@Resource
|
||||||
|
private WechatService wechatService;
|
||||||
|
|
||||||
|
@Value("${yt.gateway.is_pass:false}")
|
||||||
|
private boolean isPass;
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<AuthorizationDecision> check(Mono<Authentication> 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<AuthorizationDecision> 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<String, String> 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异常"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@ -24,6 +24,7 @@ import org.springframework.web.reactive.result.view.ViewResolver;
|
|||||||
* 根据{@link}ErrorWebFluxAutoConfiguration的配置 重写
|
* 根据{@link}ErrorWebFluxAutoConfiguration的配置 重写
|
||||||
* 主要是重写errorWebExceptionHandler()的逻辑
|
* 主要是重写errorWebExceptionHandler()的逻辑
|
||||||
* 里面不要DefaultErrorWebExceptionHandler了,用自己写的异常处理类替换
|
* 里面不要DefaultErrorWebExceptionHandler了,用自己写的异常处理类替换
|
||||||
|
* 这个全局异常 主要用来捕获 自定义权限验证{@link AuthorizationManager}类中抛出的异常
|
||||||
*/
|
*/
|
||||||
@Configuration(proxyBeanMethods = false)
|
@Configuration(proxyBeanMethods = false)
|
||||||
//@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
|
//@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
|
||||||
|
|||||||
@ -22,6 +22,7 @@ import reactor.core.publisher.Mono;
|
|||||||
/**
|
/**
|
||||||
* 异常处理操作,自定义异常中的内容
|
* 异常处理操作,自定义异常中的内容
|
||||||
* 重写了{@link}DefaultErrorWebExceptionHandler部分内容
|
* 重写了{@link}DefaultErrorWebExceptionHandler部分内容
|
||||||
|
* 这个全局异常 主要用来捕获 自定义权限验证{@link AuthorizationManager}类中抛出的异常
|
||||||
*/
|
*/
|
||||||
public class GlobalExceptionHandler extends DefaultErrorWebExceptionHandler {
|
public class GlobalExceptionHandler extends DefaultErrorWebExceptionHandler {
|
||||||
|
|
||||||
|
|||||||
@ -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<String, String> tokenBuilder(@RequestParam String wuid);
|
||||||
|
|
||||||
|
}
|
||||||
@ -1,13 +1,26 @@
|
|||||||
package com.evotech.hd.gateway;
|
package com.evotech.hd.gateway;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
|
||||||
|
import com.evotech.hd.gateway.oauth2.service.WechatService;
|
||||||
|
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
|
||||||
@SpringBootTest
|
@SpringBootTest
|
||||||
class GatewayServerApplicationTests {
|
class GatewayServerApplicationTests {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private WechatService wechatService;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void contextLoads() {
|
void contextLoads() {
|
||||||
|
Map<String, String> m = wechatService.tokenBuilder("123");
|
||||||
|
System.out.println("0000000000");
|
||||||
|
System.out.println(m);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -56,7 +56,7 @@ public class WechatAgreementController {
|
|||||||
@Operation(summary = "查询")
|
@Operation(summary = "查询")
|
||||||
@GetMapping("/list")
|
@GetMapping("/list")
|
||||||
@ApiOperationSupport(order = 4)
|
@ApiOperationSupport(order = 4)
|
||||||
public Result<List<WechatAgreement>> list(@RequestParam String appid, @RequestParam Integer type, @RequestParam Integer status) {
|
public Result<List<WechatAgreement>> list(@RequestParam(required = false) String appid, @RequestParam(required = false) Integer type, @RequestParam(required = false) Integer status) {
|
||||||
return wechatAgreementService.list(appid, type, status);
|
return wechatAgreementService.list(appid, type, status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -55,7 +55,7 @@ public class WechatSwiperController {
|
|||||||
@Operation(summary = "查询")
|
@Operation(summary = "查询")
|
||||||
@GetMapping("/list")
|
@GetMapping("/list")
|
||||||
@ApiOperationSupport(order = 4)
|
@ApiOperationSupport(order = 4)
|
||||||
public Result<List<WechatSwiper>> list(@RequestParam String appid, @RequestParam Integer type, @RequestParam Integer status) {
|
public Result<List<WechatSwiper>> list(@RequestParam(required = false) String appid, @RequestParam(required = false) Integer type, @RequestParam(required = false) Integer status) {
|
||||||
return wechatSwiperService.list(appid, type, status);
|
return wechatSwiperService.list(appid, type, status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -71,10 +71,6 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>cn.hutool</groupId>
|
<groupId>cn.hutool</groupId>
|
||||||
<artifactId>hutool-json</artifactId>
|
<artifactId>hutool-json</artifactId>
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>cn.hutool</groupId>
|
|
||||||
<artifactId>hutool-crypto</artifactId>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
package com.evotech.hd.wechat.controller;
|
package com.evotech.hd.wechat.controller;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
@ -26,14 +28,14 @@ public class LoginController {
|
|||||||
@Operation(summary = "登陆")
|
@Operation(summary = "登陆")
|
||||||
@PostMapping("/xcxlogin")
|
@PostMapping("/xcxlogin")
|
||||||
@Parameter(name="js_code", description = "登录时获取的 code,可通过wx.login获取", example = "")
|
@Parameter(name="js_code", description = "登录时获取的 code,可通过wx.login获取", example = "")
|
||||||
public Result<String> code2Session(String js_code) {
|
public Result<Map<String, String>> code2Session(String js_code) {
|
||||||
return loginService.code2Session(js_code);
|
return loginService.code2Session(js_code);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Operation(summary = "检验小程序登录态")
|
@Operation(summary = "检验小程序登录态")
|
||||||
@PostMapping("/checksessionkey")
|
@PostMapping("/checksessionkey")
|
||||||
public Result<Boolean> checkSessionKey(String wuid) {
|
public Result<Map<String, String>> checkSessionKey(String wuid) {
|
||||||
return loginService.checkSessionKey(wuid);
|
return loginService.checkSessionKey(wuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,5 +46,10 @@ public class LoginController {
|
|||||||
return loginService.phoneNumber(wuid, code);
|
return loginService.phoneNumber(wuid, code);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/tokenbuild")
|
||||||
|
public Map<String, String> tokenBuilder(String wuid) {
|
||||||
|
return loginService.tokenBuilder(wuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,13 +1,17 @@
|
|||||||
package com.evotech.hd.wechat.service;
|
package com.evotech.hd.wechat.service;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import com.evotech.hd.common.core.entity.Result;
|
import com.evotech.hd.common.core.entity.Result;
|
||||||
|
|
||||||
public interface LoginService {
|
public interface LoginService {
|
||||||
|
|
||||||
public Result<String> code2Session(String js_code);
|
public Result<Map<String, String>> code2Session(String js_code);
|
||||||
|
|
||||||
public Result<Boolean> checkSessionKey(String wuid);
|
public Result<Map<String, String>> checkSessionKey(String wuid);
|
||||||
|
|
||||||
public Result<String> phoneNumber(String wuid, String code);
|
public Result<String> phoneNumber(String wuid, String code);
|
||||||
|
|
||||||
|
public Map<String, String> tokenBuilder(String wuid);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,10 @@
|
|||||||
package com.evotech.hd.wechat.service.impl;
|
package com.evotech.hd.wechat.service.impl;
|
||||||
|
|
||||||
import java.util.Date;
|
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.stereotype.Service;
|
||||||
import org.springframework.util.StringUtils;
|
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.Result;
|
||||||
import com.evotech.hd.common.core.entity.wechat.WechatUser;
|
import com.evotech.hd.common.core.entity.wechat.WechatUser;
|
||||||
import com.evotech.hd.common.core.enums.CodeMsg;
|
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.common.redis.utils.RedisUtil;
|
||||||
import com.evotech.hd.wechat.config.XcxProperties;
|
import com.evotech.hd.wechat.config.XcxProperties;
|
||||||
import com.evotech.hd.wechat.service.AccessTokenService;
|
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.LoginUtil;
|
||||||
import com.evotech.hd.wechat.utils.xcx.PhoneNumberUtil;
|
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.DigestUtil;
|
||||||
import cn.hutool.crypto.digest.HMac;
|
import cn.hutool.crypto.digest.HMac;
|
||||||
import cn.hutool.crypto.digest.HmacAlgorithm;
|
import cn.hutool.crypto.digest.HmacAlgorithm;
|
||||||
import cn.hutool.crypto.digest.MD5;
|
|
||||||
import cn.hutool.json.JSONObject;
|
import cn.hutool.json.JSONObject;
|
||||||
import cn.hutool.json.JSONUtil;
|
import cn.hutool.json.JSONUtil;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
@ -38,12 +43,20 @@ public class LoginServiceImpl implements LoginService {
|
|||||||
@Resource
|
@Resource
|
||||||
private AccessTokenService accessTokenService;
|
private AccessTokenService accessTokenService;
|
||||||
|
|
||||||
|
@Value("${hbyt.xcx.token_exp_hour:2}")
|
||||||
|
private Integer tokenExpHour;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 登陆成功后会生成本地token,返回前端,前端请求携带
|
||||||
|
* 在gateway中对微信服务的token重加一套验证逻辑,
|
||||||
|
* 在gateway验证时,如果是过期,会在那重新生成token返回
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Result<String> code2Session(String js_code) {
|
public Result<Map<String, String>> code2Session(String js_code) {
|
||||||
String res = LoginUtil.code2Session(xcxProperties.getAppid(), xcxProperties.getAppSecret(), js_code);
|
String res = LoginUtil.code2Session(xcxProperties.getAppid(), xcxProperties.getAppSecret(), js_code);
|
||||||
JSONObject jo = JSONUtil.parseObj(res);
|
JSONObject jo = JSONUtil.parseObj(res);
|
||||||
if (StringUtils.hasText(jo.getStr("errcode"))) {
|
if (StringUtils.hasText(jo.getStr("errcode"))) {
|
||||||
return new Result<String>().error(CodeMsg.WECHAT_LOGIN_ERROR, jo);
|
return new Result<Map<String, String>>().error(CodeMsg.WECHAT_LOGIN_ERROR, jo);
|
||||||
}
|
}
|
||||||
String openid = jo.getStr("openid");
|
String openid = jo.getStr("openid");
|
||||||
String unionid = jo.getStr("unionid");
|
String unionid = jo.getStr("unionid");
|
||||||
@ -51,7 +64,7 @@ public class LoginServiceImpl implements LoginService {
|
|||||||
// 是否登陆过
|
// 是否登陆过
|
||||||
WechatUser wuser = wechatUserDao.selectOne(new QueryWrapper<WechatUser>().eq("openid", openid));
|
WechatUser wuser = wechatUserDao.selectOne(new QueryWrapper<WechatUser>().eq("openid", openid));
|
||||||
if (wuser == null) {
|
if (wuser == null) {
|
||||||
String wuid = MD5.create().digestHex(xcxProperties.getAppid() + openid + "HBYT");
|
String wuid = XCXUtil.wechatUid(xcxProperties.getAppid(), openid);
|
||||||
wuser = new WechatUser();
|
wuser = new WechatUser();
|
||||||
wuser.setAppid(xcxProperties.getAppid());
|
wuser.setAppid(xcxProperties.getAppid());
|
||||||
wuser.setCtime(new Date());
|
wuser.setCtime(new Date());
|
||||||
@ -64,26 +77,35 @@ public class LoginServiceImpl implements LoginService {
|
|||||||
redisUtil.set(HDConstant.openidPrefix + wuser.getWuid(), openid);
|
redisUtil.set(HDConstant.openidPrefix + wuser.getWuid(), openid);
|
||||||
redisUtil.set(HDConstant.unionidPrefix + wuser.getWuid(), unionid);
|
redisUtil.set(HDConstant.unionidPrefix + wuser.getWuid(), unionid);
|
||||||
redisUtil.set(HDConstant.sessionKeyPrefix + wuser.getWuid(), sessionKey);
|
redisUtil.set(HDConstant.sessionKeyPrefix + wuser.getWuid(), sessionKey);
|
||||||
|
// token
|
||||||
return new Result<String>().success(wuser.getWuid());
|
Map<String, String> m = tokenBuilder(wuser.getWuid());
|
||||||
|
return new Result<Map<String, String>>().success(m);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查登录态,检查成功后刷新本地token
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Result<Boolean> checkSessionKey(String wuid) {
|
public Result<Map<String, String>> checkSessionKey(String wuid) {
|
||||||
if (!redisUtil.hasKey(HDConstant.openidPrefix + wuid) || !redisUtil.hasKey(HDConstant.sessionKeyPrefix + wuid)) {
|
if (!redisUtil.hasKey(HDConstant.openidPrefix + wuid) || !redisUtil.hasKey(HDConstant.sessionKeyPrefix + wuid)) {
|
||||||
return new Result<Boolean>().error(CodeMsg.WECHAT_SERRION_ERROR);
|
return new Result<Map<String, String>>().error(CodeMsg.WECHAT_SERRION_ERROR);
|
||||||
}
|
}
|
||||||
String openid = redisUtil.get(HDConstant.openidPrefix + wuid).toString();
|
String openid = redisUtil.get(HDConstant.openidPrefix + wuid).toString();
|
||||||
String sessionKey = redisUtil.get(HDConstant.sessionKeyPrefix + wuid).toString();
|
String sessionKey = redisUtil.get(HDConstant.sessionKeyPrefix + wuid).toString();
|
||||||
HMac hmac = DigestUtil.hmac(HmacAlgorithm.HmacSHA256, sessionKey.getBytes());
|
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);
|
JSONObject jo = JSONUtil.parseObj(res);
|
||||||
if (jo.getInt("errcode") == 0) {
|
if (jo.getInt("errcode") == 0) {
|
||||||
return new Result<Boolean>().success("OK");
|
Map<String, String> m = tokenBuilder(wuid);
|
||||||
|
return new Result<Map<String, String>>().success(m);
|
||||||
}
|
}
|
||||||
return new Result<Boolean>().error(CodeMsg.WECHAT_SERRION_ERROR, jo);
|
return new Result<Map<String, String>>().error(CodeMsg.WECHAT_SERRION_ERROR, jo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Result<String> phoneNumber(String wuid, String code) {
|
public Result<String> phoneNumber(String wuid, String code) {
|
||||||
WechatUser wuser = wechatUserDao.selectOne(new QueryWrapper<WechatUser>().eq("wuid", wuid));
|
WechatUser wuser = wechatUserDao.selectOne(new QueryWrapper<WechatUser>().eq("wuid", wuid));
|
||||||
@ -107,6 +129,19 @@ public class LoginServiceImpl implements LoginService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, String> 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<String, String> m = new HashMap<String, String>();
|
||||||
|
m.put("wuid", wuid);
|
||||||
|
m.put("wxToken", wxToken);
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -38,5 +38,10 @@
|
|||||||
"name": "hbyt.xcx.refund_notify_url",
|
"name": "hbyt.xcx.refund_notify_url",
|
||||||
"type": "java.lang.String",
|
"type": "java.lang.String",
|
||||||
"description": "A description for 'hbyt.xcx.refund_notify_url'"
|
"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'"
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
@ -56,3 +56,6 @@ hbyt:
|
|||||||
notify_url: https://api.evo-techina.com/wechat/wechatpay/prepayback/msg
|
notify_url: https://api.evo-techina.com/wechat/wechatpay/prepayback/msg
|
||||||
# 退款回调地址
|
# 退款回调地址
|
||||||
refund_notify_url: https://api.evo-techina.com/wechat/wechatpay/refundsback/msg
|
refund_notify_url: https://api.evo-techina.com/wechat/wechatpay/refundsback/msg
|
||||||
|
# token缓存时效:小时
|
||||||
|
token_exp_hour: 2
|
||||||
|
|
||||||
Loading…
Reference in New Issue
Block a user