提交 9a991973 编写于 作者: 郝先瑞

refactor: 认证中心升级(临时提交勿clone)

上级 84aa6da7
......@@ -13,8 +13,8 @@
<description>有来实验室</description>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
<dependencies>
......
......@@ -2,7 +2,7 @@ package com.youlai.mall.oms.model.dto;
import com.youlai.mall.oms.model.entity.OmsOrder;
import com.youlai.mall.oms.model.entity.OmsOrderItem;
import com.youlai.mall.ums.dto.MemberDTO;
import com.youlai.mall.ums.dto.MemberRegisterDto;
import lombok.Data;
import lombok.experimental.Accessors;
......@@ -22,6 +22,6 @@ public class OrderDTO {
private List<OmsOrderItem> orderItems;
private MemberDTO member;
private MemberRegisterDto member;
}
......@@ -4,7 +4,7 @@ import com.youlai.common.result.Result;
import com.youlai.mall.pms.model.vo.ProductHistoryVO;
import com.youlai.mall.ums.dto.MemberAddressDTO;
import com.youlai.mall.ums.dto.MemberAuthDTO;
import com.youlai.mall.ums.dto.MemberDTO;
import com.youlai.mall.ums.dto.MemberRegisterDto;
import com.youlai.mall.ums.dto.MemberInfoDTO;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
......@@ -27,7 +27,7 @@ public interface MemberFeignClient {
* @return
*/
@PostMapping("/app-api/v1/members")
Result<Long> addMember(@RequestBody MemberDTO member);
Result<Long> registerMember(@RequestBody MemberRegisterDto member);
/**
* 获取会员的 openid
......
package com.youlai.mall.ums.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
/**
......@@ -10,7 +12,8 @@ import lombok.experimental.Accessors;
* @since 2022/2/12
*/
@Data
@Accessors(chain = true)
@AllArgsConstructor
@NoArgsConstructor
public class MemberAuthDTO {
/**
......
......@@ -11,7 +11,7 @@ import java.time.LocalDate;
* @since 2022/2/12
*/
@Data
public class MemberDTO {
public class MemberRegisterDto {
private Integer gender;
......
......@@ -8,7 +8,7 @@ import com.youlai.common.security.util.SecurityUtils;
import com.youlai.mall.pms.model.vo.ProductHistoryVO;
import com.youlai.mall.ums.dto.MemberAddressDTO;
import com.youlai.mall.ums.dto.MemberAuthDTO;
import com.youlai.mall.ums.dto.MemberDTO;
import com.youlai.mall.ums.dto.MemberRegisterDto;
import com.youlai.mall.ums.model.entity.UmsMember;
import com.youlai.mall.ums.model.vo.MemberVO;
import com.youlai.mall.ums.service.IUmsMemberService;
......@@ -40,7 +40,7 @@ public class MemberController {
@Operation(summary= "新增会员")
@PostMapping
public Result<Long> addMember(@RequestBody MemberDTO member) {
public Result<Long> addMember(@RequestBody MemberRegisterDto member) {
Long memberId = memberService.addMember(member);
return Result.success(memberId);
}
......
package com.youlai.mall.ums.convert;
import com.youlai.mall.ums.dto.MemberAuthDTO;
import com.youlai.mall.ums.dto.MemberDTO;
import com.youlai.mall.ums.dto.MemberRegisterDto;
import com.youlai.mall.ums.dto.MemberInfoDTO;
import com.youlai.mall.ums.model.entity.UmsMember;
import org.mapstruct.Mapper;
......@@ -30,5 +30,5 @@ public interface MemberConvert {
MemberInfoDTO entity2MemberInfoDTO(UmsMember entity);
UmsMember dto2Entity(MemberDTO memberDTO);
UmsMember dto2Entity(MemberRegisterDto memberRegisterDTO);
}
......@@ -7,7 +7,7 @@ import com.baomidou.mybatisplus.extension.service.IService;
import com.youlai.mall.pms.model.vo.ProductHistoryVO;
import com.youlai.mall.ums.dto.MemberAddressDTO;
import com.youlai.mall.ums.dto.MemberAuthDTO;
import com.youlai.mall.ums.dto.MemberDTO;
import com.youlai.mall.ums.dto.MemberRegisterDto;
import com.youlai.mall.ums.dto.MemberInfoDTO;
import com.youlai.mall.ums.model.entity.UmsMember;
import com.youlai.mall.ums.model.vo.MemberVO;
......@@ -51,7 +51,7 @@ public interface IUmsMemberService extends IService<UmsMember> {
* @param member
* @return
*/
Long addMember(MemberDTO member);
Long addMember(MemberRegisterDto member);
/**
* 获取登录会员信息
......
......@@ -14,7 +14,7 @@ import com.youlai.mall.ums.convert.AddressConvert;
import com.youlai.mall.ums.convert.MemberConvert;
import com.youlai.mall.ums.dto.MemberAddressDTO;
import com.youlai.mall.ums.dto.MemberAuthDTO;
import com.youlai.mall.ums.dto.MemberDTO;
import com.youlai.mall.ums.dto.MemberRegisterDto;
import com.youlai.mall.ums.dto.MemberInfoDTO;
import com.youlai.mall.ums.mapper.UmsMemberMapper;
import com.youlai.mall.ums.model.entity.UmsAddress;
......@@ -114,12 +114,12 @@ public class UmsMemberServiceImpl extends ServiceImpl<UmsMemberMapper, UmsMember
/**
* 新增会员
*
* @param memberDTO
* @param memberRegisterDTO
* @return
*/
@Override
public Long addMember(MemberDTO memberDTO) {
UmsMember umsMember = memberConvert.dto2Entity(memberDTO);
public Long addMember(MemberRegisterDto memberRegisterDTO) {
UmsMember umsMember = memberConvert.dto2Entity(memberRegisterDTO);
boolean result = this.save(umsMember);
Assert.isTrue(result, "新增会员失败");
return umsMember.getId();
......
......@@ -41,15 +41,15 @@
<spring-boot.version>3.1.0</spring-boot.version>
<!-- spring cloud & alibaba -->
<spring-cloud.version>2022.0.2</spring-cloud.version>
<spring-cloud.version>2022.0.3</spring-cloud.version>
<spring-cloud-alibaba.version>2022.0.0.0-RC2</spring-cloud-alibaba.version>
<!-- -->
<!-- spring authorization server -->
<authorization-server.version>1.1.0</authorization-server.version>
<nimbus-jose-jwt.version>9.16.1</nimbus-jose-jwt.version>
<!-- db && orm -->
<mysql.version>8.0.28</mysql.version>
<druid.version>1.2.16</druid.version>
<druid.version>1.2.18</druid.version>
<mybatis-plus.version>3.5.3.1</mybatis-plus.version>
<!-- api doc -->
......@@ -66,7 +66,6 @@
<thumbnailator.version>0.4.17</thumbnailator.version>
<!-- distributed -->
<seata.version>1.5.2</seata.version>
<redisson.version>3.21.0</redisson.version>
<!-- 阿里云短信 -->
......@@ -158,13 +157,6 @@
<version>${weixin-java.version}</version>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>${seata.version}</version>
</dependency>
<!-- 分布式锁 -->
<dependency>
<groupId>org.redisson</groupId>
......@@ -172,8 +164,6 @@
<version>${redisson.version}</version>
</dependency>
<dependency>
<groupId>com.youlai</groupId>
<artifactId>common-core</artifactId>
......
package com.youlai.auth.authentication.mobile;
import cn.hutool.core.util.StrUtil;
import com.youlai.auth.userdetails.member.MemberUserDetailsServiceImpl;
import com.youlai.auth.userdetails.member.MmsUserDetailsService;
import com.youlai.common.constant.SecurityConstants;
import com.youlai.common.web.exception.BizException;
import com.youlai.mall.ums.api.MemberFeignClient;
......@@ -44,7 +44,7 @@ public class SmsCodeAuthenticationProvider implements AuthenticationProvider {
// 比对成功删除缓存的验证码
redisTemplate.delete(codeKey);
}
UserDetails userDetails = ((MemberUserDetailsServiceImpl) userDetailsService).loadUserByMobile(mobile);
UserDetails userDetails = ((MmsUserDetailsService) userDetailsService).loadUserByMobile(mobile);
SmsCodeAuthenticationToken result = new SmsCodeAuthenticationToken(userDetails, authentication.getCredentials(), new HashSet<>());
result.setDetails(authentication.getDetails());
return result;
......
package com.youlai.auth.authentication.refresh;
import com.youlai.auth.config.AuthorizationServerConfig;
import com.youlai.auth.userdetails.member.MemberUserDetailsServiceImpl;
import com.youlai.auth.userdetails.member.MmsUserDetailsService;
import com.youlai.auth.util.RequestUtils;
import com.youlai.common.constant.SecurityConstants;
import lombok.NoArgsConstructor;
......@@ -54,13 +53,13 @@ public class PreAuthenticatedUserDetailsService<T extends Authentication> implem
switch (clientId) {
case SecurityConstants.APP_CLIENT_ID -> {
// 移动端的用户体系是会员,认证方式是通过手机号 mobile 认证
MemberUserDetailsServiceImpl memberUserDetailsService = (MemberUserDetailsServiceImpl) userDetailsService;
return memberUserDetailsService.loadUserByUsername(authentication.getName());
MmsUserDetailsService mmsUserDetailsService = (MmsUserDetailsService) userDetailsService;
return mmsUserDetailsService.loadUserByUsername(authentication.getName());
}
case SecurityConstants.WEAPP_CLIENT_ID -> {
// 小程序的用户体系是会员,认证方式是通过微信三方标识 openid 认证
MemberUserDetailsServiceImpl memberUserDetailsService = (MemberUserDetailsServiceImpl) userDetailsService;
return memberUserDetailsService.loadUserByOpenId(authentication.getName());
MmsUserDetailsService mmsUserDetailsService = (MmsUserDetailsService) userDetailsService;
return mmsUserDetailsService.loadUserByOpenId(authentication.getName());
}
// 管理系统的用户体系是系统用户,认证方式通过用户名 username 认证
default -> {
......
package com.youlai.auth.authentication.wechat;
import lombok.Getter;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationGrantAuthenticationToken;
import java.util.Map;
/**
* 微信小程序
*
* @author haoxr
* @see OAuth2AuthorizationCodeAuthenticationToken
* @since 3.0.0
*/
public class WeChatMiniProgramAuthenticationToken extends OAuth2AuthorizationGrantAuthenticationToken {
/**
* 授权类型:微信小程序
*/
public static final AuthorizationGrantType WECHAT_MINI_PROGRAM = new AuthorizationGrantType("wechat_mini_program");
@Getter
private final String code;
@Getter
private final String encryptedData;
@Getter
private final String iv;
/**
* @see org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames#SCOPE
*/
private final String scope;
/**
* Sub-class constructor.
*
* @param clientPrincipal the authenticated client principal
* @param additionalParameters the additional parameters
*/
protected WeChatMiniProgramAuthenticationToken(
Authentication clientPrincipal,
Map<String, Object> additionalParameters,
String code,
String encryptedData,
String iv,
String scope
) {
super(WeChatMiniProgramAuthenticationToken.WECHAT_MINI_PROGRAM, clientPrincipal, additionalParameters);
this.code = code;
this.encryptedData = encryptedData;
this.iv = iv;
this.scope = scope;
}
}
package com.youlai.auth.authentication.wechat;
import cn.hutool.core.util.StrUtil;
import com.youlai.auth.authentication.password.ResourceOwnerPasswordAuthenticationToken;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
* 参数解析
*
* @see org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationCodeAuthenticationConverter
*/
public class WechatAuthenticationConverter implements AuthenticationConverter {
public static final String ACCESS_TOKEN_REQUEST_ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-5.2";
@Override
public Authentication convert(HttpServletRequest request) {
// grant_type (REQUIRED)
String grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE);
if (!"wechat".equals(grantType)) {
return null;
}
MultiValueMap<String, String> parameters = getParameters(request);
// scope (OPTIONAL)
String scope = parameters.getFirst(OAuth2ParameterNames.SCOPE);
if (StringUtils.hasText(scope) &&
parameters.get(OAuth2ParameterNames.SCOPE).size() != 1) {
throwError(
OAuth2ErrorCodes.INVALID_REQUEST,
OAuth2ParameterNames.SCOPE,
ACCESS_TOKEN_REQUEST_ERROR_URI);
}
Set<String> requestedScopes = null;
if (StringUtils.hasText(scope)) {
requestedScopes = new HashSet<>(
Arrays.asList(StringUtils.delimitedListToStringArray(scope, " ")));
}
// code (REQUIRED)
String code = parameters.getFirst("code");
if (StrUtil.isBlank(code)) {
throwError(
OAuth2ErrorCodes.INVALID_REQUEST,
"code",
ACCESS_TOKEN_REQUEST_ERROR_URI);
}
// encryptedData (REQUIRED)
String encryptedData = parameters.getFirst("encryptedData");
if (StrUtil.isBlank(encryptedData)) {
throwError(
OAuth2ErrorCodes.INVALID_REQUEST,
"encryptedData",
ACCESS_TOKEN_REQUEST_ERROR_URI);
}
// iv (REQUIRED)
String iv = parameters.getFirst("iv");
if (StrUtil.isBlank(iv)) {
throwError(
OAuth2ErrorCodes.INVALID_REQUEST,
"iv",
ACCESS_TOKEN_REQUEST_ERROR_URI);
}
Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication();
if (clientPrincipal == null) {
throwError(
OAuth2ErrorCodes.INVALID_REQUEST,
OAuth2ErrorCodes.INVALID_CLIENT,
ACCESS_TOKEN_REQUEST_ERROR_URI);
}
Map<String, Object> additionalParameters = parameters
.entrySet()
.stream()
.filter(e -> !e.getKey().equals(OAuth2ParameterNames.GRANT_TYPE) &&
!e.getKey().equals(OAuth2ParameterNames.SCOPE))
.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().get(0)));
ResourceOwnerPasswordAuthenticationToken resourceOwnerPasswordAuthenticationToken =
new ResourceOwnerPasswordAuthenticationToken(
clientPrincipal,
requestedScopes,
additionalParameters
);
return resourceOwnerPasswordAuthenticationToken;
}
public static MultiValueMap<String, String> getParameters(HttpServletRequest request) {
Map<String, String[]> parameterMap = request.getParameterMap();
MultiValueMap<String, String> parameters = new LinkedMultiValueMap(parameterMap.size());
parameterMap.forEach((key, values) -> {
for (String value : values) {
parameters.add(key, value);
}
});
return parameters;
}
public static void throwError(String errorCode, String parameterName, String errorUri) {
OAuth2Error error = new OAuth2Error(errorCode, "OAuth 2.0 Parameter: " + parameterName, errorUri);
throw new OAuth2AuthenticationException(error);
}
}
package com.youlai.auth.authentication.wechat;
import cn.hutool.core.lang.Assert;
import com.youlai.auth.authentication.password.ResourceOwnerPasswordAuthenticationToken;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.core.*;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder;
import org.springframework.security.oauth2.server.authorization.token.DefaultOAuth2TokenContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
import org.springframework.util.CollectionUtils;
import java.security.Principal;
import java.util.*;
import java.util.stream.Collectors;
/**
* 微信认证提供者
*
* @author haoxr
* @since 3.0.0
*/
@Slf4j
public class WechatAuthenticationProvider implements AuthenticationProvider {
private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-5.2";
private final AuthenticationManager authenticationManager;
private final OAuth2AuthorizationService authorizationService;
private final OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator;
/**
* Constructs an {@code OAuth2ResourceOwnerPasswordAuthenticationProviderNew} using the provided parameters.
*
* @param authenticationManager the authentication manager
* @param authorizationService the authorization service
* @param tokenGenerator the token generator
* @since 0.2.3
*/
public WechatAuthenticationProvider(AuthenticationManager authenticationManager,
OAuth2AuthorizationService authorizationService,
OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator
) {
Assert.notNull(authorizationService, "authorizationService cannot be null");
Assert.notNull(tokenGenerator, "tokenGenerator cannot be null");
this.authenticationManager = authenticationManager;
this.authorizationService = authorizationService;
this.tokenGenerator = tokenGenerator;
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
ResourceOwnerPasswordAuthenticationToken authenticationToken = (ResourceOwnerPasswordAuthenticationToken) authentication;
// 验证客户端是否已认证
OAuth2ClientAuthenticationToken clientPrincipal = getAuthenticatedClientElseThrowInvalidClient(authenticationToken);
RegisteredClient registeredClient = clientPrincipal.getRegisteredClient();
// 验证客户端是否支持(grant_type=password)授权模式
if (!registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.PASSWORD)) {
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT);
}
// 密码验证
Map<String, Object> additionalParameters = authenticationToken.getAdditionalParameters();
String username = (String) additionalParameters.get("code");
String encryptedData = (String) additionalParameters.get("encryptedData");
String iv = (String) additionalParameters.get("iv");
UsernamePasswordAuthenticationToken passwordAuthenticationToken = new UsernamePasswordAuthenticationToken(username, password);
log.debug("got usernamePasswordAuthenticationToken=" + passwordAuthenticationToken);
Authentication usernamePasswordAuthentication = authenticationManager.authenticate(passwordAuthenticationToken);
usernamePasswordAuthentication.setAuthenticated(true);
// 生成 access_token
// @formatter:off
DefaultOAuth2TokenContext.Builder tokenContextBuilder = DefaultOAuth2TokenContext.builder()
.registeredClient(registeredClient)
.principal(usernamePasswordAuthentication)
.authorizationServerContext(AuthorizationServerContextHolder.getContext())
.authorizationGrantType(AuthorizationGrantType.PASSWORD)
.authorizationGrant(passwordAuthenticationToken);
// @formatter:on
// ----- Access token -----
OAuth2TokenContext tokenContext = tokenContextBuilder.tokenType(OAuth2TokenType.ACCESS_TOKEN).build();
OAuth2Token generatedAccessToken = this.tokenGenerator.generate(tokenContext);
if (generatedAccessToken == null) {
OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR,
"The token generator failed to generate the access token.", ERROR_URI);
throw new OAuth2AuthenticationException(error);
}
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
generatedAccessToken.getTokenValue(), generatedAccessToken.getIssuedAt(),
generatedAccessToken.getExpiresAt(), tokenContext.getAuthorizedScopes());
// @formatter:off
OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.withRegisteredClient(registeredClient)
.principalName(usernamePasswordAuthentication.getName())
.authorizationGrantType(AuthorizationGrantType.PASSWORD)
.attribute(Principal.class.getName(), usernamePasswordAuthentication);
// @formatter:on
if (generatedAccessToken instanceof ClaimAccessor) {
authorizationBuilder.token(accessToken, (metadata) ->
metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, ((ClaimAccessor) generatedAccessToken).getClaims()));
} else {
authorizationBuilder.accessToken(accessToken);
}
// ----- Refresh token -----
OAuth2RefreshToken refreshToken = null;
if (registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.REFRESH_TOKEN) &&
// Do not issue refresh token to public client
!clientPrincipal.getClientAuthenticationMethod().equals(ClientAuthenticationMethod.NONE)) {
tokenContext = tokenContextBuilder.tokenType(OAuth2TokenType.REFRESH_TOKEN).build();
OAuth2Token generatedRefreshToken = this.tokenGenerator.generate(tokenContext);
if (!(generatedRefreshToken instanceof OAuth2RefreshToken)) {
OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR,
"The token generator failed to generate the refresh token.", ERROR_URI);
throw new OAuth2AuthenticationException(error);
}
refreshToken = (OAuth2RefreshToken) generatedRefreshToken;
authorizationBuilder.refreshToken(refreshToken);
}
OAuth2Authorization authorization = authorizationBuilder.build();
this.authorizationService.save(authorization);
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, accessToken, refreshToken, additionalParameters);
}
@Override
public boolean supports(Class<?> authentication) {
return ResourceOwnerPasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
private static OAuth2ClientAuthenticationToken getAuthenticatedClientElseThrowInvalidClient(Authentication authentication) {
OAuth2ClientAuthenticationToken clientPrincipal = null;
if (OAuth2ClientAuthenticationToken.class.isAssignableFrom(authentication.getPrincipal().getClass())) {
clientPrincipal = (OAuth2ClientAuthenticationToken) authentication.getPrincipal();
}
if (clientPrincipal != null && clientPrincipal.isAuthenticated()) {
return clientPrincipal;
}
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_CLIENT);
}
}
package com.youlai.auth.authentication.wechat;
import jakarta.annotation.Nullable;
import lombok.Getter;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationGrantAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.util.SpringAuthorizationServerVersion;
import org.springframework.util.Assert;
import java.util.*;
/**
* 微信授权登录
*
* @author haoxr
* @since 3.0.0
*/
public class WechatAuthenticationToken extends OAuth2AuthorizationGrantAuthenticationToken {
private final Set<String> scopes;
/**
* {@link org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientCredentialsAuthenticationToken}
*
* @param clientPrincipal
* @param additionalParameters
*/
public WechatAuthenticationToken(
Authentication clientPrincipal,
@Nullable Set<String> scopes,
Map<String, Object> additionalParameters
) {
super(AuthorizationGrantType.PASSWORD, clientPrincipal, additionalParameters);
this.scopes = Collections.unmodifiableSet(scopes != null ? new HashSet<>(scopes) : Collections.emptySet());
}
public Set<String> getScopes() {
return this.scopes;
}
@Override
public Object getCredentials() {
return this.getAdditionalParameters().get(OAuth2ParameterNames.PASSWORD);
}
}
package com.youlai.auth.authentication.wechat;
import cn.hutool.core.util.StrUtil;
import com.youlai.auth.authentication.password.ResourceOwnerPasswordAuthenticationToken;
import com.youlai.auth.util.OAuth2EndpointUtils;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
* 参数解析
*
* @see org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationCodeAuthenticationConverter
*/
public class WechatMiniProgramAuthenticationConverter implements AuthenticationConverter {
public static final String ACCESS_TOKEN_REQUEST_ERROR_URI ="https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/login/auth.code2Session.html";
String CODE = "code";
String IV = "iv";
String ENCRYPTED_DATA = "encryptedData";
@Override
public Authentication convert(HttpServletRequest request) {
// grant_type (REQUIRED)
String grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE);
if (!WeChatMiniProgramAuthenticationToken.WECHAT_MINI_PROGRAM.getValue().equals(grantType)) {
return null;
}
MultiValueMap<String, String> parameters = OAuth2EndpointUtils.getParameters(request);
// scope (OPTIONAL)
String scope = parameters.getFirst(OAuth2ParameterNames.SCOPE);
if (StringUtils.hasText(scope) &&
parameters.get(OAuth2ParameterNames.SCOPE).size() != 1) {
OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.SCOPE,
ACCESS_TOKEN_REQUEST_ERROR_URI);
}
Set<String> requestedScopes = null;
if (StringUtils.hasText(scope)) {
requestedScopes = new HashSet<>(
Arrays.asList(StringUtils.delimitedListToStringArray(scope, " ")));
}
// code (REQUIRED)
String code = parameters.getFirst(WechatMiniProgramParameterNames.CODE);
if (StrUtil.isBlank(code)) {
throwError(
OAuth2ErrorCodes.INVALID_REQUEST,
WechatMiniProgramParameterNames.CODE,
ACCESS_TOKEN_REQUEST_ERROR_URI);
}
// encryptedData (REQUIRED)
String encryptedData = parameters.getFirst(WechatMiniProgramParameterNames.ENCRYPTED_DATA);
if (StrUtil.isBlank(encryptedData)) {
throwError(
OAuth2ErrorCodes.INVALID_REQUEST,
WechatMiniProgramParameterNames.ENCRYPTED_DATA,
ACCESS_TOKEN_REQUEST_ERROR_URI);
}
// iv (REQUIRED)
String iv = parameters.getFirst(WechatMiniProgramParameterNames.IV);
if (StrUtil.isBlank(iv)) {
throwError(
OAuth2ErrorCodes.INVALID_REQUEST,
WechatMiniProgramParameterNames.IV,
ACCESS_TOKEN_REQUEST_ERROR_URI);
}
Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication();
if (clientPrincipal == null) {
throwError(
OAuth2ErrorCodes.INVALID_REQUEST,
OAuth2ErrorCodes.INVALID_CLIENT,
ACCESS_TOKEN_REQUEST_ERROR_URI);
}
Map<String, Object> additionalParameters = parameters
.entrySet()
.stream()
.filter(e ->
!e.getKey().equals(OAuth2ParameterNames.GRANT_TYPE)
&& !e.getKey().equals(OAuth2ParameterNames.SCOPE)
)
.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().get(0)));
return new ResourceOwnerPasswordAuthenticationToken(
clientPrincipal,
requestedScopes,
additionalParameters
);
}
public static MultiValueMap<String, String> getParameters(HttpServletRequest request) {
Map<String, String[]> parameterMap = request.getParameterMap();
MultiValueMap<String, String> parameters = new LinkedMultiValueMap(parameterMap.size());
parameterMap.forEach((key, values) -> {
for (String value : values) {
parameters.add(key, value);
}
});
return parameters;
}
public static void throwError(String errorCode, String parameterName, String errorUri) {
OAuth2Error error = new OAuth2Error(errorCode, "OAuth 2.0 Parameter: " + parameterName, errorUri);
throw new OAuth2AuthenticationException(error);
}
}
package com.youlai.auth.authentication.wechat;
import cn.hutool.core.lang.Assert;
import com.youlai.auth.authentication.password.ResourceOwnerPasswordAuthenticationToken;
import com.youlai.auth.userdetails.member.MmsUserDetailsService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.oauth2.core.*;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder;
import org.springframework.security.oauth2.server.authorization.token.DefaultOAuth2TokenContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
import java.security.Principal;
import java.util.Map;
/**
* 微信认证提供者
*
* @author haoxr
* @since 3.0.0
*/
@Slf4j
public class WechatMiniProgramAuthenticationProvider implements AuthenticationProvider {
private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-5.2";
private final OAuth2AuthorizationService authorizationService;
private final OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator;
private final MmsUserDetailsService mmsUserDetailsService;
/**
* Constructs an {@code OAuth2ResourceOwnerPasswordAuthenticationProviderNew} using the provided parameters.
*
* @param authenticationManager the authentication manager
* @param authorizationService the authorization service
* @param tokenGenerator the token generator
* @since 0.2.3
*/
public WechatMiniProgramAuthenticationProvider(
OAuth2AuthorizationService authorizationService,
OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator,
MmsUserDetailsService mmsUserDetailsService
) {
Assert.notNull(authorizationService, "authorizationService cannot be null");
Assert.notNull(tokenGenerator, "tokenGenerator cannot be null");
this.authorizationService = authorizationService;
this.tokenGenerator = tokenGenerator;
this.mmsUserDetailsService = mmsUserDetailsService;
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
WeChatMiniProgramAuthenticationToken authenticationToken = (WeChatMiniProgramAuthenticationToken) authentication;
authenticationToken.getIv()
// 验证客户端是否已认证
OAuth2ClientAuthenticationToken clientPrincipal = getAuthenticatedClientElseThrowInvalidClient(authenticationToken);
RegisteredClient registeredClient = clientPrincipal.getRegisteredClient();
// 验证客户端是否支持(grant_type=password)授权模式
if (!registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.PASSWORD)) {
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT);
}
// 密码验证
Map<String, Object> additionalParameters = authenticationToken.getAdditionalParameters();
String code = (String) additionalParameters.get("code");
String encryptedData = (String) additionalParameters.get("encryptedData");
String iv = (String) additionalParameters.get("iv");
UserDetails userDetails = mmsUserDetailsService.loadUserByWechatCode(code, encryptedData, iv);
UsernamePasswordAuthenticationToken principal = UsernamePasswordAuthenticationToken.authenticated(userDetails, null,
userDetails.getAuthorities());
WeChatMiniProgramAuthenticationToken weChatMiniProgramAuthenticationToken =new WeChatMiniProgramAuthenticationToken()
Authentication usernamePasswordAuthentication =new WeChatMiniProgramAuthenticationToken();
// 生成 access_token
// @formatter:off
DefaultOAuth2TokenContext.Builder tokenContextBuilder = DefaultOAuth2TokenContext.builder()
.registeredClient(registeredClient)
.principal(userDetails.getUsername())
.authorizationServerContext(AuthorizationServerContextHolder.getContext())
.authorizationGrantType(AuthorizationGrantType.PASSWORD);
// @formatter:on
// ----- Access token -----
OAuth2TokenContext tokenContext = tokenContextBuilder.tokenType(OAuth2TokenType.ACCESS_TOKEN).build();
OAuth2Token generatedAccessToken = this.tokenGenerator.generate(tokenContext);
if (generatedAccessToken == null) {
OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR,
"The token generator failed to generate the access token.", ERROR_URI);
throw new OAuth2AuthenticationException(error);
}
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
generatedAccessToken.getTokenValue(), generatedAccessToken.getIssuedAt(),
generatedAccessToken.getExpiresAt(), tokenContext.getAuthorizedScopes());
// @formatter:off
OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.withRegisteredClient(registeredClient)
.principalName(usernamePasswordAuthentication.getName())
.authorizationGrantType(AuthorizationGrantType.PASSWORD)
.attribute(Principal.class.getName(), usernamePasswordAuthentication);
// @formatter:on
if (generatedAccessToken instanceof ClaimAccessor) {
authorizationBuilder.token(accessToken, (metadata) ->
metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, ((ClaimAccessor) generatedAccessToken).getClaims()));
} else {
authorizationBuilder.accessToken(accessToken);
}
// ----- Refresh token -----
OAuth2RefreshToken refreshToken = null;
if (registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.REFRESH_TOKEN) &&
// Do not issue refresh token to public client
!clientPrincipal.getClientAuthenticationMethod().equals(ClientAuthenticationMethod.NONE)) {
tokenContext = tokenContextBuilder.tokenType(OAuth2TokenType.REFRESH_TOKEN).build();
OAuth2Token generatedRefreshToken = this.tokenGenerator.generate(tokenContext);
if (!(generatedRefreshToken instanceof OAuth2RefreshToken)) {
OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR,
"The token generator failed to generate the refresh token.", ERROR_URI);
throw new OAuth2AuthenticationException(error);
}
refreshToken = (OAuth2RefreshToken) generatedRefreshToken;
authorizationBuilder.refreshToken(refreshToken);
}
OAuth2Authorization authorization = authorizationBuilder.build();
this.authorizationService.save(authorization);
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, accessToken, refreshToken, additionalParameters);
}
@Override
public boolean supports(Class<?> authentication) {
return ResourceOwnerPasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
private static OAuth2ClientAuthenticationToken getAuthenticatedClientElseThrowInvalidClient(Authentication authentication) {
OAuth2ClientAuthenticationToken clientPrincipal = null;
if (OAuth2ClientAuthenticationToken.class.isAssignableFrom(authentication.getPrincipal().getClass())) {
clientPrincipal = (OAuth2ClientAuthenticationToken) authentication.getPrincipal();
}
if (clientPrincipal != null && clientPrincipal.isAuthenticated()) {
return clientPrincipal;
}
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_CLIENT);
}
}
package com.youlai.auth.authentication.wechat;
/**
* 微信小程序参数名称
*
* @author haoxr
* @since 3.0.0
*/
public interface WechatMiniProgramParameterNames {
String CODE = "code";
String IV = "iv";
String ENCRYPTED_DATA = "encryptedData";
}
package com.youlai.auth.authentication.wechat;
import lombok.Data;
/**
* 微信用户信息
*
* @author <a href="mailto:xianrui0365@163.com">haoxr</a>
* @since 2021/10/4
*/
@Data
public class WechatUserInfo {
private String avatarUrl;
private String city;
private String country;
private Integer gender;
private String language;
private String nickName;
private String province;
}
......@@ -4,20 +4,24 @@ import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl;
import cn.binarywang.wx.miniapp.config.WxMaConfig;
import cn.binarywang.wx.miniapp.config.impl.WxMaDefaultConfigImpl;
import org.springframework.beans.factory.annotation.Value;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 微信小程序配置
*
* @author haoxr
* @since 3.0.0
*/
@ConfigurationProperties(prefix = "miniapp.wechat")
@Configuration
public class WechatWeappConfig {
public class WechatMiniappConfig {
@Value("${wechat.weapp.appid}")
@Setter
private String appid;
@Value("${wechat.weapp.secret}")
@Setter
private String secret;
@Bean
......
......@@ -17,7 +17,7 @@ import java.util.HashSet;
* @since 2021/9/27
*/
@Data
public class MemberUserDetails implements UserDetails {
public class MmsUserDetails implements UserDetails {
private Long memberId;
private String username;
......@@ -34,7 +34,7 @@ public class MemberUserDetails implements UserDetails {
*
* @param member 小程序会员用户认证信息
*/
public MemberUserDetails(MemberAuthDTO member) {
public MmsUserDetails(MemberAuthDTO member) {
this.setMemberId(member.getMemberId());
this.setUsername(member.getUsername());
this.setEnabled(GlobalConstants.STATUS_YES.equals(member.getStatus()));
......
package com.youlai.auth.userdetails.member;
import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import cn.binarywang.wx.miniapp.bean.WxMaUserInfo;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.lang.Assert;
import com.youlai.common.enums.StatusEnum;
import com.youlai.common.result.Result;
import com.youlai.common.result.ResultCode;
import com.youlai.mall.ums.api.MemberFeignClient;
import com.youlai.mall.ums.dto.MemberAuthDTO;
import com.youlai.mall.ums.dto.MemberRegisterDto;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.springframework.security.authentication.AccountExpiredException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.LockedException;
......@@ -20,9 +28,10 @@ import org.springframework.stereotype.Service;
*/
@Service("memberUserDetailsService")
@RequiredArgsConstructor
public class MemberUserDetailsServiceImpl implements UserDetailsService {
public class MmsUserDetailsService implements UserDetailsService {
private final MemberFeignClient memberFeignClient;
private final WxMaService wxMaService;
@Override
public UserDetails loadUserByUsername(String username) {
......@@ -37,12 +46,12 @@ public class MemberUserDetailsServiceImpl implements UserDetailsService {
* @return
*/
public UserDetails loadUserByMobile(String mobile) {
MemberUserDetails userDetails = null;
MmsUserDetails userDetails = null;
Result<MemberAuthDTO> result = memberFeignClient.loadUserByMobile(mobile);
if (Result.isSuccess(result)) {
MemberAuthDTO member = result.getData();
if (null != member) {
userDetails = new MemberUserDetails(member);
userDetails = new MmsUserDetails(member);
}
}
if (userDetails == null) {
......@@ -57,25 +66,45 @@ public class MemberUserDetailsServiceImpl implements UserDetailsService {
return userDetails;
}
/**
* openid 认证方式
*
* @param openId
* @return
*/
public UserDetails loadUserByOpenId(String openId) {
MemberUserDetails userDetails = null;
Result<MemberAuthDTO> result = memberFeignClient.loadUserByOpenId(openId);
if (Result.isSuccess(result)) {
MemberAuthDTO member = result.getData();
if (null != member) {
userDetails = new MemberUserDetails(member);
@SneakyThrows
public UserDetails loadUserByWechatCode(String code, String encryptedData, String iv) {
// 根据 code 获取 openid
WxMaJscode2SessionResult sessionInfo = wxMaService.getUserService().getSessionInfo(code);
String openid = sessionInfo.getOpenid();
// 根据 openid 获取微信用户认证信息
Result<MemberAuthDTO> getMemberAuthInfoResult = memberFeignClient.loadUserByOpenId(openid);
MemberAuthDTO memberAuthInfo = null;
// 会员不存在,注册成为新会员
if (ResultCode.USER_NOT_EXIST.getCode().equals(getMemberAuthInfoResult.getCode())) {
String sessionKey = sessionInfo.getSessionKey();
// 解密 encryptedData 获取用户信息
WxMaUserInfo userInfo = wxMaService.getUserService().getUserInfo(sessionKey, encryptedData, iv);
MemberRegisterDto memberRegisterInfo = new MemberRegisterDto();
BeanUtil.copyProperties(userInfo, memberRegisterInfo);
memberRegisterInfo.setOpenid(openid);
// 注册会员
Result<Long> registerMemberResult = memberFeignClient.registerMember(memberRegisterInfo);
// 注册成功将会员信息赋值给会员认证信息
Long memberId = null;
if (Result.isSuccess(registerMemberResult) && (memberId = registerMemberResult.getData()) != null) {
memberAuthInfo = new MemberAuthDTO(memberId, openid, StatusEnum.ENABLE.getValue());
}
} else {
Assert.isTrue((memberAuthInfo = getMemberAuthInfoResult.getData()) != null, "获取会员认证信息失败");
}
if (userDetails == null) {
// 用户不存在
if (memberAuthInfo == null) {
throw new UsernameNotFoundException(ResultCode.USER_NOT_EXIST.getMsg());
} else if (!userDetails.isEnabled()) {
}
UserDetails userDetails = new MmsUserDetails(memberAuthInfo);
if (!userDetails.isEnabled()) {
throw new DisabledException("该账户已被禁用!");
} else if (!userDetails.isAccountNonLocked()) {
throw new LockedException("该账号已被锁定!");
......@@ -84,4 +113,6 @@ public class MemberUserDetailsServiceImpl implements UserDetailsService {
}
return userDetails;
}
}
......@@ -20,7 +20,7 @@ import org.springframework.stereotype.Service;
@Service("sysUserDetailsService")
@Slf4j
@RequiredArgsConstructor
public class SysUserDetailsServiceImpl implements UserDetailsService {
public class SysUserDetailsService implements UserDetailsService {
private final UserFeignClient userFeignClient;
......
package com.youlai.auth.util;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.endpoint.PkceParameterNames;
import org.springframework.util.Assert;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* @author haoxr
* @see org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2EndpointUtils
* @since 2023/6/8
*/
public class OAuth2EndpointUtils {
static final String ACCESS_TOKEN_REQUEST_ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-5.2";
public OAuth2EndpointUtils() {
}
public static MultiValueMap<String, String> getParameters(HttpServletRequest request) {
Map<String, String[]> parameterMap = request.getParameterMap();
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>(parameterMap.size());
parameterMap.forEach((key, values) -> {
if (values.length > 0) {
for (String value : values) {
parameters.add(key, value);
}
}
});
return parameters;
}
static Map<String, Object> getParametersIfMatchesAuthorizationCodeGrantRequest(HttpServletRequest request, String... exclusions) {
if (!matchesAuthorizationCodeGrantRequest(request)) {
return Collections.emptyMap();
}
Map<String, Object> parameters = new HashMap<>(getParameters(request).toSingleValueMap());
for (String exclusion : exclusions) {
parameters.remove(exclusion);
}
return parameters;
}
static boolean matchesAuthorizationCodeGrantRequest(HttpServletRequest request) {
return AuthorizationGrantType.AUTHORIZATION_CODE.getValue().equals(
request.getParameter(OAuth2ParameterNames.GRANT_TYPE)) &&
request.getParameter(OAuth2ParameterNames.CODE) != null;
}
static boolean matchesPkceTokenRequest(HttpServletRequest request) {
return matchesAuthorizationCodeGrantRequest(request) &&
request.getParameter(PkceParameterNames.CODE_VERIFIER) != null;
}
public static void throwError(String errorCode, String parameterName, String errorUri) {
OAuth2Error error = new OAuth2Error(errorCode, "OAuth 2.0 Parameter: " + parameterName, errorUri);
throw new OAuth2AuthenticationException(error);
}
static String normalizeUserCode(String userCode) {
Assert.hasText(userCode, "userCode cannot be empty");
StringBuilder sb = new StringBuilder(userCode.toUpperCase().replaceAll("[^A-Z\\d]+", ""));
Assert.isTrue(sb.length() == 8, "userCode must be exactly 8 alpha/numeric characters");
sb.insert(4, '-');
return sb.toString();
}
}
package com.youlai.common.result;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import java.io.Serializable;
......
......@@ -12,8 +12,8 @@
<artifactId>common-file</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
<dependencies>
......
......@@ -12,8 +12,8 @@
<artifactId>common-log</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
</project>
\ No newline at end of file
......@@ -12,8 +12,8 @@
<artifactId>common-seata</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
......@@ -23,15 +23,10 @@
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<exclusions>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>${seata.version}</version>
</dependency>
</dependencies>
</project>
\ No newline at end of file
......@@ -12,8 +12,8 @@
<artifactId>common-security</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
<dependencies>
......
......@@ -12,8 +12,8 @@
<artifactId>common-sms</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
<dependencies>
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册