refactor: 优化oauth2授权扩展

This commit is contained in:
hxr 2024-03-23 16:21:23 +08:00
parent 09359627a0
commit a1c68b15dc
8 changed files with 63 additions and 70 deletions

View File

@ -14,14 +14,14 @@ import com.nimbusds.jose.proc.SecurityContext;
import com.youlai.auth.model.SysUserDetails;
import com.youlai.auth.oauth2.extension.captcha.CaptchaAuthenticationConverter;
import com.youlai.auth.oauth2.extension.captcha.CaptchaAuthenticationProvider;
import com.youlai.auth.oauth2.extension.miniapp.WxMiniAppAuthenticationConverter;
import com.youlai.auth.oauth2.extension.miniapp.WxMiniAppAuthenticationProvider;
import com.youlai.auth.oauth2.extension.miniapp.WxMiniAppAuthenticationToken;
import com.youlai.auth.oauth2.extension.wechat.WechatAuthenticationConverter;
import com.youlai.auth.oauth2.extension.wechat.WechatAuthenticationProvider;
import com.youlai.auth.oauth2.extension.wechat.WechatAuthenticationToken;
import com.youlai.auth.oauth2.extension.password.PasswordAuthenticationConverter;
import com.youlai.auth.oauth2.extension.password.PasswordAuthenticationProvider;
import com.youlai.auth.oauth2.extension.sms.SmsAuthenticationConverter;
import com.youlai.auth.oauth2.extension.sms.SmsAuthenticationProvider;
import com.youlai.auth.oauth2.extension.sms.SmsAuthenticationToken;
import com.youlai.auth.oauth2.extension.smscode.SmsCodeAuthenticationConverter;
import com.youlai.auth.oauth2.extension.smscode.SmsCodeAuthenticationProvider;
import com.youlai.auth.oauth2.extension.smscode.SmsCodeAuthenticationToken;
import com.youlai.auth.oauth2.handler.MyAuthenticationFailureHandler;
import com.youlai.auth.oauth2.handler.MyAuthenticationSuccessHandler;
import com.youlai.auth.oauth2.jackson.SysUserMixin;
@ -96,8 +96,6 @@ public class AuthorizationServerConfig {
private final CustomOidcUserInfoService customOidcUserInfoService;
private final OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer;
private static final String CUSTOM_CONSENT_PAGE_URI = "/oauth2/consent"; // 自定义授权页
private static final String CUSTOM_LOGIN_PAGE_URI = "/sso-login"; // 自定义登录页
private final StringRedisTemplate redisTemplate;
@ -119,8 +117,6 @@ public class AuthorizationServerConfig {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
.authorizationEndpoint(authorizationEndpoint -> authorizationEndpoint.consentPage(CUSTOM_CONSENT_PAGE_URI)) // 自定义授权页
// 自定义授权模式转换器(Converter)
.tokenEndpoint(tokenEndpoint -> tokenEndpoint
.accessTokenRequestConverters(
@ -130,8 +126,8 @@ public class AuthorizationServerConfig {
List.of(
new PasswordAuthenticationConverter(),
new CaptchaAuthenticationConverter(),
new WxMiniAppAuthenticationConverter(),
new SmsAuthenticationConverter()
new WechatAuthenticationConverter(),
new SmsCodeAuthenticationConverter()
)
)
)
@ -142,8 +138,8 @@ public class AuthorizationServerConfig {
List.of(
new PasswordAuthenticationProvider(authenticationManager, authorizationService, tokenGenerator),
new CaptchaAuthenticationProvider(authenticationManager, authorizationService, tokenGenerator, redisTemplate, codeGenerator),
new WxMiniAppAuthenticationProvider(authorizationService, tokenGenerator, memberDetailsService, wxMaService),
new SmsAuthenticationProvider(authorizationService, tokenGenerator, memberDetailsService, redisTemplate)
new WechatAuthenticationProvider(authorizationService, tokenGenerator, memberDetailsService, wxMaService),
new SmsCodeAuthenticationProvider(authorizationService, tokenGenerator, memberDetailsService, redisTemplate)
)
)
)
@ -161,17 +157,14 @@ public class AuthorizationServerConfig {
);
http
// 当用户未登录且尝试访问需要认证的端点时重定向至登录页面
.exceptionHandling((exceptions) -> exceptions
.defaultAuthenticationEntryPointFor(
new LoginUrlAuthenticationEntryPoint(CUSTOM_LOGIN_PAGE_URI),
new LoginUrlAuthenticationEntryPoint("/login"),
new MediaTypeRequestMatcher(MediaType.TEXT_HTML)
)
)
// Accept access tokens for User Info and/or Client Registration
.oauth2ResourceServer(oauth2ResourceServer ->
oauth2ResourceServer.jwt(Customizer.withDefaults()));
return http.build();
}
@ -374,8 +367,8 @@ public class AuthorizationServerConfig {
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.authorizationGrantType(WxMiniAppAuthenticationToken.WECHAT_MINI_APP) // 微信小程序模式
.authorizationGrantType(SmsAuthenticationToken.SMS_CODE) // 短信验证码模式
.authorizationGrantType(WechatAuthenticationToken.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)

View File

@ -1,4 +1,4 @@
package com.youlai.auth.oauth2.extension.sms;
package com.youlai.auth.oauth2.extension.smscode;
import cn.hutool.core.util.StrUtil;
import com.youlai.auth.util.OAuth2EndpointUtils;
@ -26,13 +26,13 @@ import java.util.stream.Collectors;
* @see org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationCodeAuthenticationConverter
* @since 3.0.0
*/
public class SmsAuthenticationConverter implements AuthenticationConverter {
public class SmsCodeAuthenticationConverter implements AuthenticationConverter {
@Override
public Authentication convert(HttpServletRequest request) {
// 授权类型 (必需)
String grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE);
if (!SmsAuthenticationToken.SMS_CODE.getValue().equals(grantType)) {
if (!SmsCodeAuthenticationToken.SMS_CODE.getValue().equals(grantType)) {
return null;
}
@ -57,20 +57,20 @@ public class SmsAuthenticationConverter implements AuthenticationConverter {
}
// 手机号(必需)
String mobile = parameters.getFirst(SmsParameterNames.MOBILE);
String mobile = parameters.getFirst(SmsCodeParameterNames.MOBILE);
if (StrUtil.isBlank(mobile)) {
OAuth2EndpointUtils.throwError(
OAuth2ErrorCodes.INVALID_REQUEST,
SmsParameterNames.MOBILE,
SmsCodeParameterNames.MOBILE,
OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI);
}
// 验证码(必需)
String code = parameters.getFirst(SmsParameterNames.CODE);
String code = parameters.getFirst(SmsCodeParameterNames.CODE);
if (StrUtil.isBlank(code)) {
OAuth2EndpointUtils.throwError(
OAuth2ErrorCodes.INVALID_REQUEST,
SmsParameterNames.CODE,
SmsCodeParameterNames.CODE,
OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI);
}
@ -85,7 +85,7 @@ public class SmsAuthenticationConverter implements AuthenticationConverter {
)
.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().get(0)));
return new SmsAuthenticationToken(
return new SmsCodeAuthenticationToken(
clientPrincipal,
requestedScopes,
additionalParameters

View File

@ -1,4 +1,4 @@
package com.youlai.auth.oauth2.extension.sms;
package com.youlai.auth.oauth2.extension.smscode;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
@ -34,7 +34,7 @@ import java.util.Map;
* @since 3.0.0
*/
@Slf4j
public class SmsAuthenticationProvider implements AuthenticationProvider {
public class SmsCodeAuthenticationProvider implements AuthenticationProvider {
private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-5.2";
@ -52,7 +52,7 @@ public class SmsAuthenticationProvider implements AuthenticationProvider {
* @param tokenGenerator the token generator
* @since 0.2.3
*/
public SmsAuthenticationProvider(
public SmsCodeAuthenticationProvider(
OAuth2AuthorizationService authorizationService,
OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator,
MemberDetailsService memberDetailsService,
@ -72,21 +72,21 @@ public class SmsAuthenticationProvider implements AuthenticationProvider {
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
SmsAuthenticationToken smsAuthenticationToken = (SmsAuthenticationToken) authentication;
SmsCodeAuthenticationToken smsCodeAuthenticationToken = (SmsCodeAuthenticationToken) authentication;
OAuth2ClientAuthenticationToken clientPrincipal = OAuth2AuthenticationProviderUtils
.getAuthenticatedClientElseThrowInvalidClient(smsAuthenticationToken);
.getAuthenticatedClientElseThrowInvalidClient(smsCodeAuthenticationToken);
RegisteredClient registeredClient = clientPrincipal.getRegisteredClient();
// 验证客户端是否支持授权类型(grant_type=wechat_mini_app)
if (!registeredClient.getAuthorizationGrantTypes().contains(SmsAuthenticationToken.SMS_CODE)) {
if (!registeredClient.getAuthorizationGrantTypes().contains(SmsCodeAuthenticationToken.SMS_CODE)) {
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT);
}
// 短信验证码校验
Map<String, Object> additionalParameters = smsAuthenticationToken.getAdditionalParameters();
String mobile = (String) additionalParameters.get(SmsParameterNames.MOBILE);
String code = (String) additionalParameters.get(SmsParameterNames.CODE);
Map<String, Object> additionalParameters = smsCodeAuthenticationToken.getAdditionalParameters();
String mobile = (String) additionalParameters.get(SmsCodeParameterNames.MOBILE);
String code = (String) additionalParameters.get(SmsCodeParameterNames.CODE);
if (!code.equals("666666")) { // 666666 是后门因为短信收费正式环境删除这个if
String codeKey = RedisConstants.LOGIN_SMS_CODE_PREFIX + mobile;
@ -107,8 +107,8 @@ public class SmsAuthenticationProvider implements AuthenticationProvider {
.registeredClient(registeredClient)
.principal(usernamePasswordAuthentication)
.authorizationServerContext(AuthorizationServerContextHolder.getContext())
.authorizationGrantType(SmsAuthenticationToken.SMS_CODE)
.authorizationGrant(smsAuthenticationToken);
.authorizationGrantType(SmsCodeAuthenticationToken.SMS_CODE)
.authorizationGrant(smsCodeAuthenticationToken);
// 生成访问令牌(Access Token)
OAuth2TokenContext tokenContext = tokenContextBuilder.tokenType(OAuth2TokenType.ACCESS_TOKEN).build();
@ -124,7 +124,7 @@ public class SmsAuthenticationProvider implements AuthenticationProvider {
generatedAccessToken.getExpiresAt(), tokenContext.getAuthorizedScopes());
OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.withRegisteredClient(registeredClient)
.principalName(userDetails.getUsername())
.authorizationGrantType(SmsAuthenticationToken.SMS_CODE)
.authorizationGrantType(SmsCodeAuthenticationToken.SMS_CODE)
.attribute(Principal.class.getName(), usernamePasswordAuthentication);
if (generatedAccessToken instanceof ClaimAccessor) {
authorizationBuilder.token(accessToken, (metadata) ->
@ -159,7 +159,7 @@ public class SmsAuthenticationProvider implements AuthenticationProvider {
@Override
public boolean supports(Class<?> authentication) {
return SmsAuthenticationToken.class.isAssignableFrom(authentication);
return SmsCodeAuthenticationToken.class.isAssignableFrom(authentication);
}
}

View File

@ -1,4 +1,4 @@
package com.youlai.auth.oauth2.extension.sms;
package com.youlai.auth.oauth2.extension.smscode;
import jakarta.annotation.Nullable;
import org.springframework.security.core.Authentication;
@ -14,10 +14,10 @@ import java.util.Set;
/**
* 短信验证码身份验证令牌
*
* @author haoxr
* @author Ray Hao
* @since 3.0.0
*/
public class SmsAuthenticationToken extends OAuth2AuthorizationGrantAuthenticationToken {
public class SmsCodeAuthenticationToken extends OAuth2AuthorizationGrantAuthenticationToken {
/**
* 令牌申请访问范围
@ -27,15 +27,15 @@ public class SmsAuthenticationToken extends OAuth2AuthorizationGrantAuthenticati
/**
* 授权类型(短信验证码: sms_code)
*/
public static final AuthorizationGrantType SMS_CODE = new AuthorizationGrantType("sms");
public static final AuthorizationGrantType SMS_CODE = new AuthorizationGrantType("sms_code");
protected SmsAuthenticationToken(
protected SmsCodeAuthenticationToken(
Authentication clientPrincipal,
Set<String> scopes,
@Nullable Map<String, Object> additionalParameters
) {
super(SmsAuthenticationToken.SMS_CODE, clientPrincipal, additionalParameters);
super(SmsCodeAuthenticationToken.SMS_CODE, clientPrincipal, additionalParameters);
this.scopes = Collections.unmodifiableSet(scopes != null ? new HashSet<>(scopes) : Collections.emptySet());
}

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package com.youlai.auth.oauth2.extension.sms;
package com.youlai.auth.oauth2.extension.smscode;
/**
* 短信验证码模式参数名称常量
@ -22,7 +22,7 @@ package com.youlai.auth.oauth2.extension.sms;
* @author haoxr
* @since 3.0.0
*/
public final class SmsParameterNames {
public final class SmsCodeParameterNames {
/**
* 手机号
@ -35,7 +35,7 @@ public final class SmsParameterNames {
public static final String CODE = "code";
private SmsParameterNames() {
private SmsCodeParameterNames() {
}
}

View File

@ -1,4 +1,4 @@
package com.youlai.auth.oauth2.extension.miniapp;
package com.youlai.auth.oauth2.extension.wechat;
import cn.hutool.core.util.StrUtil;
import com.youlai.auth.util.OAuth2EndpointUtils;
@ -26,7 +26,7 @@ import java.util.stream.Collectors;
* @see org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationCodeAuthenticationConverter
* @since 3.0.0
*/
public class WxMiniAppAuthenticationConverter implements AuthenticationConverter {
public class WechatAuthenticationConverter implements AuthenticationConverter {
public static final String ACCESS_TOKEN_REQUEST_ERROR_URI = "https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/login/auth.code2Session.html";
@ -34,7 +34,7 @@ public class WxMiniAppAuthenticationConverter implements AuthenticationConverter
public Authentication convert(HttpServletRequest request) {
// 授权类型 (必需)
String grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE);
if (!WxMiniAppAuthenticationToken.WECHAT_MINI_APP.getValue().equals(grantType)) {
if (!WechatAuthenticationToken.WECHAT_MINI_APP.getValue().equals(grantType)) {
return null;
}
@ -76,7 +76,7 @@ public class WxMiniAppAuthenticationConverter implements AuthenticationConverter
)
.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().get(0)));
return new WxMiniAppAuthenticationToken(
return new WechatAuthenticationToken(
clientPrincipal,
requestedScopes,
additionalParameters

View File

@ -1,4 +1,4 @@
package com.youlai.auth.oauth2.extension.miniapp;
package com.youlai.auth.oauth2.extension.wechat;
import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
@ -35,7 +35,7 @@ import java.util.Map;
* @since 3.0.0
*/
@Slf4j
public class WxMiniAppAuthenticationProvider implements AuthenticationProvider {
public class WechatAuthenticationProvider implements AuthenticationProvider {
private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-5.2";
@ -54,7 +54,7 @@ public class WxMiniAppAuthenticationProvider implements AuthenticationProvider {
* @param tokenGenerator the token generator
* @since 0.2.3
*/
public WxMiniAppAuthenticationProvider(
public WechatAuthenticationProvider(
OAuth2AuthorizationService authorizationService,
OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator,
MemberDetailsService memberDetailsService,
@ -74,19 +74,19 @@ public class WxMiniAppAuthenticationProvider implements AuthenticationProvider {
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
WxMiniAppAuthenticationToken wxMiniAppAuthenticationToken = (WxMiniAppAuthenticationToken) authentication;
WechatAuthenticationToken wechatAuthenticationToken = (WechatAuthenticationToken) authentication;
OAuth2ClientAuthenticationToken clientPrincipal = OAuth2AuthenticationProviderUtils
.getAuthenticatedClientElseThrowInvalidClient(wxMiniAppAuthenticationToken);
.getAuthenticatedClientElseThrowInvalidClient(wechatAuthenticationToken);
RegisteredClient registeredClient = clientPrincipal.getRegisteredClient();
// 验证客户端是否支持授权类型(grant_type=wechat_mini_app)
if (!registeredClient.getAuthorizationGrantTypes().contains(WxMiniAppAuthenticationToken.WECHAT_MINI_APP)) {
if (!registeredClient.getAuthorizationGrantTypes().contains(WechatAuthenticationToken.WECHAT_MINI_APP)) {
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT);
}
// 微信 code 获取 openid
Map<String, Object> additionalParameters = wxMiniAppAuthenticationToken.getAdditionalParameters();
Map<String, Object> additionalParameters = wechatAuthenticationToken.getAdditionalParameters();
String code = (String) additionalParameters.get(OAuth2ParameterNames.CODE);
WxMaJscode2SessionResult sessionInfo;
try {
@ -106,8 +106,8 @@ public class WxMiniAppAuthenticationProvider implements AuthenticationProvider {
.registeredClient(registeredClient)
.principal(usernamePasswordAuthentication)
.authorizationServerContext(AuthorizationServerContextHolder.getContext())
.authorizationGrantType(WxMiniAppAuthenticationToken.WECHAT_MINI_APP)
.authorizationGrant(wxMiniAppAuthenticationToken);
.authorizationGrantType(WechatAuthenticationToken.WECHAT_MINI_APP)
.authorizationGrant(wechatAuthenticationToken);
// 生成访问令牌(Access Token)
OAuth2TokenContext tokenContext = tokenContextBuilder.tokenType(OAuth2TokenType.ACCESS_TOKEN).build();
@ -123,7 +123,7 @@ public class WxMiniAppAuthenticationProvider implements AuthenticationProvider {
generatedAccessToken.getExpiresAt(), tokenContext.getAuthorizedScopes());
OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.withRegisteredClient(registeredClient)
.principalName(userDetails.getUsername())
.authorizationGrantType(WxMiniAppAuthenticationToken.WECHAT_MINI_APP)
.authorizationGrantType(WechatAuthenticationToken.WECHAT_MINI_APP)
.attribute(Principal.class.getName(), usernamePasswordAuthentication);
if (generatedAccessToken instanceof ClaimAccessor) {
authorizationBuilder.token(accessToken, (metadata) ->
@ -157,7 +157,7 @@ public class WxMiniAppAuthenticationProvider implements AuthenticationProvider {
@Override
public boolean supports(Class<?> authentication) {
return WxMiniAppAuthenticationToken.class.isAssignableFrom(authentication);
return WechatAuthenticationToken.class.isAssignableFrom(authentication);
}
}

View File

@ -1,4 +1,4 @@
package com.youlai.auth.oauth2.extension.miniapp;
package com.youlai.auth.oauth2.extension.wechat;
import jakarta.annotation.Nullable;
import org.springframework.security.core.Authentication;
@ -18,7 +18,7 @@ import java.util.Set;
* @see OAuth2AuthorizationGrantAuthenticationToken
* @since 3.0.0
*/
public class WxMiniAppAuthenticationToken extends OAuth2AuthorizationGrantAuthenticationToken {
public class WechatAuthenticationToken extends OAuth2AuthorizationGrantAuthenticationToken {
/**
* 令牌申请访问范围
@ -28,15 +28,15 @@ public class WxMiniAppAuthenticationToken extends OAuth2AuthorizationGrantAuthen
/**
* 授权类型微信小程序
*/
public static final AuthorizationGrantType WECHAT_MINI_APP = new AuthorizationGrantType("wx_mini_app");
public static final AuthorizationGrantType WECHAT_MINI_APP = new AuthorizationGrantType("wechat");
protected WxMiniAppAuthenticationToken(
protected WechatAuthenticationToken(
Authentication clientPrincipal,
Set<String> scopes,
@Nullable Map<String, Object> additionalParameters
) {
super(WxMiniAppAuthenticationToken.WECHAT_MINI_APP, clientPrincipal, additionalParameters);
super(WechatAuthenticationToken.WECHAT_MINI_APP, clientPrincipal, additionalParameters);
this.scopes = Collections.unmodifiableSet(scopes != null ? new HashSet<>(scopes) : Collections.emptySet());
}