当前位置: 首页 > backend >正文

spring-security5-oauth2系列:密码授权模式

目录

示例

大体流程

配置器

OAuth2AuthorizationServerConfigurer

createConfigurers

​​​​​​​configure

过滤器

​​​​​​​OAuth2TokenEndpointFilter

​​​​​​​自定义配置

配置示例

​​​​​​​tokenEndpoint

​​​​​​​自定义请求参数转换器

CustomDelegatingAuthenticationConverter

​​​​​​​PasswordGrantAuthenticationConverter

​​​​​​​PasswordRefreshTokenAuthenticationConverter

自定义认证提供者

PasswordGrantAuthenticationProvider

​​​​​​​PasswordRefreshTokenAuthenticationProvider

时序图


        本篇文章我们来研究spring-security5框架如何实现OAuth2的密码授权模式,即用户向客户端提供自己的用户名和密码,客户端使用这些信息向“服务提供商”索要授权。

        先从一个简单示例开始,如下图所示:

示例

        首先,新建一个config包用于存放spring-security通用配置;

        然后,新建一个AuthSecurityConfig类,给AuthSecurityConfig类中加上@EnableWebSecurity 注解后,这样便会自动被 Spring发现并注册。

@Configuration

@EnableWebSecurity

public class AuthSecurityConfig{

  @Bean

  @Order(1)

  public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http,                                                                   OAuth2AuthorizationService authorizationService,                                                               OAuth2TokenGenerator<?> tokenGenerator) throws Exception {

      OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer =

                new OAuth2AuthorizationServerConfigurer<>();

      authorizationServerConfigurer.tokenEndpoint(tokenEndpoint ->

        tokenEndpoint

.accessTokenRequestConverter(new CustomDelegatingAuthenticationConverter())

.authenticationProvider(new PasswordGrantAuthenticationProvider())

          .authenticationProvider(new PasswordRefreshTokenAuthenticationProvider()));

RequestMatcher endpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher();

      http.requestMatcher(endpointsMatcher)

          .apply(authorizationServerConfigurer);

      return http.build();

  }

        在这里,首先实例化一个配置器OAuth2AuthorizationServerConfigurer对象;

        然后,自定义accessTokenRequestConverter和authenticationProvider配置信息;

        最后,将该配置器对象应用到HttpSecurity 对象即可。

大体流程

        点击示例里的OAuth2AuthorizationServerConfigurer类,如下所示:

​​​​​​​配置器

OAuth2AuthorizationServerConfigurer

public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBuilder<B>>

extends AbstractHttpConfigurer<OAuth2AuthorizationServerConfigurer<B>, B> {

private final Map<Class<? extends AbstractOAuth2Configurer>, AbstractOAuth2Configurer> configurers = createConfigurers();

        在这里,调用createConfigurers()方法,创建各种相关的子配置器对象。

        点击createConfigurers()方法,如下所示:

​​​​​​​createConfigurers

private Map<Class<? extends AbstractOAuth2Configurer>, AbstractOAuth2Configurer> createConfigurers() {

Map<Class<? extends AbstractOAuth2Configurer>, AbstractOAuth2Configurer> configurers = new LinkedHashMap<>();

configurers.put(OAuth2ClientAuthenticationConfigurer.class, new OAuth2ClientAuthenticationConfigurer(this::postProcess));

configurers.put(OAuth2AuthorizationEndpointConfigurer.class, new OAuth2AuthorizationEndpointConfigurer(this::postProcess));

configurers.put(OAuth2TokenEndpointConfigurer.class, new OAuth2TokenEndpointConfigurer(this::postProcess));

configurers.put(OAuth2TokenIntrospectionEndpointConfigurer.class, new OAuth2TokenIntrospectionEndpointConfigurer(this::postProcess));

configurers.put(OAuth2TokenRevocationEndpointConfigurer.class, new OAuth2TokenRevocationEndpointConfigurer(this::postProcess));

configurers.put(OidcConfigurer.class, new OidcConfigurer(this::postProcess));

return configurers;

}

        在这里,我们看到程序创建了配置器OAuth2TokenEndpointConfigurer对象。配置器对象创建好了之后,重点要关注该对象的configure()方法。

        点击对象的configure()方法,如下所示:

​​​​​​​configure

public final class OAuth2TokenEndpointConfigurer extends AbstractOAuth2Configurer {

  ... ...

@Override

<B extends HttpSecurityBuilder<B>> void configure(B builder) {

AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class);

ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder);

OAuth2TokenEndpointFilter tokenEndpointFilter =

new OAuth2TokenEndpointFilter(

authenticationManager,

providerSettings.getTokenEndpoint());

if (this.accessTokenRequestConverter != null) {

tokenEndpointFilter.setAuthenticationConverter(this.accessTokenRequestConverter);

}

... ...

builder.addFilterAfter(postProcess(tokenEndpointFilter), FilterSecurityInterceptor.class);

}

...

}

       在这里,我们看到程序创建了过滤器OAuth2TokenEndpointFilter对象,并且传入请求参数转换器对象accessTokenRequestConverter和认证管理器对象authenticationManager。

        过滤器对象如下所示:

过滤器

​​​​​​​OAuth2TokenEndpointFilter

public final class OAuth2TokenEndpointFilter extends OncePerRequestFilter {

... ...

@Override

protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

if (!this.tokenEndpointMatcher.matches(request)) {

filterChain.doFilter(request, response);

return;

}

try {

... ...

Authentication authorizationGrantAuthentication = this.authenticationConverter.convert(request);

... ...

OAuth2AccessTokenAuthenticationToken accessTokenAuthentication =

(OAuth2AccessTokenAuthenticationToken) this.authenticationManager.authenticate(authorizationGrantAuthentication);

this.authenticationSuccessHandler.onAuthenticationSuccess(request, response, accessTokenAuthentication);

} catch (OAuth2AuthenticationException ex) {

... ...

}

}

private void sendAccessTokenResponse(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {

... ...

}

}

        在这里,我们看到整个过滤逻辑主要包括请求参数转换操作convert(request)和认证操作authenticate(authorizationGrantAuthentication)两个。

        请求参数转换操作由authenticationConverter对象来处理,该对象在实例化过滤器时传入。

        认证操作由authenticationManager对象处理,该对象在实例化过滤器时传入。

        具体实现逻辑见后续章节。

​​​​​​​自定义配置

配置示例

@Configuration

@EnableWebSecurity

public class AuthSecurityConfig{

  @Bean

  @Order(1)

  public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http,                                                                   OAuth2AuthorizationService authorizationService,                                                               OAuth2TokenGenerator<?> tokenGenerator) throws Exception {

      OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer =

                new OAuth2AuthorizationServerConfigurer<>();

      authorizationServerConfigurer.tokenEndpoint(tokenEndpoint ->

        tokenEndpoint

.accessTokenRequestConverter(new CustomDelegatingAuthenticationConverter())

.authenticationProvider(new PasswordGrantAuthenticationProvider())

          .authenticationProvider(new PasswordRefreshTokenAuthenticationProvider()));

RequestMatcher endpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher();

      http.requestMatcher(endpointsMatcher)

          .apply(authorizationServerConfigurer);

      return http.build();

  }

       在这里,通过调用tokenEndpoint()方法实现对请求参数转换器和认证提供者的自定义。传入的匿名内部类,采用链式调用的方式先自定义请求参数转换器CustomDelegatingAuthenticationConverter,再自定义认证提供者PasswordGrantAuthenticationProvider和PasswordRefreshTokenAuthenticationProvider。

​​​​​​​tokenEndpoint

public OAuth2AuthorizationServerConfigurer<B> tokenEndpoint(Customizer<OAuth2TokenEndpointConfigurer> tokenEndpoint(Customizer) {

tokenEndpointCustomizer.customize(getConfigurer(OAuth2TokenEndpointConfigurer.class));

return this;

}

       在这里,通过调用getConfigurer()方法获取配置器实例OAuth2TokenEndpointConfigurer,然后将该实例传入给匿名内部类的接口方法。

​​​​​​​自定义请求参数转换器

CustomDelegatingAuthenticationConverter

        这是一个转换器的代表类,代表如下两个转换器类:PasswordGrantAuthenticationConverter、PasswordRefreshTokenAuthenticationConverter。

@Slf4j

public class CustomDelegatingAuthenticationConverter implements AuthenticationConverter {

    private final List<AuthenticationConverter> converters;

    public CustomDelegatingAuthenticationConverter() {

        this.converters = Arrays.asList(

            new PasswordGrantAuthenticationConverter(),

            new PasswordRefreshTokenAuthenticationConverter());

    }

    @Nullable

    @Override

    public Authentication convert(HttpServletRequest request) {

        for (AuthenticationConverter converter : this.converters) {

            Authentication authentication = converter.convert(request);

            if (authentication != null) {

                return authentication;

            }

        }

        log.error("没有匹配到合适的Converter");

        throw new BusinessException(ResponseCode.BUSINESS_AUTH_ERROR.getCode());

    }

}

       在这里,按顺序调用每个转换器实例的convert()方法,只要调用结果返回不为null则表示调用成功,成功则结束convert操作;如果遍历所有的转换器都没有调用成功,则抛出异常。

​​​​​​​PasswordGrantAuthenticationConverter

public class PasswordGrantAuthenticationConverter implements AuthenticationConverter {

    public PasswordGrantAuthenticationConverter() { }

    @Override

    public Authentication convert(HttpServletRequest request) {

        // 判断登录授权模式是否是支持的类型

        String grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE);

        String username = request.getParameter(OAuth2ParameterNames.USERNAME);

        if (!”password”.equals(grantType) || StringUtils.isEmpty(username)) {

            return null;

        }

        ... ...

        Authentication clientPrincipal=securityContextHolder.getContext().getAuthentication();

        // 获取用户名与密码,并校验密码的合法性

        String password = parameters.getFirst(OAuth2ParameterNames.PASSWORD);

        // 获取用户信息

        CustomUserDetails userDetails = customUserDetailsService.loadUser(username, password);

        // 返回自定义的PasswordGrantAuthenticationToken对象

        return new PasswordGrantAuthenticationToken(clientPrincipal, additionalParameters,

                , userDetails);

    }

}

        在这里,将用户输入的username 和password 转换为Authentication对象。

​​​​​​​PasswordRefreshTokenAuthenticationConverter

public class PasswordRefreshTokenAuthenticationConverter implements AuthenticationConverter {

@Override

public Authentication convert(HttpServletRequest request) {

String grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE);

if (!AuthorizationGrantType.REFRESH_TOKEN.getValue().equals(grantType)) {

return null;

}

    Authentication clientPrincipal=SecurityContextHolder.getContext().getAuthentication();

String refreshToken = parameters.getFirst(OAuth2ParameterNames.REFRESH_TOKEN);

String scope = parameters.getFirst(OAuth2ParameterNames.SCOPE);

Set<String> requestedScopes = Arrays.asList(StringUtils.delimitedListToStringArray(scope, " ")));

    ... ...

return new OAuth2RefreshTokenAuthenticationToken(

refreshToken, clientPrincipal, requestedScopes, additionalParameters);

}

}

        在这里,将用户输入的refreshToken 转换为Authentication对象。

自定义认证提供者

PasswordGrantAuthenticationProvider

public class PasswordGrantAuthenticationProvider implements AuthenticationProvider {

    public PasswordGrantAuthenticationProvider() {}

    @Override

    public Authentication authenticate(Authentication authentication) throws AuthenticationException {

        //获取自定义token信息

        PasswordGrantAuthenticationToken passwordGrantAuthenticationToken =

                (PasswordGrantAuthenticationToken) authentication;

        ... ...

        //授权类型

        AuthorizationGrantType authorizationGrantType = passwordGrantAuthenticationToken.getGrantType();

        //密码

        String password = (String)additionalParameters.get(OAuth2ParameterNames.PASSWORD);

        //用户信息

        CustomUserDetails userDetails = passwordGrantAuthenticationToken.getUserDetails();

        // Ensure the client is authenticated

        OAuth2ClientAuthenticationToken clientPrincipal =

  AuthUtils.getAuthenticatedClientElseThrowInvalidClient(passwordGrantAuthenticationToken);

        ... ...

        // 由于在上面已验证过用户名、密码,现在构建一个已认证的对象

        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =

                new UsernamePasswordAuthenticationToken(userDetails, password);

        DefaultOAuth2TokenContext.Builder tokenContextBuilder = DefaultOAuth2TokenContext.builder()

                .registeredClient(registeredClient)

                .principal(usernamePasswordAuthenticationToken)

                .providerContext(ProviderContextHolder.getProviderContext())

                .tokenType(OAuth2TokenType.ACCESS_TOKEN)

                .authorizationGrantType(authorizationGrantType)

                .authorizedScopes(registeredClient.getScopes())

                .authorizationGrant(passwordGrantAuthenticationToken);

        // Initialize the OAuth2Authorization

        OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.withRegisteredClient(registeredClient)

                .principalName(clientPrincipal.getName())

                .attribute(Principal.class.getName(), usernamePasswordAuthenticationToken)

                .attribute(OAuth2Authorization.AUTHORIZED_SCOPE_ATTRIBUTE_NAME, registeredClient.getScopes()).authorizationGrantType(authorizationGrantType);

        // ----- Access token -----

        OAuth2AccessToken accessToken = getAccessToken(tokenContextBuilder, authorizationBuilder);

        // ----- Refresh token -----

        OAuth2RefreshToken refreshToken =

            getRefreshToken(registeredClient, clientPrincipal, tokenContextBuilder, authorizationBuilder);

        ... ...

        //存储token信息

        authorizationService.save(authorization);

        return new PasswordTokenAuthenticationToken(registeredClient, clientPrincipal, accessToken, refreshToken, additionalParameters);

    }

    @Override

    public boolean supports(Class<?> authentication) {

        return PasswordGrantAuthenticationToken.class.isAssignableFrom(authentication);

    }

}

        在这里,验证用户输入的username 和password的合法性。

        如果合法,则生成accessToken 和refreshToken ,然后返回给用户。

​​​​​​​PasswordRefreshTokenAuthenticationProvider

public class PasswordRefreshTokenAuthenticationProvider implements AuthenticationProvider {

public PasswordRefreshTokenAuthenticationProvider() {}

@Override

public Authentication authenticate(Authentication authentication) {

OAuth2RefreshTokenAuthenticationToken refreshTokenAuthentication =

(OAuth2RefreshTokenAuthenticationToken) authentication;

OAuth2ClientAuthenticationToken clientPrincipal =

AuthUtils.getAuthenticatedClientElseThrowInvalidClient(refreshTokenAuthentication);

RegisteredClient registeredClient = clientPrincipal.getRegisteredClient();

OAuth2Authorization authorization = this.authorizationService.findByToken(

refreshTokenAuthentication.getRefreshToken(), OAuth2TokenType.REFRESH_TOKEN);

if (authorization == null || registeredClient == null ) {

log.error("Authentication或registeredClient的信息为空!");

throw new BusinessException(ResponseCode.BUSINESS_AUTH_ERROR.getCode());

}

if (!registeredClient.getId().equals(authorization.getRegisteredClientId()) ||

!registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.REFRESH_TOKEN)) {

log.error("registeredClient信息不符合要求!");

throw new BusinessException(ResponseCode.BUSINESS_AUTH_ERROR.getCode());

}

OAuth2Authorization.Token<OAuth2RefreshToken> refreshToken = authorization.getRefreshToken();

... ...

//获取token构造时存储的用户密码认证对象

UsernamePasswordAuthenticationToken principal = authorization.getAttribute(Principal.class.getName());

DefaultOAuth2TokenContext.Builder tokenContextBuilder = DefaultOAuth2TokenContext.builder()

.registeredClient(registeredClient)

.principal(principal)

.providerContext(ProviderContextHolder.getProviderContext())

.authorization(authorization)

.authorizedScopes(scopes)

.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)

.authorizationGrant(refreshTokenAuthentication);

OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.from(authorization);

// ----- Access token -----

OAuth2AccessToken accessToken = getOAuth2AccessToken(tokenContextBuilder, authorizationBuilder);

// ----- Refresh token -----

OAuth2RefreshToken currentRefreshToken = getOAuth2RefreshToken(refreshToken, registeredClient,tokenContextBuilder, authorizationBuilder);

        ... ...

    return new OAuth2AccessTokenAuthenticationToken(

registeredClient, clientPrincipal, accessToken, currentRefreshToken, additionalParameters);

}

@Override

public boolean supports(Class<?> authentication) {

  return OAuth2RefreshTokenAuthenticationToken.class.isAssignableFrom(authentication);

}

}

        在这里,验证用户输入的refreshToken 的合法性。

        如果合法,则生成新的accessToken 和refreshToken返回给用户。

时序图

  1. 类对象主要包括:配置器对象OAuth2TokenEndpointConfigurer、过滤器对象OAuth2TokenEndpointFilter、请求参数转换器对象CustomDelegatingAuthenticationConverter、认证管理器对象ProviderManager;
  2. 配置器对象:使用模板方法设计模式,提供了init、beforeConfigure、configure等几个主要的过程来对过滤器进行配置;
  3. 过滤器对象:过滤器的过滤逻辑不仅简单也很清晰,即只包含了convert和authenticate两个过程;
  4. 请求参数转换器对象:框架提供了由开发人员自定义请求参数转换器的功能,请求参数转换器的主要功能是把HTTP请求参数封装为框架需要的请求参数类;
  5. 认证管理器对象:认证管理器管理着多个认证提供者,框架提供了由开发人员自定义认证提供者的功能。
http://www.xdnf.cn/news/14290.html

相关文章:

  • Go同步原语与数据竞争:原子操作(atomic)
  • 【LangChain】4 基于文档的问答
  • 【量化】策略交易之动量策略(Momentum)
  • Transformer实战——从词袋模型到Transformer:NLP技术演进
  • Kubernetes镜像拉取认证指南
  • 勇者和魔塔的算法题
  • Linux进程池详解:从入门到理解
  • Vue 3 九宫格抽奖系统,采用优雅的 UI 设计和流畅的动画效果
  • 【工具变量】全国分省数字基础设施水平数据集-含原始数据及处理代码(2005-2024年)
  • LLMs 系列实操科普(6)
  • 网络代理设置
  • leetcode0765. 情侣牵手-hard
  • HTTP和HTTPS协议
  • Design Compiler:解组(Ungroup)
  • Modbus协议全方位解析与C#开发实战指南
  • Apache Doris FE 问题排查与故障分析全景指南
  • TI 毫米波雷达走读系列—— 3DFFT及测角
  • python基础举例
  • 人工智能学习19-Pandas-设置
  • OSI 七层网络模型
  • 分类预测 | Matlab基于AOA-VMD-LSTM故障诊断分类预测
  • WebSocket与XMPP:即时通讯技术的本质区别与选择逻辑优雅草卓伊凡|片翼|许贝贝
  • day31 打卡
  • 语音交互革命:基于 Amazon Nova Sonic + MCP 构建下一代沉浸式 Agent
  • 从 C 语言计算器到串口屏应用
  • Chapter10-XXE
  • PDF转Markdown基准测试
  • Python训练打卡Day50
  • RabbitMQ核心函数的参数意义和使用场景
  • 动态多目标进化算法:基于迁移学习的动态多目标粒子群优化算法(TrMOPSO)求解IEEE CEC 2015,提供完整MATLAB代码