您的位置:首页 > 新闻 > 会展 > 基于Spring Security OAuth2认证中心授权模式扩展

基于Spring Security OAuth2认证中心授权模式扩展

2025/4/24 5:06:41 来源:https://blog.csdn.net/danfeng827/article/details/142214836  浏览:    关键词:基于Spring Security OAuth2认证中心授权模式扩展

介绍

Spring Security OAuth2 默认实现的四种授权模式在实际的应用场景中往往满足不了预期。 需要扩展如下需求:

  • 手机号+短信验证码登陆
  • 微信授权登录

本次主要通过继承Spring Security OAuth2 抽象类和接口,来实现对oauth2/token接口的手机号+短信的认证授权。

代码

TechStack/springoatuh2

开发环境

  • JDK 17
  • Spring Boot 3

核心概念和流程

  • SecurityFilterChain: 表示Spring Security的过滤器链。实现安全配置和认证扩展配置
  • RegisteredClientRepository: 表示自定义的授权客户端信息,需要进行配置。这个客户端信息是oauth2/token中需要进行认证的信息。
  • AbstractAuthenticationToken: 表示用户认证信息。 需要对其进行扩展
  • AuthenticationProvider: 验证登录信息,实现token的生成。需要对其进行扩展
  • AuthenticationConverter: 实现对AbstractAuthenticationToken自定义扩展类的转换。

主要流程就是,实现上述AbstractAuthenticationToken、AuthenticationProvider、AuthenticationConverter三个抽象类和接口的扩展。并通过实现AuthenticationSuccessHandler扩展类,用来返回token给http response中。

AuthorizationServerConfig.java

Bean@Order(Ordered.HIGHEST_PRECEDENCE)public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);http.getConfigurer(OAuth2AuthorizationServerConfigurer.class).tokenEndpoint(tokenEndpoint -> tokenEndpoint// 自定义授权模式转换器.accessTokenRequestConverter(new MobilePhoneAuthenticationConverter()).accessTokenRequestConverter(new UsernamePasswordGrantAuthenticationConverter())// 自定义授权响应.accessTokenResponseHandler(new CustomizerAuthenticationSuccessHandler()).errorResponseHandler(new CustomizerAuthenticationFailureHandler())).oidc(Customizer.withDefaults());http.exceptionHandling((exceptions) -> exceptions.authenticationEntryPoint((request, response, authException) -> {response.setStatus(HttpStatus.UNAUTHORIZED.value());response.setContentType(MediaType.APPLICATION_JSON_VALUE);OAuth2Error error = new OAuth2Error("unauthorized",authException.getMessage(),"https://tools.ietf.org/html/rfc6750#section-3.1");new ObjectMapper().writeValue(response.getOutputStream(), error);}));// 添加自定义的认证提供者http.authenticationProvider(mobilePhoneAuthenticationProvider);return http.build();}@Beanpublic RegisteredClientRepository registeredClientRepository(PasswordEncoder passwordEncoder) {RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString()).clientId("mobile-client").clientSecret(passwordEncoder.encode("secret")).clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC).authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE).authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN).authorizationGrantType(new AuthorizationGrantType("mobile_phone")) // 自定义授权类型.redirectUri("http://127.0.0.1:8080/login/oauth2/code/mobile-client").scope("message.read").scope("message.write").tokenSettings(TokenSettings.builder().accessTokenFormat(OAuth2TokenFormat.REFERENCE) // 设置访问令牌格式为 REFERENCE.build()).build();return new InMemoryRegisteredClientRepository(registeredClient);}@Beanpublic AuthorizationServerSettings authorizationServerSettings() {return AuthorizationServerSettings.builder().build();}

SecurityConfig.java

 @Bean@Order(2)public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {http.authorizeHttpRequests((authorize) -> authorize.requestMatchers("/send-sms", "/oauth2/token").permitAll().anyRequest().authenticated()).csrf((csrf) -> csrf.ignoringRequestMatchers("/send-sms", "/oauth2/token"));return http.build();}@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}

MobilePhoneAuthenticationConverter.java

public class MobilePhoneAuthenticationConverter implements AuthenticationConverter {@Overridepublic Authentication convert(HttpServletRequest request) {String grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE);if (!"mobile_phone".equals(grantType)) {return null;}String phoneNumber = request.getParameter("phone_number");String smsCode = request.getParameter("sms_code");String clientId = request.getParameter(OAuth2ParameterNames.CLIENT_ID);if (phoneNumber == null || smsCode == null || clientId == null) {throw new OAuth2AuthenticationException(new OAuth2Error("invalid_request"));}return new MobilePhoneAuthenticationToken(phoneNumber, smsCode, clientId);}
}

MobilePhoneAuthenticationProvider.java

@Component
public class MobilePhoneAuthenticationProvider implements AuthenticationProvider {@Autowiredprivate OAuth2AuthorizationService authorizationService;@Autowiredprivate OAuth2TokenGenerator<OAuth2Token> tokenGenerator;@Overridepublic Authentication authenticate(Authentication authentication) throws AuthenticationException {MobilePhoneAuthenticationToken mobilePhoneAuthentication = (MobilePhoneAuthenticationToken) authentication;// 验证手机号和验证码的逻辑...String phoneNumber = (String) mobilePhoneAuthentication.getPrincipal();String smsCode = (String) mobilePhoneAuthentication.getCredentials();// 这里应该添加实际的验证逻辑if (!"123456".equals(smsCode)) {  // 示例验证,实际应该查询数据库或缓存throw new BadCredentialsException("Invalid SMS code");}OAuth2ClientAuthenticationToken clientPrincipal =getAuthenticatedClientElseThrowInvalidClient();RegisteredClient registeredClient = clientPrincipal.getRegisteredClient();OAuth2TokenContext tokenContext = DefaultOAuth2TokenContext.builder().registeredClient(registeredClient).principal(mobilePhoneAuthentication).authorizationServerContext(AuthorizationServerContextHolder.getContext()).authorizedScopes(registeredClient.getScopes()).tokenType(OAuth2TokenType.ACCESS_TOKEN).authorizationGrantType(new AuthorizationGrantType("mobile_phone")).build();OAuth2Token generatedAccessToken = this.tokenGenerator.generate(tokenContext);if (!(generatedAccessToken instanceof OAuth2AccessToken)) {throw new OAuth2AuthenticationException(new OAuth2Error("server_error", "The token generator failed to generate the access token.", null));}OAuth2AccessToken accessToken = (OAuth2AccessToken) generatedAccessToken;OAuth2RefreshToken refreshToken = null;if (registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.REFRESH_TOKEN)) {tokenContext = DefaultOAuth2TokenContext.builder().registeredClient(registeredClient).principal(mobilePhoneAuthentication).authorizationServerContext(AuthorizationServerContextHolder.getContext()).authorizedScopes(registeredClient.getScopes()).tokenType(OAuth2TokenType.REFRESH_TOKEN).authorizationGrantType(new AuthorizationGrantType("mobile_phone")).build();OAuth2Token generatedRefreshToken = this.tokenGenerator.generate(tokenContext);if (!(generatedRefreshToken instanceof OAuth2RefreshToken)) {throw new OAuth2AuthenticationException(new OAuth2Error("server_error", "The token generator failed to generate the refresh token.", null));}refreshToken = (OAuth2RefreshToken) generatedRefreshToken;}OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(registeredClient).principalName(phoneNumber).authorizationGrantType(new AuthorizationGrantType("mobile_phone")).token(accessToken).refreshToken(refreshToken).build();this.authorizationService.save(authorization);return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, accessToken, refreshToken,  Collections.emptyMap());}@Overridepublic boolean supports(Class<?> authentication) {return MobilePhoneAuthenticationToken.class.isAssignableFrom(authentication);}private OAuth2ClientAuthenticationToken getAuthenticatedClientElseThrowInvalidClient() {// 这里需要实现获取当前认证的客户端逻辑// 例如,从 SecurityContextHolder 中获取Authentication authentication = SecurityContextHolder.getContext().getAuthentication();if (authentication instanceof OAuth2ClientAuthenticationToken) {return (OAuth2ClientAuthenticationToken) authentication;}throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_CLIENT);}

MobilePhoneAuthenticationToken.java

public class MobilePhoneAuthenticationToken extends AbstractAuthenticationToken {private final String phoneNumber;private final String smsCode;private final String clientId;public MobilePhoneAuthenticationToken(String phoneNumber, String smsCode, String clientId) {super(null);this.phoneNumber = phoneNumber;this.smsCode = smsCode;this.clientId = clientId;setAuthenticated(false);}public MobilePhoneAuthenticationToken(String phoneNumber, String smsCode, String clientId, Collection<? extends GrantedAuthority> authorities) {super(authorities);this.phoneNumber = phoneNumber;this.smsCode = smsCode;this.clientId = clientId;super.setAuthenticated(true);}@Overridepublic Object getCredentials() {return this.smsCode;}@Overridepublic Object getPrincipal() {return this.phoneNumber;}public String getClientId() {return this.clientId;}
}

测试验证

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com