diff --git a/docs/nacos/nacos_config.zip b/docs/nacos/nacos_config.zip index 304e82a22..7b1896863 100644 Binary files a/docs/nacos/nacos_config.zip and b/docs/nacos/nacos_config.zip differ diff --git a/mall-oms/oms-boot/src/main/resources/bootstrap-prod.yml b/mall-oms/oms-boot/src/main/resources/bootstrap-prod.yml index 8a229c4b1..e6738f073 100644 --- a/mall-oms/oms-boot/src/main/resources/bootstrap-prod.yml +++ b/mall-oms/oms-boot/src/main/resources/bootstrap-prod.yml @@ -11,11 +11,11 @@ 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 refresh: true diff --git a/mall-pms/pms-boot/src/main/resources/bootstrap-prod.yml b/mall-pms/pms-boot/src/main/resources/bootstrap-prod.yml index 8c1eea5e2..ae853df88 100644 --- a/mall-pms/pms-boot/src/main/resources/bootstrap-prod.yml +++ b/mall-pms/pms-boot/src/main/resources/bootstrap-prod.yml @@ -11,11 +11,11 @@ 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 diff --git a/mall-sms/sms-boot/src/main/resources/bootstrap-prod.yml b/mall-sms/sms-boot/src/main/resources/bootstrap-prod.yml index 85f96b491..e5f6d19b6 100644 --- a/mall-sms/sms-boot/src/main/resources/bootstrap-prod.yml +++ b/mall-sms/sms-boot/src/main/resources/bootstrap-prod.yml @@ -12,12 +12,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 refresh: true \ No newline at end of file diff --git a/mall-ums/ums-boot/src/main/resources/bootstrap-prod.yml b/mall-ums/ums-boot/src/main/resources/bootstrap-prod.yml index d42e185ed..6142f7d6c 100644 --- a/mall-ums/ums-boot/src/main/resources/bootstrap-prod.yml +++ b/mall-ums/ums-boot/src/main/resources/bootstrap-prod.yml @@ -11,11 +11,11 @@ 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 refresh: true diff --git a/pom.xml b/pom.xml index 84ab02f80..635e2cd6e 100644 --- a/pom.xml +++ b/pom.xml @@ -35,14 +35,14 @@ 17 17 - 3.1.0 + 3.1.1 - 2022.0.3 + 2022.0.2 2022.0.0.0-RC2 - 1.1.0 - 9.16.1 + 1.1.0 + 9.31 8.0.28 @@ -60,7 +60,7 @@ 3.0.5 1.6.2 9.16.1 - 0.4.17 + 0.4.19 4.5.25 @@ -69,7 +69,7 @@ 8.5.3 4.8.1 - + 3.16.3 @@ -302,16 +302,10 @@ ${nimbus-jose-jwt.version} - - net.coobird - thumbnailator - ${thumbnailator.version} - - org.springframework.security spring-security-oauth2-authorization-server - ${authorization-server.version} + ${spring-authorization-server.version} diff --git a/youlai-auth/pom.xml b/youlai-auth/pom.xml index 09c31fe8e..3a682aabb 100644 --- a/youlai-auth/pom.xml +++ b/youlai-auth/pom.xml @@ -12,6 +12,11 @@ youlai-auth + + org.springframework.boot + spring-boot-starter-test + test + @@ -88,11 +93,6 @@ common-mybatis - - org.springframework.boot - spring-boot-starter-test - test - 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 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 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 tokenGenerator + public PasswordAuthenticationProvider(AuthenticationManager authenticationManager, + OAuth2AuthorizationService authorizationService, + OAuth2TokenGenerator 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 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 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 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 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