diff --git a/youlai-auth/src/main/java/com/youlai/auth/authentication/captcha/CaptchaAuthenticationConverter.java b/youlai-auth/src/main/java/com/youlai/auth/authentication/captcha/CaptchaAuthenticationConverter.java
new file mode 100644
index 000000000..bb50fac88
--- /dev/null
+++ b/youlai-auth/src/main/java/com/youlai/auth/authentication/captcha/CaptchaAuthenticationConverter.java
@@ -0,0 +1,115 @@
+package com.youlai.auth.authentication.captcha;
+
+import cn.hutool.core.util.StrUtil;
+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.OAuth2ErrorCodes;
+import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
+import org.springframework.security.web.authentication.AuthenticationConverter;
+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;
+
+/**
+ * 密码认证参数解析器
+ *
+ * 解析请求参数中的用户名和密码,并构建相应的身份验证(Authentication)对象
+ *
+ * @author haoxr
+ * @since 3.0.0
+ */
+public class CaptchaAuthenticationConverter implements AuthenticationConverter {
+
+ @Override
+ public Authentication convert(HttpServletRequest request) {
+ // 授权类型 (必需)
+ String grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE);
+ if (!CaptchaAuthenticationToken.CAPTCHA.getValue().equals(grantType)) {
+ return null;
+ }
+
+ // 客户端信息
+ Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication();
+
+ // 参数提取验证
+ MultiValueMap parameters = OAuth2EndpointUtils.getParameters(request);
+
+ // 令牌申请访问范围验证 (可选)
+ String scope = parameters.getFirst(OAuth2ParameterNames.SCOPE);
+ if (StringUtils.hasText(scope) &&
+ parameters.get(OAuth2ParameterNames.SCOPE).size() != 1) {
+ OAuth2EndpointUtils.throwError(
+ OAuth2ErrorCodes.INVALID_REQUEST,
+ OAuth2ParameterNames.SCOPE,
+ OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI);
+ }
+ Set requestedScopes = null;
+ if (StringUtils.hasText(scope)) {
+ requestedScopes = new HashSet<>(Arrays.asList(StringUtils.delimitedListToStringArray(scope, " ")));
+ }
+
+ // 用户名验证(必需)
+ String username = parameters.getFirst(OAuth2ParameterNames.USERNAME);
+ if (StrUtil.isBlank(username)) {
+ OAuth2EndpointUtils.throwError(
+ OAuth2ErrorCodes.INVALID_REQUEST,
+ OAuth2ParameterNames.USERNAME,
+ OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI
+ );
+ }
+
+ // 密码验证(必需)
+ String password = parameters.getFirst(OAuth2ParameterNames.PASSWORD);
+ if (StrUtil.isBlank(password)) {
+ OAuth2EndpointUtils.throwError(
+ OAuth2ErrorCodes.INVALID_REQUEST,
+ OAuth2ParameterNames.PASSWORD,
+ OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI
+ );
+ }
+
+ // 验证码(必需)
+ String verifyCode = parameters.getFirst(CaptchaParameterNames.VERIFY_CODE);
+ if (StrUtil.isBlank(verifyCode)) {
+ OAuth2EndpointUtils.throwError(
+ OAuth2ErrorCodes.INVALID_REQUEST,
+ CaptchaParameterNames.VERIFY_CODE,
+ OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI
+ );
+ }
+
+ // 验证码缓存Key(必需)
+ String verifyCodeKey = parameters.getFirst(CaptchaParameterNames.VERIFY_CODE_KEY);
+ if (StrUtil.isBlank(verifyCodeKey)) {
+ OAuth2EndpointUtils.throwError(
+ OAuth2ErrorCodes.INVALID_REQUEST,
+ CaptchaParameterNames.VERIFY_CODE_KEY,
+ OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI
+ );
+ }
+
+
+ // 附加参数(保存用户名/密码传递给 CaptchaAuthenticationProvider 用于身份认证)
+ Map 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 CaptchaAuthenticationToken(
+ clientPrincipal,
+ requestedScopes,
+ additionalParameters
+ );
+ }
+
+
+}
diff --git a/youlai-auth/src/main/java/com/youlai/auth/authentication/captcha/CaptchaAuthenticationProvider.java b/youlai-auth/src/main/java/com/youlai/auth/authentication/captcha/CaptchaAuthenticationProvider.java
new file mode 100644
index 000000000..8e8d13c29
--- /dev/null
+++ b/youlai-auth/src/main/java/com/youlai/auth/authentication/captcha/CaptchaAuthenticationProvider.java
@@ -0,0 +1,216 @@
+package com.youlai.auth.authentication.captcha;
+
+import cn.hutool.core.lang.Assert;
+import cn.hutool.core.util.StrUtil;
+import com.youlai.auth.authentication.smscode.SmsCodeParameterNames;
+import com.youlai.auth.util.OAuth2AuthenticationProviderUtils;
+import com.youlai.common.constant.SecurityConstants;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.data.redis.core.RedisTemplate;
+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.OidcIdToken;
+import org.springframework.security.oauth2.core.oidc.OidcScopes;
+import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames;
+import org.springframework.security.oauth2.jwt.Jwt;
+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
+ * @see org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeAuthenticationProvider
+ * @since 3.0.0
+ */
+@Slf4j
+public class CaptchaAuthenticationProvider implements AuthenticationProvider {
+
+
+ private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-5.2";
+
+ private static final OAuth2TokenType ID_TOKEN_TOKEN_TYPE = new OAuth2TokenType(OidcParameterNames.ID_TOKEN);
+ private final AuthenticationManager authenticationManager;
+ private final OAuth2AuthorizationService authorizationService;
+ private final OAuth2TokenGenerator extends OAuth2Token> tokenGenerator;
+ private final RedisTemplate redisTemplate;
+
+ /**
+ * 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 CaptchaAuthenticationProvider(AuthenticationManager authenticationManager,
+ OAuth2AuthorizationService authorizationService,
+ OAuth2TokenGenerator extends OAuth2Token> tokenGenerator,
+ RedisTemplate redisTemplate
+ ) {
+ Assert.notNull(authorizationService, "authorizationService cannot be null");
+ Assert.notNull(tokenGenerator, "tokenGenerator cannot be null");
+ this.authenticationManager = authenticationManager;
+ this.authorizationService = authorizationService;
+ this.tokenGenerator = tokenGenerator;
+ this.redisTemplate = redisTemplate;
+ }
+
+ @Override
+ public Authentication authenticate(Authentication authentication) throws AuthenticationException {
+
+ CaptchaAuthenticationToken captchaAuthenticationToken = (CaptchaAuthenticationToken) authentication;
+ OAuth2ClientAuthenticationToken clientPrincipal = OAuth2AuthenticationProviderUtils
+ .getAuthenticatedClientElseThrowInvalidClient(captchaAuthenticationToken);
+ RegisteredClient registeredClient = clientPrincipal.getRegisteredClient();
+
+ // 验证客户端是否支持授权类型(grant_type=password)
+ if (!registeredClient.getAuthorizationGrantTypes().contains(CaptchaAuthenticationToken.CAPTCHA)) {
+ throw new OAuth2AuthenticationException(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT);
+ }
+
+ // 证码校验
+ Map additionalParameters = captchaAuthenticationToken.getAdditionalParameters();
+ String verifyCode = (String) additionalParameters.get(CaptchaParameterNames.VERIFY_CODE);
+ String verifyCodeKey = (String) additionalParameters.get(CaptchaParameterNames.VERIFY_CODE_KEY);
+
+ String cacheCode = (String) redisTemplate.opsForValue().get(verifyCodeKey);
+ if (!StrUtil.equals(verifyCode, cacheCode)) {
+ throw new OAuth2AuthenticationException("验证码错误");
+ }
+
+ // 生成用户名密码身份验证令牌
+ String username = (String) additionalParameters.get(OAuth2ParameterNames.USERNAME);
+ String password = (String) additionalParameters.get(OAuth2ParameterNames.PASSWORD);
+ UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(username, password);
+ // 用户名密码身份验证,成功后返回带有权限的认证信息
+ Authentication usernamePasswordAuthentication = authenticationManager.authenticate(usernamePasswordAuthenticationToken);
+
+ // 验证申请访问范围(Scope)
+ Set authorizedScopes = registeredClient.getScopes();
+ Set requestedScopes = captchaAuthenticationToken.getScopes();
+ if (!CollectionUtils.isEmpty(requestedScopes)) {
+ Set unauthorizedScopes = requestedScopes.stream()
+ .filter(requestedScope -> !registeredClient.getScopes().contains(requestedScope))
+ .collect(Collectors.toSet());
+ if (!CollectionUtils.isEmpty(unauthorizedScopes)) {
+ throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_SCOPE);
+ }
+ authorizedScopes = new LinkedHashSet<>(requestedScopes);
+ }
+
+ // 访问令牌(Access Token) 构造器
+ DefaultOAuth2TokenContext.Builder tokenContextBuilder = DefaultOAuth2TokenContext.builder()
+ .registeredClient(registeredClient)
+ .principal(usernamePasswordAuthentication) // 身份验证成功的认证信息(用户名、权限等信息)
+ .authorizationServerContext(AuthorizationServerContextHolder.getContext())
+ .authorizedScopes(authorizedScopes)
+ .authorizationGrantType(CaptchaAuthenticationToken.CAPTCHA) // 授权方式
+ .authorizationGrant(captchaAuthenticationToken) // 授权具体对象
+ ;
+
+ // 生成访问令牌(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());
+
+ OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.withRegisteredClient(registeredClient)
+ .principalName(usernamePasswordAuthentication.getName())
+ .authorizationGrantType(CaptchaAuthenticationToken.CAPTCHA)
+ .authorizedScopes(authorizedScopes)
+ .attribute(Principal.class.getName(), usernamePasswordAuthentication);
+ 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);
+ }
+
+ // 生成 ID token
+ OidcIdToken idToken;
+ if (requestedScopes.contains(OidcScopes.OPENID)) {
+ // @formatter:off
+ tokenContext = tokenContextBuilder
+ .tokenType(ID_TOKEN_TOKEN_TYPE)
+ .authorization(authorizationBuilder.build()) // ID token customizer may need access to the access token and/or refresh token
+ .build();
+ // @formatter:on
+ OAuth2Token generatedIdToken = this.tokenGenerator.generate(tokenContext);
+ if (!(generatedIdToken instanceof Jwt)) {
+ OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR,
+ "The token generator failed to generate the ID token.", ERROR_URI);
+ throw new OAuth2AuthenticationException(error);
+ }
+
+ idToken = new OidcIdToken(generatedIdToken.getTokenValue(), generatedIdToken.getIssuedAt(),
+ generatedIdToken.getExpiresAt(), ((Jwt) generatedIdToken).getClaims());
+ authorizationBuilder.token(idToken, (metadata) ->
+ metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, idToken.getClaims()));
+ } else {
+ idToken = null;
+ }
+
+ OAuth2Authorization authorization = authorizationBuilder.build();
+
+ this.authorizationService.save(authorization);
+
+ additionalParameters = Collections.emptyMap();
+ if (idToken != null) {
+ additionalParameters = new HashMap<>();
+ additionalParameters.put(OidcParameterNames.ID_TOKEN, idToken.getTokenValue());
+ }
+ return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, accessToken, refreshToken, additionalParameters);
+ }
+
+ @Override
+ public boolean supports(Class> authentication) {
+ return CaptchaAuthenticationToken.class.isAssignableFrom(authentication);
+ }
+
+}
diff --git a/youlai-auth/src/main/java/com/youlai/auth/authentication/captcha/CaptchaAuthenticationToken.java b/youlai-auth/src/main/java/com/youlai/auth/authentication/captcha/CaptchaAuthenticationToken.java
new file mode 100644
index 000000000..914e8a147
--- /dev/null
+++ b/youlai-auth/src/main/java/com/youlai/auth/authentication/captcha/CaptchaAuthenticationToken.java
@@ -0,0 +1,62 @@
+package com.youlai.auth.authentication.captcha;
+
+import jakarta.annotation.Nullable;
+import org.springframework.security.core.Authentication;
+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 java.util.Collections;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * 验证码模式身份验证令牌(包含用户名、密码、验证码)
+ *
+ * @author haoxr
+ * @see org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientCredentialsAuthenticationToken
+ * @since 3.0.0
+ */
+public class CaptchaAuthenticationToken extends OAuth2AuthorizationGrantAuthenticationToken {
+
+ /**
+ * 令牌申请访问范围
+ */
+ private final Set scopes;
+
+ /**
+ * 授权类型(验证码: captcha)
+ */
+ public static final AuthorizationGrantType CAPTCHA = new AuthorizationGrantType("captcha");
+
+
+ /**
+ * 验证码模式身份验证令牌
+ *
+ * @param clientPrincipal 客户端信息
+ * @param scopes 令牌申请访问范围
+ * @param additionalParameters 自定义额外参数(用户名、密码、验证码)
+ */
+ public CaptchaAuthenticationToken(
+ Authentication clientPrincipal,
+ Set scopes,
+ @Nullable Map additionalParameters
+ ) {
+ super(CAPTCHA, clientPrincipal, additionalParameters);
+ this.scopes = Collections.unmodifiableSet(scopes != null ? new HashSet<>(scopes) : Collections.emptySet());
+
+ }
+
+ /**
+ * 用户凭证(密码)
+ */
+ @Override
+ public Object getCredentials() {
+ return this.getAdditionalParameters().get(OAuth2ParameterNames.PASSWORD);
+ }
+
+ public Set getScopes() {
+ return scopes;
+ }
+}
diff --git a/youlai-auth/src/main/java/com/youlai/auth/authentication/captcha/CaptchaParameterNames.java b/youlai-auth/src/main/java/com/youlai/auth/authentication/captcha/CaptchaParameterNames.java
new file mode 100644
index 000000000..de76d729d
--- /dev/null
+++ b/youlai-auth/src/main/java/com/youlai/auth/authentication/captcha/CaptchaParameterNames.java
@@ -0,0 +1,26 @@
+
+
+package com.youlai.auth.authentication.captcha;
+
+/**
+ * 验证码模式请求参数名称常量
+ *
+ * @author haoxr
+ * @since 3.0.0
+ */
+public final class CaptchaParameterNames {
+ /**
+ * 验证码
+ */
+ public static final String VERIFY_CODE = "verifyCode";
+
+ /**
+ * 验证码缓存Key
+ */
+ public static final String VERIFY_CODE_KEY = "verifyCodeKey";
+
+
+ private CaptchaParameterNames() {
+ }
+
+}
diff --git a/youlai-auth/src/main/java/com/youlai/auth/authentication/password/ResourceOwnerPasswordAuthenticationConverter.java b/youlai-auth/src/main/java/com/youlai/auth/authentication/password/PasswordAuthenticationConverter.java
similarity index 92%
rename from youlai-auth/src/main/java/com/youlai/auth/authentication/password/ResourceOwnerPasswordAuthenticationConverter.java
rename to youlai-auth/src/main/java/com/youlai/auth/authentication/password/PasswordAuthenticationConverter.java
index 3cf6cb392..7acf99857 100644
--- a/youlai-auth/src/main/java/com/youlai/auth/authentication/password/ResourceOwnerPasswordAuthenticationConverter.java
+++ b/youlai-auth/src/main/java/com/youlai/auth/authentication/password/PasswordAuthenticationConverter.java
@@ -20,7 +20,7 @@ import java.util.Set;
import java.util.stream.Collectors;
/**
- * 密码认证参数解析器
+ * 密码模式参数解析器
*
* 解析请求参数中的用户名和密码,并构建相应的身份验证(Authentication)对象
*
@@ -28,7 +28,7 @@ import java.util.stream.Collectors;
* @see org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationCodeAuthenticationConverter
* @since 3.0.0
*/
-public class ResourceOwnerPasswordAuthenticationConverter implements AuthenticationConverter {
+public class PasswordAuthenticationConverter implements AuthenticationConverter {
@Override
public Authentication convert(HttpServletRequest request) {
@@ -78,7 +78,7 @@ public class ResourceOwnerPasswordAuthenticationConverter implements Authenticat
);
}
- // 附加参数(保存用户名/密码传递给 ResourceOwnerPasswordAuthenticationProvider 用于身份认证)
+ // 附加参数(保存用户名/密码传递给 PasswordAuthenticationProvider 用于身份认证)
Map additionalParameters = parameters
.entrySet()
.stream()
@@ -86,7 +86,7 @@ public class ResourceOwnerPasswordAuthenticationConverter implements Authenticat
!e.getKey().equals(OAuth2ParameterNames.SCOPE)
).collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().get(0)));
- return new ResourceOwnerPasswordAuthenticationToken(
+ return new PasswordAuthenticationToken(
clientPrincipal,
requestedScopes,
additionalParameters
diff --git a/youlai-auth/src/main/java/com/youlai/auth/authentication/password/ResourceOwnerPasswordAuthenticationProvider.java b/youlai-auth/src/main/java/com/youlai/auth/authentication/password/PasswordAuthenticationProvider.java
similarity index 92%
rename from youlai-auth/src/main/java/com/youlai/auth/authentication/password/ResourceOwnerPasswordAuthenticationProvider.java
rename to youlai-auth/src/main/java/com/youlai/auth/authentication/password/PasswordAuthenticationProvider.java
index f0c660ad3..f1c7f57e7 100644
--- a/youlai-auth/src/main/java/com/youlai/auth/authentication/password/ResourceOwnerPasswordAuthenticationProvider.java
+++ b/youlai-auth/src/main/java/com/youlai/auth/authentication/password/PasswordAuthenticationProvider.java
@@ -1,5 +1,6 @@
package com.youlai.auth.authentication.password;
+
import cn.hutool.core.lang.Assert;
import com.youlai.auth.util.OAuth2AuthenticationProviderUtils;
import lombok.extern.slf4j.Slf4j;
@@ -36,11 +37,10 @@ import java.util.stream.Collectors;
* 处理基于用户名和密码的身份验证
*
* @author haoxr
- * @see org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeAuthenticationProvider
* @since 3.0.0
*/
@Slf4j
-public class ResourceOwnerPasswordAuthenticationProvider implements AuthenticationProvider {
+public class PasswordAuthenticationProvider implements AuthenticationProvider {
private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-5.2";
@@ -58,9 +58,9 @@ public class ResourceOwnerPasswordAuthenticationProvider implements Authenticati
* @param tokenGenerator the token generator
* @since 0.2.3
*/
- public ResourceOwnerPasswordAuthenticationProvider(AuthenticationManager authenticationManager,
- OAuth2AuthorizationService authorizationService,
- OAuth2TokenGenerator extends OAuth2Token> tokenGenerator
+ public PasswordAuthenticationProvider(AuthenticationManager authenticationManager,
+ OAuth2AuthorizationService authorizationService,
+ OAuth2TokenGenerator extends OAuth2Token> tokenGenerator
) {
Assert.notNull(authorizationService, "authorizationService cannot be null");
Assert.notNull(tokenGenerator, "tokenGenerator cannot be null");
@@ -72,7 +72,7 @@ public class ResourceOwnerPasswordAuthenticationProvider implements Authenticati
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
- ResourceOwnerPasswordAuthenticationToken resourceOwnerPasswordAuthentication = (ResourceOwnerPasswordAuthenticationToken) authentication;
+ PasswordAuthenticationToken resourceOwnerPasswordAuthentication = (PasswordAuthenticationToken) authentication;
OAuth2ClientAuthenticationToken clientPrincipal = OAuth2AuthenticationProviderUtils
.getAuthenticatedClientElseThrowInvalidClient(resourceOwnerPasswordAuthentication);
RegisteredClient registeredClient = clientPrincipal.getRegisteredClient();
@@ -138,7 +138,7 @@ public class ResourceOwnerPasswordAuthenticationProvider implements Authenticati
authorizationBuilder.accessToken(accessToken);
}
- // 生成刷新令牌(Refresh token)
+ // 生成刷新令牌(Refresh Token)
OAuth2RefreshToken refreshToken = null;
if (registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.REFRESH_TOKEN) &&
// Do not issue refresh token to public client
@@ -194,7 +194,7 @@ public class ResourceOwnerPasswordAuthenticationProvider implements Authenticati
@Override
public boolean supports(Class> authentication) {
- return ResourceOwnerPasswordAuthenticationToken.class.isAssignableFrom(authentication);
+ return PasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
}
diff --git a/youlai-auth/src/main/java/com/youlai/auth/authentication/password/ResourceOwnerPasswordAuthenticationToken.java b/youlai-auth/src/main/java/com/youlai/auth/authentication/password/PasswordAuthenticationToken.java
similarity index 79%
rename from youlai-auth/src/main/java/com/youlai/auth/authentication/password/ResourceOwnerPasswordAuthenticationToken.java
rename to youlai-auth/src/main/java/com/youlai/auth/authentication/password/PasswordAuthenticationToken.java
index d4b3da645..c9ad27d74 100644
--- a/youlai-auth/src/main/java/com/youlai/auth/authentication/password/ResourceOwnerPasswordAuthenticationToken.java
+++ b/youlai-auth/src/main/java/com/youlai/auth/authentication/password/PasswordAuthenticationToken.java
@@ -12,10 +12,12 @@ import java.util.*;
* 密码授权模式身份验证令牌(包含用户名和密码等)
*
* @author haoxr
- * @see org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientCredentialsAuthenticationToken
* @since 3.0.0
*/
-public class ResourceOwnerPasswordAuthenticationToken extends OAuth2AuthorizationGrantAuthenticationToken {
+public class PasswordAuthenticationToken extends OAuth2AuthorizationGrantAuthenticationToken {
+
+ public static final AuthorizationGrantType PASSWORD = new AuthorizationGrantType("password");
+
/**
* 令牌申请访问范围
@@ -29,12 +31,12 @@ public class ResourceOwnerPasswordAuthenticationToken extends OAuth2Authorizatio
* @param scopes 令牌申请访问范围
* @param additionalParameters 自定义额外参数(用户名和密码)
*/
- public ResourceOwnerPasswordAuthenticationToken(
+ public PasswordAuthenticationToken(
Authentication clientPrincipal,
Set scopes,
@Nullable Map additionalParameters
) {
- super(AuthorizationGrantType.PASSWORD, clientPrincipal, additionalParameters);
+ super(PASSWORD, clientPrincipal, additionalParameters);
this.scopes = Collections.unmodifiableSet(scopes != null ? new HashSet<>(scopes) : Collections.emptySet());
}
diff --git a/youlai-auth/src/main/java/com/youlai/auth/authentication/smscode/SmsCodeAuthenticationConverter.java b/youlai-auth/src/main/java/com/youlai/auth/authentication/smscode/SmsCodeAuthenticationConverter.java
index fffc1a1d4..6dcc4f4e1 100644
--- a/youlai-auth/src/main/java/com/youlai/auth/authentication/smscode/SmsCodeAuthenticationConverter.java
+++ b/youlai-auth/src/main/java/com/youlai/auth/authentication/smscode/SmsCodeAuthenticationConverter.java
@@ -57,20 +57,20 @@ public class SmsCodeAuthenticationConverter implements AuthenticationConverter {
}
// 手机号(必需)
- String mobile = parameters.getFirst("mobile");
+ String mobile = parameters.getFirst(SmsCodeParameterNames.MOBILE);
if (StrUtil.isBlank(mobile)) {
OAuth2EndpointUtils.throwError(
OAuth2ErrorCodes.INVALID_REQUEST,
- "mobile",
+ SmsCodeParameterNames.MOBILE,
OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI);
}
// 验证码(必需)
- String verifyCode = parameters.getFirst("verifyCode");
+ String verifyCode = parameters.getFirst(SmsCodeParameterNames.VERIFY_CODE);
if (StrUtil.isBlank(verifyCode)) {
OAuth2EndpointUtils.throwError(
OAuth2ErrorCodes.INVALID_REQUEST,
- "verifyCode",
+ SmsCodeParameterNames.VERIFY_CODE,
OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI);
}
diff --git a/youlai-auth/src/main/java/com/youlai/auth/authentication/smscode/SmsCodeAuthenticationProvider.java b/youlai-auth/src/main/java/com/youlai/auth/authentication/smscode/SmsCodeAuthenticationProvider.java
index 833f28b3c..d4c62ef24 100644
--- a/youlai-auth/src/main/java/com/youlai/auth/authentication/smscode/SmsCodeAuthenticationProvider.java
+++ b/youlai-auth/src/main/java/com/youlai/auth/authentication/smscode/SmsCodeAuthenticationProvider.java
@@ -2,7 +2,7 @@ package com.youlai.auth.authentication.smscode;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
-import com.youlai.auth.userdetails.member.MobileUserDetailsService;
+import com.youlai.auth.userdetails.member.MemberDetailsService;
import com.youlai.auth.util.OAuth2AuthenticationProviderUtils;
import com.youlai.common.constant.SecurityConstants;
import lombok.extern.slf4j.Slf4j;
@@ -41,7 +41,7 @@ public class SmsCodeAuthenticationProvider implements AuthenticationProvider {
private final OAuth2AuthorizationService authorizationService;
private final OAuth2TokenGenerator extends OAuth2Token> tokenGenerator;
- private final MobileUserDetailsService mobileUserDetailsService;
+ private final MemberDetailsService memberDetailsService;
private final RedisTemplate redisTemplate;
@@ -55,17 +55,17 @@ public class SmsCodeAuthenticationProvider implements AuthenticationProvider {
public SmsCodeAuthenticationProvider(
OAuth2AuthorizationService authorizationService,
OAuth2TokenGenerator extends OAuth2Token> tokenGenerator,
- MobileUserDetailsService mobileUserDetailsService,
+ MemberDetailsService memberDetailsService,
RedisTemplate redisTemplate
) {
Assert.notNull(authorizationService, "authorizationService cannot be null");
Assert.notNull(tokenGenerator, "tokenGenerator cannot be null");
- Assert.notNull(mobileUserDetailsService, "userDetailsService cannot be null");
+ Assert.notNull(memberDetailsService, "userDetailsService cannot be null");
Assert.notNull(redisTemplate, "redisTemplate cannot be null");
this.authorizationService = authorizationService;
this.tokenGenerator = tokenGenerator;
- this.mobileUserDetailsService = mobileUserDetailsService;
+ this.memberDetailsService = memberDetailsService;
this.redisTemplate = redisTemplate;
}
@@ -83,10 +83,10 @@ public class SmsCodeAuthenticationProvider implements AuthenticationProvider {
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT);
}
- // 微信 code 获取 openid
+ // 短信验证码校验
Map additionalParameters = smsCodeAuthenticationToken.getAdditionalParameters();
- String mobile = (String) additionalParameters.get("mobile");
- String verifyCode = (String) additionalParameters.get("verifyCode");
+ String mobile = (String) additionalParameters.get(SmsCodeParameterNames.MOBILE);
+ String verifyCode = (String) additionalParameters.get(SmsCodeParameterNames.VERIFY_CODE);
if (!verifyCode.equals("666666")) { // 666666 是后门,因为短信收费,正式环境删除这个if
String codeKey = SecurityConstants.SMS_CODE_PREFIX + mobile;
@@ -98,7 +98,7 @@ public class SmsCodeAuthenticationProvider implements AuthenticationProvider {
}
// 根据手机号获取会员信息
- UserDetails userDetails = mobileUserDetailsService.loadUserByUsername(mobile);
+ UserDetails userDetails = memberDetailsService.loadUserByMobile(mobile);
Authentication usernamePasswordAuthentication = new UsernamePasswordAuthenticationToken(userDetails, null);
diff --git a/youlai-auth/src/main/java/com/youlai/auth/authentication/smscode/SmsCodeAuthenticationToken.java b/youlai-auth/src/main/java/com/youlai/auth/authentication/smscode/SmsCodeAuthenticationToken.java
index 9b271ed12..6c276af6c 100644
--- a/youlai-auth/src/main/java/com/youlai/auth/authentication/smscode/SmsCodeAuthenticationToken.java
+++ b/youlai-auth/src/main/java/com/youlai/auth/authentication/smscode/SmsCodeAuthenticationToken.java
@@ -25,7 +25,7 @@ public class SmsCodeAuthenticationToken extends OAuth2AuthorizationGrantAuthenti
private final Set scopes;
/**
- * 授权类型(短信验证码:sms_code)
+ * 授权类型(短信验证码: sms_code)
*/
public static final AuthorizationGrantType SMS_CODE = new AuthorizationGrantType("sms_code");
diff --git a/youlai-auth/src/main/java/com/youlai/auth/authentication/smscode/SmsCodeParameterNames.java b/youlai-auth/src/main/java/com/youlai/auth/authentication/smscode/SmsCodeParameterNames.java
new file mode 100644
index 000000000..ee406e1ec
--- /dev/null
+++ b/youlai-auth/src/main/java/com/youlai/auth/authentication/smscode/SmsCodeParameterNames.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2002-2023 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.youlai.auth.authentication.smscode;
+
+/**
+ * 短信验证码模式参数名称常量
+ *
+ * @author haoxr
+ * @since 3.0.0
+ */
+public final class SmsCodeParameterNames {
+
+ /**
+ * 手机号
+ */
+ public static final String MOBILE = "mobile";
+
+ /**
+ * 验证码
+ */
+ public static final String VERIFY_CODE = "verifyCode";
+
+
+ private SmsCodeParameterNames() {
+ }
+
+}
diff --git a/youlai-auth/src/main/java/com/youlai/auth/authentication/wxminiapp/WxMiniAppAuthenticationProvider.java b/youlai-auth/src/main/java/com/youlai/auth/authentication/wxminiapp/WxMiniAppAuthenticationProvider.java
index 51c21ef31..9869ef3a9 100644
--- a/youlai-auth/src/main/java/com/youlai/auth/authentication/wxminiapp/WxMiniAppAuthenticationProvider.java
+++ b/youlai-auth/src/main/java/com/youlai/auth/authentication/wxminiapp/WxMiniAppAuthenticationProvider.java
@@ -3,7 +3,7 @@ package com.youlai.auth.authentication.wxminiapp;
import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import cn.hutool.core.lang.Assert;
-import com.youlai.auth.userdetails.member.OpenidUserDetailsService;
+import com.youlai.auth.userdetails.member.MemberDetailsService;
import com.youlai.auth.util.OAuth2AuthenticationProviderUtils;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
@@ -42,7 +42,7 @@ public class WxMiniAppAuthenticationProvider implements AuthenticationProvider {
private final OAuth2AuthorizationService authorizationService;
private final OAuth2TokenGenerator extends OAuth2Token> tokenGenerator;
- private final OpenidUserDetailsService openidUserDetailsService;
+ private final MemberDetailsService memberDetailsService;
private final WxMaService wxMaService;
@@ -57,17 +57,17 @@ public class WxMiniAppAuthenticationProvider implements AuthenticationProvider {
public WxMiniAppAuthenticationProvider(
OAuth2AuthorizationService authorizationService,
OAuth2TokenGenerator extends OAuth2Token> tokenGenerator,
- OpenidUserDetailsService openidUserDetailsService,
+ MemberDetailsService memberDetailsService,
WxMaService wxMaService
) {
Assert.notNull(authorizationService, "authorizationService cannot be null");
Assert.notNull(tokenGenerator, "tokenGenerator cannot be null");
- Assert.notNull(openidUserDetailsService, "userDetailsService cannot be null");
+ Assert.notNull(memberDetailsService, "userDetailsService cannot be null");
Assert.notNull(wxMaService, "wxMaService cannot be null");
this.authorizationService = authorizationService;
this.tokenGenerator = tokenGenerator;
- this.openidUserDetailsService = openidUserDetailsService;
+ this.memberDetailsService = memberDetailsService;
this.wxMaService = wxMaService;
}
@@ -97,7 +97,7 @@ public class WxMiniAppAuthenticationProvider implements AuthenticationProvider {
}
String openid = sessionInfo.getOpenid();
// 根据 openid 获取会员信息
- UserDetails userDetails = openidUserDetailsService.loadUserByUsername(openid);
+ UserDetails userDetails = memberDetailsService.loadUserByOpenid(openid);
Authentication usernamePasswordAuthentication = new UsernamePasswordAuthenticationToken(userDetails, userDetails.getPassword());
diff --git a/youlai-auth/src/main/java/com/youlai/auth/config/AuthorizationServerConfig.java b/youlai-auth/src/main/java/com/youlai/auth/config/AuthorizationServerConfig.java
index f7b91fd74..4f99d7f69 100644
--- a/youlai-auth/src/main/java/com/youlai/auth/config/AuthorizationServerConfig.java
+++ b/youlai-auth/src/main/java/com/youlai/auth/config/AuthorizationServerConfig.java
@@ -7,15 +7,19 @@ import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
-import com.youlai.auth.authentication.password.ResourceOwnerPasswordAuthenticationConverter;
-import com.youlai.auth.authentication.password.ResourceOwnerPasswordAuthenticationProvider;
+import com.youlai.auth.authentication.captcha.CaptchaAuthenticationConverter;
+import com.youlai.auth.authentication.captcha.CaptchaAuthenticationProvider;
+import com.youlai.auth.authentication.captcha.CaptchaAuthenticationToken;
+import com.youlai.auth.authentication.password.PasswordAuthenticationConverter;
+import com.youlai.auth.authentication.password.PasswordAuthenticationProvider;
import com.youlai.auth.authentication.smscode.SmsCodeAuthenticationConverter;
import com.youlai.auth.authentication.smscode.SmsCodeAuthenticationProvider;
+import com.youlai.auth.authentication.smscode.SmsCodeAuthenticationToken;
import com.youlai.auth.authentication.wxminiapp.WxMiniAppAuthenticationConverter;
import com.youlai.auth.authentication.wxminiapp.WxMiniAppAuthenticationProvider;
-import com.youlai.auth.userdetails.member.MemberUserDetails;
-import com.youlai.auth.userdetails.member.MobileUserDetailsService;
-import com.youlai.auth.userdetails.member.OpenidUserDetailsService;
+import com.youlai.auth.authentication.wxminiapp.WxMiniAppAuthenticationToken;
+import com.youlai.auth.userdetails.member.MemberDetails;
+import com.youlai.auth.userdetails.member.MemberDetailsService;
import com.youlai.auth.userdetails.user.SysUserDetails;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
@@ -30,16 +34,21 @@ import org.springframework.security.config.annotation.authentication.configurati
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.oauth2.core.AuthorizationGrantType;
+import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
+import org.springframework.security.oauth2.core.oidc.OidcScopes;
import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames;
import org.springframework.security.oauth2.jwt.JwtClaimsSet;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
import org.springframework.security.oauth2.server.authorization.*;
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository;
+import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
+import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.security.oauth2.server.authorization.token.*;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.util.matcher.RequestMatcher;
@@ -52,14 +61,19 @@ import java.util.List;
import java.util.Optional;
import java.util.UUID;
+/**
+ * 授权服务器配置
+ *
+ * @author haoxr
+ * @since 3.0.0
+ */
@Configuration
@RequiredArgsConstructor
public class AuthorizationServerConfig {
private final WxMaService wxMaService;
- private final MobileUserDetailsService mobileUserDetailsService;
- private final OpenidUserDetailsService openidUserDetailsService;
private final RedisTemplate redisTemplate;
+ private final MemberDetailsService memberDetailsService;
/**
@@ -80,25 +94,25 @@ public class AuthorizationServerConfig {
authorizationServerConfigurer
.tokenEndpoint(tokenEndpoint ->
tokenEndpoint
- .accessTokenRequestConverters( // <1>
- authenticationConverters ->
- authenticationConverters.addAll(
- List.of(
- new ResourceOwnerPasswordAuthenticationConverter(),
- new WxMiniAppAuthenticationConverter(),
- new SmsCodeAuthenticationConverter()
- )
- )
- )
- .authenticationProviders( // <2>
- authenticationProviders ->
- authenticationProviders.addAll(
+ .accessTokenRequestConverters(authenticationConverters ->// <1>
+ authenticationConverters.addAll(
List.of(
- new ResourceOwnerPasswordAuthenticationProvider(authenticationManager, authorizationService, tokenGenerator),
- new WxMiniAppAuthenticationProvider(authorizationService, tokenGenerator, openidUserDetailsService,wxMaService),
- new SmsCodeAuthenticationProvider(authorizationService, tokenGenerator, mobileUserDetailsService,redisTemplate)
+ new PasswordAuthenticationConverter(),
+ new CaptchaAuthenticationConverter(),
+ new WxMiniAppAuthenticationConverter(),
+ new SmsCodeAuthenticationConverter()
)
- )
+ )
+ )
+ .authenticationProviders(authenticationProviders ->// <2>
+ authenticationProviders.addAll(
+ List.of(
+ new PasswordAuthenticationProvider(authenticationManager, authorizationService, tokenGenerator),
+ new CaptchaAuthenticationProvider(authenticationManager, authorizationService, tokenGenerator, redisTemplate),
+ new WxMiniAppAuthenticationProvider(authorizationService, tokenGenerator, memberDetailsService, wxMaService),
+ new SmsCodeAuthenticationProvider(authorizationService, tokenGenerator, memberDetailsService, redisTemplate)
+ )
+ )
)
);
@@ -161,9 +175,16 @@ public class AuthorizationServerConfig {
@Bean
public RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate) {
- return new JdbcRegisteredClientRepository(jdbcTemplate);
+ JdbcRegisteredClientRepository registeredClientRepository = new JdbcRegisteredClientRepository(jdbcTemplate);
+
+ // 初始化 OAuth2 客户端
+ initMallAppClient(registeredClientRepository);
+ initMallAdminClient(registeredClientRepository);
+
+ return registeredClientRepository;
}
+
@Bean
public OAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate,
RegisteredClientRepository registeredClientRepository) {
@@ -178,7 +199,6 @@ public class AuthorizationServerConfig {
}
-
@Bean
OAuth2TokenGenerator> tokenGenerator(JWKSource jwkSource) {
JwtGenerator jwtGenerator = new JwtGenerator(new NimbusJwtEncoder(jwkSource));
@@ -194,13 +214,13 @@ public class AuthorizationServerConfig {
@Bean
public OAuth2TokenCustomizer jwtCustomizer() {
return context -> {
- if (OAuth2TokenType.ACCESS_TOKEN .equals(context.getTokenType()) && context.getPrincipal() instanceof UsernamePasswordAuthenticationToken) {
+ if (OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType()) && context.getPrincipal() instanceof UsernamePasswordAuthenticationToken) {
// Customize headers/claims for access_token
Optional.ofNullable(context.getPrincipal().getPrincipal()).ifPresent(principal -> {
JwtClaimsSet.Builder claims = context.getClaims();
if (principal instanceof SysUserDetails userDetails) {
claims.claim("user_id", String.valueOf(userDetails.getUserId()));
- } else if (principal instanceof MemberUserDetails userDetails) {
+ } else if (principal instanceof MemberDetails userDetails) {
claims.claim("member_id", String.valueOf(userDetails.getId()));
}
});
@@ -211,9 +231,84 @@ public class AuthorizationServerConfig {
};
}
+
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
+ /**
+ * 初始化创建商城管理客户端
+ *
+ * @param registeredClientRepository
+ */
+ private void initMallAdminClient(JdbcRegisteredClientRepository registeredClientRepository) {
+
+ String clientId = "mall-admin";
+ String clientSecret = "123456";
+ String clientName = "商城管理客户端";
+
+ // 如果使用明文,在客户端认证的时候会自动升级加密方式(修改密码), 直接使用 bcrypt 加密避免不必要的麻烦
+ // 不开玩笑,官方ISSUE: https://github.com/spring-projects/spring-authorization-server/issues/1099
+ String encodeSecret = passwordEncoder().encode(clientSecret);
+
+ RegisteredClient registeredMallAdminClient = registeredClientRepository.findByClientId(clientId);
+ String id = registeredMallAdminClient != null ? registeredMallAdminClient.getId() : UUID.randomUUID().toString();
+
+ RegisteredClient mallAppClient = RegisteredClient.withId(id)
+ .clientId(clientId)
+ .clientSecret(encodeSecret)
+ .clientName(clientName)
+ .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
+ .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
+ .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
+ .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
+ .authorizationGrantType(AuthorizationGrantType.PASSWORD) // 密码模式
+ .authorizationGrantType(CaptchaAuthenticationToken.CAPTCHA) // 验证码模式
+ .redirectUri("http://127.0.0.1:8080/authorized")
+ .postLogoutRedirectUri("http://127.0.0.1:8080/logged-out")
+ .scope(OidcScopes.OPENID)
+ .scope(OidcScopes.PROFILE)
+ .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
+ .build();
+ registeredClientRepository.save(mallAppClient);
+ }
+
+ /**
+ * 初始化创建商城APP客户端
+ *
+ * @param registeredClientRepository
+ */
+ private void initMallAppClient(JdbcRegisteredClientRepository registeredClientRepository) {
+
+ String clientId = "mall-app";
+ String clientSecret = "123456";
+ String clientName = "商城APP客户端";
+
+ // 如果使用明文,在客户端认证的时候会自动升级加密方式,直接使用 bcrypt 加密避免不必要的麻烦
+ String encodeSecret = passwordEncoder().encode(clientSecret);
+
+ RegisteredClient registeredMallAppClient = registeredClientRepository.findByClientId(clientId);
+ String id = registeredMallAppClient != null ? registeredMallAppClient.getId() : UUID.randomUUID().toString();
+
+ RegisteredClient mallAppClient = RegisteredClient.withId(id)
+ .clientId(clientId)
+ .clientSecret(encodeSecret)
+ .clientName(clientName)
+ .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
+ .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
+ .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
+ .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
+ .authorizationGrantType(WxMiniAppAuthenticationToken.WECHAT_MINI_APP) // 微信小程序模式
+ .authorizationGrantType(SmsCodeAuthenticationToken.SMS_CODE) // 短信验证码模式
+ .redirectUri("http://127.0.0.1:8080/authorized")
+ .postLogoutRedirectUri("http://127.0.0.1:8080/logged-out")
+ .scope(OidcScopes.OPENID)
+ .scope(OidcScopes.PROFILE)
+ .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
+ .build();
+ registeredClientRepository.save(mallAppClient);
+ }
+
+
}
diff --git a/youlai-auth/src/main/java/com/youlai/auth/config/SecurityConfig.java b/youlai-auth/src/main/java/com/youlai/auth/config/SecurityConfig.java
index 1dc7c85de..7800bbdf9 100644
--- a/youlai-auth/src/main/java/com/youlai/auth/config/SecurityConfig.java
+++ b/youlai-auth/src/main/java/com/youlai/auth/config/SecurityConfig.java
@@ -10,6 +10,8 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
+import org.springframework.security.crypto.factory.PasswordEncoderFactories;
+import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import java.util.List;
diff --git a/youlai-auth/src/main/java/com/youlai/auth/userdetails/member/MemberUserDetails.java b/youlai-auth/src/main/java/com/youlai/auth/userdetails/member/MemberDetails.java
similarity index 89%
rename from youlai-auth/src/main/java/com/youlai/auth/userdetails/member/MemberUserDetails.java
rename to youlai-auth/src/main/java/com/youlai/auth/userdetails/member/MemberDetails.java
index a042ea16f..d4e579bed 100644
--- a/youlai-auth/src/main/java/com/youlai/auth/userdetails/member/MemberUserDetails.java
+++ b/youlai-auth/src/main/java/com/youlai/auth/userdetails/member/MemberDetails.java
@@ -1,6 +1,5 @@
package com.youlai.auth.userdetails.member;
-import cn.hutool.core.collection.CollectionUtil;
import com.youlai.common.constant.GlobalConstants;
import com.youlai.mall.ums.dto.MemberAuthDTO;
import lombok.Data;
@@ -9,7 +8,6 @@ import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.Collections;
-import java.util.HashSet;
/**
@@ -19,7 +17,7 @@ import java.util.HashSet;
* @since 3.0.0
*/
@Data
-public class MemberUserDetails implements UserDetails {
+public class MemberDetails implements UserDetails {
/**
* 会员ID
@@ -47,7 +45,7 @@ public class MemberUserDetails implements UserDetails {
*
* @param memAuthInfo 会员认证信息
*/
- public MemberUserDetails(MemberAuthDTO memAuthInfo) {
+ public MemberDetails(MemberAuthDTO memAuthInfo) {
this.setId(memAuthInfo.getId());
this.setUsername(memAuthInfo.getUsername());
this.setEnabled(GlobalConstants.STATUS_YES.equals(memAuthInfo.getStatus()));
diff --git a/youlai-auth/src/main/java/com/youlai/auth/userdetails/member/OpenidUserDetailsService.java b/youlai-auth/src/main/java/com/youlai/auth/userdetails/member/MemberDetailsService.java
similarity index 69%
rename from youlai-auth/src/main/java/com/youlai/auth/userdetails/member/OpenidUserDetailsService.java
rename to youlai-auth/src/main/java/com/youlai/auth/userdetails/member/MemberDetailsService.java
index 8d8334c3a..fab02441b 100644
--- a/youlai-auth/src/main/java/com/youlai/auth/userdetails/member/OpenidUserDetailsService.java
+++ b/youlai-auth/src/main/java/com/youlai/auth/userdetails/member/MemberDetailsService.java
@@ -12,29 +12,53 @@ import org.springframework.security.authentication.AccountExpiredException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.core.userdetails.UserDetails;
-import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
/**
- * 会员信息(openid为主体)加载实现类
+ * 商城会员用户认证服务
*
* @author haoxr
* @since 3.0.0
*/
@Service
@RequiredArgsConstructor
-public class OpenidUserDetailsService implements UserDetailsService {
+public class MemberDetailsService {
private final MemberFeignClient memberFeignClient;
+
+ /**
+ * 手机号码认证方式
+ *
+ * @param mobile 手机号
+ * @return 用户信息
+ */
+ public UserDetails loadUserByMobile(String mobile) {
+ Result result = memberFeignClient.loadUserByMobile(mobile);
+
+ MemberAuthDTO memberAuthInfo;
+ if (!(Result.isSuccess(result) && (memberAuthInfo = result.getData()) != null)) {
+ throw new UsernameNotFoundException(ResultCode.USER_NOT_EXIST.getMsg());
+ }
+ MemberDetails userDetails = new MemberDetails(memberAuthInfo);
+ if (!userDetails.isEnabled()) {
+ throw new DisabledException("该账户已被禁用!");
+ } else if (!userDetails.isAccountNonLocked()) {
+ throw new LockedException("该账号已被锁定!");
+ } else if (!userDetails.isAccountNonExpired()) {
+ throw new AccountExpiredException("该账号已过期!");
+ }
+ return userDetails;
+ }
+
/**
* 根据用户名获取用户信息
*
* @param openid 微信公众平台唯一身份标识
- * @return {@link MemberUserDetails}
+ * @return {@link MemberDetails}
*/
- public UserDetails loadUserByUsername(String openid) {
+ public UserDetails loadUserByOpenid(String openid) {
// 根据 openid 获取微信用户认证信息
Result getMemberAuthInfoResult = memberFeignClient.loadUserByOpenId(openid);
@@ -61,7 +85,7 @@ public class OpenidUserDetailsService implements UserDetailsService {
throw new UsernameNotFoundException(ResultCode.USER_NOT_EXIST.getMsg());
}
- UserDetails userDetails = new MemberUserDetails(memberAuthInfo);
+ UserDetails userDetails = new MemberDetails(memberAuthInfo);
if (!userDetails.isEnabled()) {
throw new DisabledException("该账户已被禁用!");
} else if (!userDetails.isAccountNonLocked()) {
@@ -72,5 +96,4 @@ public class OpenidUserDetailsService implements UserDetailsService {
return userDetails;
}
-
}
diff --git a/youlai-auth/src/main/java/com/youlai/auth/userdetails/member/MobileUserDetailsService.java b/youlai-auth/src/main/java/com/youlai/auth/userdetails/member/MobileUserDetailsService.java
deleted file mode 100644
index 7a6e6498e..000000000
--- a/youlai-auth/src/main/java/com/youlai/auth/userdetails/member/MobileUserDetailsService.java
+++ /dev/null
@@ -1,61 +0,0 @@
-package com.youlai.auth.userdetails.member;
-
-import cn.binarywang.wx.miniapp.api.WxMaService;
-import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
-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;
-import org.springframework.security.core.userdetails.UserDetails;
-import org.springframework.security.core.userdetails.UserDetailsService;
-import org.springframework.security.core.userdetails.UsernameNotFoundException;
-import org.springframework.stereotype.Service;
-
-/**
- * 商城会员用户认证服务
- *
- * @author haoxr
- * @since 3.0.0
- */
-@Service
-@RequiredArgsConstructor
-public class MobileUserDetailsService implements UserDetailsService {
-
- private final MemberFeignClient memberFeignClient;
-
-
- /**
- * 手机号码认证方式
- *
- * @param mobile 手机号
- * @return 用户信息
- */
- @Override
- public UserDetails loadUserByUsername(String mobile) {
- Result result = memberFeignClient.loadUserByMobile(mobile);
-
- MemberAuthDTO memberAuthInfo;
- if (!(Result.isSuccess(result) && (memberAuthInfo = result.getData()) != null)) {
- throw new UsernameNotFoundException(ResultCode.USER_NOT_EXIST.getMsg());
- }
- MemberUserDetails userDetails = new MemberUserDetails(memberAuthInfo);
- if (!userDetails.isEnabled()) {
- throw new DisabledException("该账户已被禁用!");
- } else if (!userDetails.isAccountNonLocked()) {
- throw new LockedException("该账号已被锁定!");
- } else if (!userDetails.isAccountNonExpired()) {
- throw new AccountExpiredException("该账号已过期!");
- }
- return userDetails;
- }
-
-
-}
diff --git a/youlai-auth/src/main/java/com/youlai/auth/userdetails/user/SysUserDetails.java b/youlai-auth/src/main/java/com/youlai/auth/userdetails/user/SysUserDetails.java
index b9b113002..e0319f6e2 100644
--- a/youlai-auth/src/main/java/com/youlai/auth/userdetails/user/SysUserDetails.java
+++ b/youlai-auth/src/main/java/com/youlai/auth/userdetails/user/SysUserDetails.java
@@ -14,9 +14,7 @@ import java.util.stream.Collectors;
/**
- * 系统用户信息
- *
- * 包含用户名、密码和权限
+ * 系统用户信息(包含用户名、密码和权限)
*
* 用户名和密码用于认证,认证成功之后授予权限
*
diff --git a/youlai-auth/src/main/java/com/youlai/auth/userdetails/user/SysUserDetailsService.java b/youlai-auth/src/main/java/com/youlai/auth/userdetails/user/SysUserDetailsService.java
index bf0a24a85..f7b056a65 100644
--- a/youlai-auth/src/main/java/com/youlai/auth/userdetails/user/SysUserDetailsService.java
+++ b/youlai-auth/src/main/java/com/youlai/auth/userdetails/user/SysUserDetailsService.java
@@ -19,7 +19,6 @@ import org.springframework.stereotype.Service;
* @author haoxr
* @since 3.0.0
*/
-@Primary // UserDetailsService 默认的实现,其他需要显式声明
@Service
@RequiredArgsConstructor
public class SysUserDetailsService implements UserDetailsService {
diff --git a/youlai-auth/src/main/resources/bootstrap-dev.yml b/youlai-auth/src/main/resources/bootstrap-dev.yml
index 119a4fc90..c6fce4870 100644
--- a/youlai-auth/src/main/resources/bootstrap-dev.yml
+++ b/youlai-auth/src/main/resources/bootstrap-dev.yml
@@ -2,6 +2,9 @@ server:
port: 9000
spring:
+ mvc:
+ path-match:
+ matching-strategy: ant_path_matcher
cloud:
nacos:
# 注册中心
@@ -17,5 +20,4 @@ spring:
data-id: youlai-common.yaml
refresh: true
username: nacos
- password: nacos
-
+ password: nacos
\ No newline at end of file
diff --git a/youlai-auth/src/main/resources/bootstrap-prod.yml b/youlai-auth/src/main/resources/bootstrap-prod.yml
index e6e30c513..d5ad70eec 100644
--- a/youlai-auth/src/main/resources/bootstrap-prod.yml
+++ b/youlai-auth/src/main/resources/bootstrap-prod.yml
@@ -10,13 +10,12 @@ spring:
# 注册中心
discovery:
server-addr: http://f.youlai.tech:8848
- namespace: prod-namespace-id
+ namespace: prod
# 配置中心
config:
server-addr: ${spring.cloud.nacos.discovery.server-addr}
file-extension: yaml
- namespace: prod-namespace-id
+ namespace: prod
shared-configs[0]:
data-id: youlai-common.yaml
- namespace: prod-namespace-id
refresh: true
\ No newline at end of file
diff --git a/youlai-auth/src/test/java/com/youlai/auth/authentication/CaptchaAuthenticationTests.java b/youlai-auth/src/test/java/com/youlai/auth/authentication/CaptchaAuthenticationTests.java
new file mode 100644
index 000000000..d2edc4ae4
--- /dev/null
+++ b/youlai-auth/src/test/java/com/youlai/auth/authentication/CaptchaAuthenticationTests.java
@@ -0,0 +1,48 @@
+package com.youlai.auth.authentication;
+
+
+import com.youlai.auth.authentication.captcha.CaptchaParameterNames;
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.http.HttpHeaders;
+import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
+import org.springframework.test.web.servlet.MockMvc;
+
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@SpringBootTest
+@AutoConfigureMockMvc
+@Slf4j
+public class CaptchaAuthenticationTests {
+
+ @Autowired
+ private MockMvc mvc;
+
+
+ @Test
+ void testPasswordAuthentication() throws Exception {
+ HttpHeaders headers = new HttpHeaders();
+ headers.setBasicAuth("mall-admin", "123456");
+
+ // @formatter:off
+ this.mvc.perform(post("/oauth2/token")
+ .param(OAuth2ParameterNames.GRANT_TYPE, "captcha")
+ .param(OAuth2ParameterNames.USERNAME, "admin")
+ .param(OAuth2ParameterNames.PASSWORD, "123456")
+ .param(CaptchaParameterNames.VERIFY_CODE, "123456")
+ .param(CaptchaParameterNames.VERIFY_CODE_KEY, "123456")
+ .headers(headers))
+ .andDo(print())
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.access_token").isNotEmpty());
+ // @formatter:on
+ }
+
+
+}
\ No newline at end of file
diff --git a/youlai-auth/src/test/java/com/youlai/auth/authentication/PasswordAuthenticationTests.java b/youlai-auth/src/test/java/com/youlai/auth/authentication/PasswordAuthenticationTests.java
new file mode 100644
index 000000000..8aac9bb9d
--- /dev/null
+++ b/youlai-auth/src/test/java/com/youlai/auth/authentication/PasswordAuthenticationTests.java
@@ -0,0 +1,45 @@
+package com.youlai.auth.authentication;
+
+
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.http.HttpHeaders;
+import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
+import org.springframework.test.web.servlet.MockMvc;
+
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@SpringBootTest
+@AutoConfigureMockMvc
+@Slf4j
+public class PasswordAuthenticationTests {
+
+ @Autowired
+ private MockMvc mvc;
+
+
+ @Test
+ void testPasswordAuthentication() throws Exception {
+ HttpHeaders headers = new HttpHeaders();
+ headers.setBasicAuth("mall-admin", "123456");
+
+ // @formatter:off
+ this.mvc.perform(post("/oauth2/token")
+ .param(OAuth2ParameterNames.GRANT_TYPE, "password")
+ .param(OAuth2ParameterNames.USERNAME, "admin")
+ .param(OAuth2ParameterNames.PASSWORD, "123456")
+ .headers(headers))
+ .andDo(print())
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.access_token").isNotEmpty());
+ // @formatter:on
+ }
+
+
+}
\ No newline at end of file
diff --git a/youlai-auth/src/test/java/com/youlai/auth/authentication/SmsCodeAuthenticationTests.java b/youlai-auth/src/test/java/com/youlai/auth/authentication/SmsCodeAuthenticationTests.java
new file mode 100644
index 000000000..731b9624c
--- /dev/null
+++ b/youlai-auth/src/test/java/com/youlai/auth/authentication/SmsCodeAuthenticationTests.java
@@ -0,0 +1,43 @@
+package com.youlai.auth.authentication;
+
+
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.http.HttpHeaders;
+import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
+import org.springframework.test.web.servlet.MockMvc;
+
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@SpringBootTest
+@AutoConfigureMockMvc
+@Slf4j
+public class SmsCodeAuthenticationTests {
+
+
+ @Autowired
+ private MockMvc mvc;
+
+ @Test
+ void testSmsCodeAuthentication() throws Exception {
+ HttpHeaders headers = new HttpHeaders();
+ headers.setBasicAuth("mall-app", "123456");
+
+ this.mvc.perform(post("/oauth2/token")
+ .param(OAuth2ParameterNames.GRANT_TYPE, "sms_code")
+ .param("mobile", "18866668888")
+ .param("verifyCode", "666666")
+ .headers(headers))
+ .andDo(print())
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.access_token").isNotEmpty());
+ }
+
+
+}
\ No newline at end of file
diff --git a/youlai-auth/src/test/java/com/youlai/auth/authentication/WechatMiniAppAuthenticationTests.java b/youlai-auth/src/test/java/com/youlai/auth/authentication/WechatMiniAppAuthenticationTests.java
new file mode 100644
index 000000000..daf994408
--- /dev/null
+++ b/youlai-auth/src/test/java/com/youlai/auth/authentication/WechatMiniAppAuthenticationTests.java
@@ -0,0 +1,43 @@
+package com.youlai.auth.authentication;
+
+
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.http.HttpHeaders;
+import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
+import org.springframework.test.web.servlet.MockMvc;
+
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@SpringBootTest
+@AutoConfigureMockMvc
+@Slf4j
+public class WechatMiniAppAuthenticationTests {
+
+ @Autowired
+ private MockMvc mvc;
+
+ @Test
+ void testWechatMiniAppPasswordAuthentication() throws Exception {
+ HttpHeaders headers = new HttpHeaders();
+ headers.setBasicAuth("mall-app", "123456");
+
+ // @formatter:off
+ this.mvc.perform(post("/oauth2/token")
+ .param(OAuth2ParameterNames.GRANT_TYPE, "wechat_mini_app")
+ .param(OAuth2ParameterNames.CODE, "codeVal")
+ .headers(headers))
+ .andDo(print())
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.access_token").isNotEmpty());
+ // @formatter:on
+ }
+
+
+}
\ No newline at end of file
diff --git a/youlai-auth/src/test/java/com/youlai/auth/security/authentication/password/ResourceOwnerPasswordAuthenticationTests.java b/youlai-auth/src/test/java/com/youlai/auth/security/authentication/password/ResourceOwnerPasswordAuthenticationTests.java
deleted file mode 100644
index c156bbf4d..000000000
--- a/youlai-auth/src/test/java/com/youlai/auth/security/authentication/password/ResourceOwnerPasswordAuthenticationTests.java
+++ /dev/null
@@ -1,99 +0,0 @@
-package com.youlai.auth.security.authentication.password;
-
-
-import com.youlai.auth.authentication.wxminiapp.WxMiniAppAuthenticationToken;
-import lombok.extern.slf4j.Slf4j;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.http.HttpHeaders;
-import org.springframework.security.crypto.password.PasswordEncoder;
-import org.springframework.security.oauth2.core.AuthorizationGrantType;
-import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
-import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
-import org.springframework.security.oauth2.core.oidc.OidcScopes;
-import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
-import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
-import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
-import org.springframework.test.web.servlet.MockMvc;
-
-import java.util.UUID;
-
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
-import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
-
-@SpringBootTest
-@AutoConfigureMockMvc
-@Slf4j
-public class ResourceOwnerPasswordAuthenticationTests {
-
-
- private final String clientId = "mall-app";
- private final String clientSecret = "secret";
-
-
- @Autowired
- private RegisteredClientRepository registeredClientRepository;
-
- @Autowired
- private MockMvc mvc;
-
- @Autowired
- private PasswordEncoder passwordEncoder;
-
- @BeforeEach
- public void setUp() {
- // 注册 mall-app 客户端
-
- //
- String encodeSecret = passwordEncoder.encode(clientSecret);
- RegisteredClient messagingClient = RegisteredClient.withId(UUID.randomUUID().toString())
- .clientId(clientId)
- .clientSecret(encodeSecret)
- .clientName(clientId)
- .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
- .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
- .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
- .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
- .authorizationGrantType(AuthorizationGrantType.PASSWORD)
- .authorizationGrantType(WxMiniAppAuthenticationToken.WECHAT_MINI_APP)
- .redirectUri("http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc")
- .redirectUri("http://127.0.0.1:8080/authorized")
- .postLogoutRedirectUri("http://127.0.0.1:8080/logged-out")
- .scope(OidcScopes.OPENID)
- .scope(OidcScopes.PROFILE)
- .scope("message.read")
- .scope("message.write")
- .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
- .build();
-
- RegisteredClient registeredMessagingClient = registeredClientRepository.findByClientId(clientId);
- if (registeredMessagingClient == null) {
- registeredClientRepository.save(messagingClient);
- }
- }
-
-
- @Test
- void testLoginApiForOAuth2PasswordMode() throws Exception {
- HttpHeaders headers = new HttpHeaders();
- headers.setBasicAuth("mall-app", "secret");
-
- // @formatter:off
- this.mvc.perform(post("/oauth2/token")
- .param(OAuth2ParameterNames.GRANT_TYPE, "password")
- .param(OAuth2ParameterNames.USERNAME, "admin")
- .param(OAuth2ParameterNames.PASSWORD, "123456")
- .headers(headers))
- .andDo(print())
- .andExpect(status().isOk())
- .andExpect(jsonPath("$.access_token").isNotEmpty());
- // @formatter:on
- }
-
-
-}
\ No newline at end of file
diff --git a/youlai-auth/src/test/java/com/youlai/auth/security/authentication/password/SmsCodeAuthenticationTests.java b/youlai-auth/src/test/java/com/youlai/auth/security/authentication/password/SmsCodeAuthenticationTests.java
deleted file mode 100644
index 901c0c880..000000000
--- a/youlai-auth/src/test/java/com/youlai/auth/security/authentication/password/SmsCodeAuthenticationTests.java
+++ /dev/null
@@ -1,100 +0,0 @@
-package com.youlai.auth.security.authentication.password;
-
-
-import com.youlai.auth.authentication.smscode.SmsCodeAuthenticationToken;
-import com.youlai.auth.authentication.wxminiapp.WxMiniAppAuthenticationToken;
-import lombok.extern.slf4j.Slf4j;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.http.HttpHeaders;
-import org.springframework.security.crypto.password.PasswordEncoder;
-import org.springframework.security.oauth2.core.AuthorizationGrantType;
-import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
-import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
-import org.springframework.security.oauth2.core.oidc.OidcScopes;
-import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
-import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
-import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
-import org.springframework.test.web.servlet.MockMvc;
-
-import java.util.UUID;
-
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
-import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
-
-@SpringBootTest
-@AutoConfigureMockMvc
-@Slf4j
-public class SmsCodeAuthenticationTests {
-
-
- private final String clientId = "mall-app";
- private final String clientSecret = "secret";
-
-
- @Autowired
- private RegisteredClientRepository registeredClientRepository;
-
- @Autowired
- private MockMvc mvc;
-
- @Autowired
- private PasswordEncoder passwordEncoder;
-
- @BeforeEach
- public void setUp() {
- // 注册 mall-app 客户端
-
- String encodeSecret = passwordEncoder.encode(clientSecret);
- RegisteredClient messagingClient = RegisteredClient.withId(UUID.randomUUID().toString())
- .clientId(clientId)
- .clientSecret(encodeSecret)
- .clientName(clientId)
- .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
- .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
- .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
- .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
- .authorizationGrantType(AuthorizationGrantType.PASSWORD)
- .authorizationGrantType(WxMiniAppAuthenticationToken.WECHAT_MINI_APP)
- .authorizationGrantType(SmsCodeAuthenticationToken.SMS_CODE)
- .redirectUri("http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc")
- .redirectUri("http://127.0.0.1:8080/authorized")
- .postLogoutRedirectUri("http://127.0.0.1:8080/logged-out")
- .scope(OidcScopes.OPENID)
- .scope(OidcScopes.PROFILE)
- .scope("message.read")
- .scope("message.write")
- .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
- .build();
-
- RegisteredClient registeredMessagingClient = registeredClientRepository.findByClientId(clientId);
- if (registeredMessagingClient == null) {
- registeredClientRepository.save(messagingClient);
- }
- }
-
-
- @Test
- void testSmsCodeMode() throws Exception {
- HttpHeaders headers = new HttpHeaders();
- headers.setBasicAuth("mall-app", "secret");
-
- // @formatter:off
- this.mvc.perform(post("/oauth2/token")
- .param(OAuth2ParameterNames.GRANT_TYPE, "sms_code")
- .param("mobile", "18866668888")
- .param("verifyCode", "666666")
- .headers(headers))
- .andDo(print())
- .andExpect(status().isOk())
- .andExpect(jsonPath("$.access_token").isNotEmpty());
- // @formatter:on
- }
-
-
-}
\ No newline at end of file
diff --git a/youlai-auth/src/test/java/com/youlai/auth/security/authentication/password/WechatMiniAppAuthenticationTests.java b/youlai-auth/src/test/java/com/youlai/auth/security/authentication/password/WechatMiniAppAuthenticationTests.java
deleted file mode 100644
index bc9c112ae..000000000
--- a/youlai-auth/src/test/java/com/youlai/auth/security/authentication/password/WechatMiniAppAuthenticationTests.java
+++ /dev/null
@@ -1,97 +0,0 @@
-package com.youlai.auth.security.authentication.password;
-
-
-import com.youlai.auth.authentication.wxminiapp.WxMiniAppAuthenticationToken;
-import lombok.extern.slf4j.Slf4j;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.http.HttpHeaders;
-import org.springframework.security.crypto.password.PasswordEncoder;
-import org.springframework.security.oauth2.core.AuthorizationGrantType;
-import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
-import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
-import org.springframework.security.oauth2.core.oidc.OidcScopes;
-import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
-import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
-import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
-import org.springframework.test.web.servlet.MockMvc;
-
-import java.util.UUID;
-
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
-import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
-
-@SpringBootTest
-@AutoConfigureMockMvc
-@Slf4j
-public class WechatMiniAppAuthenticationTests {
-
-
- private final String clientId = "mall-app";
- private final String clientSecret = "secret";
-
-
- @Autowired
- private RegisteredClientRepository registeredClientRepository;
-
- @Autowired
- private MockMvc mvc;
-
- @Autowired
- private PasswordEncoder passwordEncoder;
-
- @BeforeEach
- public void setUp() {
- // 注册 mall-app 客户端
-
- String encodeSecret = passwordEncoder.encode(clientSecret);
- RegisteredClient messagingClient = RegisteredClient.withId(UUID.randomUUID().toString())
- .clientId(clientId)
- .clientSecret(encodeSecret)
- .clientName(clientId)
- .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
- .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
- .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
- .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
- .authorizationGrantType(AuthorizationGrantType.PASSWORD)
- .authorizationGrantType(WxMiniAppAuthenticationToken.WECHAT_MINI_APP)
- .redirectUri("http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc")
- .redirectUri("http://127.0.0.1:8080/authorized")
- .postLogoutRedirectUri("http://127.0.0.1:8080/logged-out")
- .scope(OidcScopes.OPENID)
- .scope(OidcScopes.PROFILE)
- .scope("message.read")
- .scope("message.write")
- .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
- .build();
-
- RegisteredClient registeredMessagingClient = registeredClientRepository.findByClientId(clientId);
- if (registeredMessagingClient == null) {
- registeredClientRepository.save(messagingClient);
- }
- }
-
-
- @Test
- void testWechatMiniAppPasswordMode() throws Exception {
- HttpHeaders headers = new HttpHeaders();
- headers.setBasicAuth("mall-app", "secret");
-
- // @formatter:off
- this.mvc.perform(post("/oauth2/token")
- .param(OAuth2ParameterNames.GRANT_TYPE, "wechat_mini_app")
- .param(OAuth2ParameterNames.CODE, "codeVal")
- .headers(headers))
- .andDo(print())
- .andExpect(status().isOk())
- .andExpect(jsonPath("$.access_token").isNotEmpty());
- // @formatter:on
- }
-
-
-}
\ No newline at end of file
diff --git a/youlai-gateway/src/main/java/com/youlai/gateway/captcha/handler/CaptchaHandler.java b/youlai-gateway/src/main/java/com/youlai/gateway/captcha/handler/CaptchaHandler.java
index 8b3f71005..02b1b4e35 100644
--- a/youlai-gateway/src/main/java/com/youlai/gateway/captcha/handler/CaptchaHandler.java
+++ b/youlai-gateway/src/main/java/com/youlai/gateway/captcha/handler/CaptchaHandler.java
@@ -65,7 +65,7 @@ public class CaptchaHandler implements HandlerFunction {
String captchaBase64 = captcha.toBase64();
Map result = new HashMap<>(2);
result.put("verifyCodeKey", uuid);
- result.put("verifyCodeImg", captchaBase64);
+ result.put("verifyCodeBase64", captchaBase64);
return ServerResponse.ok().body(BodyInserters.fromValue(Result.success(result)));
}
diff --git a/youlai-gateway/src/main/resources/bootstrap-prod.yml b/youlai-gateway/src/main/resources/bootstrap-prod.yml
index 875ee156a..781214814 100644
--- a/youlai-gateway/src/main/resources/bootstrap-prod.yml
+++ b/youlai-gateway/src/main/resources/bootstrap-prod.yml
@@ -10,12 +10,12 @@ spring:
nacos:
discovery:
server-addr: http://f.youlai.tech:8848
- namespace: prod-namespace-id
+ namespace: prod
config:
server-addr: ${spring.cloud.nacos.discovery.server-addr}
file-extension: yaml
- namespace: prod-namespace-id
+ namespace: prod
shared-configs[0]:
data-id: youlai-common.yaml
- namespace: prod-namespace-id
+ namespace: prod
refresh: true
diff --git a/youlai-system/system-boot/src/main/java/com/youlai/system/controller/SysUserController.java b/youlai-system/system-boot/src/main/java/com/youlai/system/controller/SysUserController.java
index 31b42f995..d3b9794cf 100644
--- a/youlai-system/system-boot/src/main/java/com/youlai/system/controller/SysUserController.java
+++ b/youlai-system/system-boot/src/main/java/com/youlai/system/controller/SysUserController.java
@@ -162,7 +162,8 @@ public class SysUserController {
@PostMapping("/_import")
public Result importUsers(@Parameter(description = "部门ID") Long deptId, MultipartFile file) throws IOException {
UserImportListener listener = new UserImportListener(deptId);
- String msg = importExcel(file.getInputStream(), UserImportVO.class, listener);
+ EasyExcel.read(file.getInputStream(), UserImportVO.class, listener).sheet().doRead();
+ String msg = listener.getMsg();
return Result.success(msg);
}
@@ -177,10 +178,4 @@ public class SysUserController {
EasyExcel.write(response.getOutputStream(), UserExportVO.class).sheet("用户列表")
.doWrite(exportUserList);
}
-
- public static String importExcel(InputStream is, Class clazz, MyAnalysisEventListener listener) {
- EasyExcel.read(is, clazz, listener).sheet().doRead();
- return listener.getMsg();
- }
-
}
diff --git a/youlai-system/system-boot/src/main/java/com/youlai/system/service/impl/SysUserServiceImpl.java b/youlai-system/system-boot/src/main/java/com/youlai/system/service/impl/SysUserServiceImpl.java
index 4a62b8deb..828531faf 100644
--- a/youlai-system/system-boot/src/main/java/com/youlai/system/service/impl/SysUserServiceImpl.java
+++ b/youlai-system/system-boot/src/main/java/com/youlai/system/service/impl/SysUserServiceImpl.java
@@ -21,7 +21,6 @@ import com.youlai.system.model.query.UserPageQuery;
import com.youlai.system.model.vo.UserExportVO;
import com.youlai.system.model.vo.UserInfoVO;
import com.youlai.system.model.vo.UserPageVO;
-import com.youlai.system.service.SysMenuService;
import com.youlai.system.service.SysRoleService;
import com.youlai.system.service.SysUserRoleService;
import com.youlai.system.service.SysUserService;
@@ -50,8 +49,6 @@ public class SysUserServiceImpl extends ServiceImpl impl
private final SysUserRoleService userRoleService;
- private final SysMenuService menuService;
-
private final SysRoleService roleService;
private final UserConverter userConverter;
diff --git a/youlai-system/system-boot/src/main/resources/bootstrap-prod.yml b/youlai-system/system-boot/src/main/resources/bootstrap-prod.yml
index 3e877af08..6b75c44b4 100644
--- a/youlai-system/system-boot/src/main/resources/bootstrap-prod.yml
+++ b/youlai-system/system-boot/src/main/resources/bootstrap-prod.yml
@@ -11,13 +11,13 @@ spring:
nacos:
discovery:
server-addr: http://f.youlai.tech:8848
- namespace: prod-namespace-id
+ namespace: prod
config:
server-addr: ${spring.cloud.nacos.discovery.server-addr}
file-extension: yaml
- namespace: prod-namespace-id
+ namespace: prod
shared-configs[0]:
data-id: youlai-common.yaml
- namespace: prod-namespace-id
+ namespace: prod
refresh: true