欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Spring Security Oauth2 认证(获取token/刷新token)流程(password模式)

程序员文章站 2022-06-13 20:02:52
...

https://blog.csdn.net/bluuusea/article/details/80284458

1.本文介绍的认证流程范围

本文主要对从用户发起获取token的请求(/oauth/token),到请求结束返回token中间经过的几个关键点进行说明。

2.认证会用到的相关请求

注:所有请求均为post请求。

  • 获取access_token请求(/oauth/token)
    请求所需参数:client_id、client_secret、grant_type、username、password
http://localhost/oauth/token?client_id=demoClientId&client_secret=demoClientSecret&grant_type=password&username=demoUser&password=50575tyL86xp29O380t1
  • 检查头肯是否有效请求(/oauth/check_token)
    请求所需参数:token
http://localhost/oauth/check_token?token=f57ce129-2d4d-4bd7-1111-f31ccc69d4d1
  • 刷新token请求(/oauth/token)
    请求所需参数:grant_type、refresh_token、client_id、client_secret
    其中grant_type为固定值:grant_type=refresh_token
http://localhost/oauth/token?grant_type=refresh_token&refresh_token=fbde81ee-f419-42b1-1234-9191f1f95be9&client_id=demoClientId&client_secret=demoClientSecret

2.认证核心流程

注:文中介绍的认证服务器端token存储在Reids,用户信息存储使用数据库,文中会包含相关的部分代码。

2.1.获取token的主要流程:

加粗内容为每一步的重点,不想细看的可以只看加粗内容:

  1. 用户发起获取token的请求。
  2. 过滤器会验证path是否是认证的请求/oauth/token,如果为false,则直接返回没有后续操作。
  3. 过滤器通过clientId查询生成一个Authentication对象
  4. 然后会通过username和生成的Authentication对象生成一个UserDetails对象,并检查用户是否存在。
  5. 以上全部通过会进入地址/oauth/token,即TokenEndpoint的postAccessToken方法中。
  6. postAccessToken方法中会验证Scope,然后验证是否是refreshToken请求等。
  7. 之后调用AbstractTokenGranter中的grant方法。
  8. grant方法中调用AbstractUserDetailsAuthenticationProvider的authenticate方法,通过username和Authentication对象来检索用户是否存在
  9. 然后通过DefaultTokenServices类从tokenStore中获取OAuth2AccessToken对象
  10. 然后将OAuth2AccessToken对象包装进响应流返回

2.2.刷新token(refresh token)的流程

刷新token(refresh token)的流程与获取token的流程只有⑨有所区别:

  • 获取token调用的是AbstractTokenGranter中的getAccessToken方法,然后调用tokenStore中的getAccessToken方法获取token。
  • 刷新token调用的是RefreshTokenGranter中的getAccessToken方法,然后使用tokenStore中的refreshAccessToken方法获取token。

2.3.tokenStore的特点

tokenStore通常情况为自定义实现,一般放置在缓存或者数据库中。此处可以利用自定义tokenStore来实现多种需求,如:

  • 同已用户每次获取token,获取到的都是同一个token,只有token失效后才会获取新token。
  • 同一用户每次获取token都生成一个完成周期的token并且保证每次生成的token都能够使用(多点登录)。
  • 同一用户每次获取token都保证只有最后一个token能够使用,之前的token都设为无效(单点token)。

3.获取token的详细流程(代码截图)

3.1.代码截图梳理流程

1.一个比较重要的过滤器
Spring Security Oauth2 认证(获取token/刷新token)流程(password模式)
2.此处是①中的attemptAuthentication方法
Spring Security Oauth2 认证(获取token/刷新token)流程(password模式)
3.此处是②中调用的authenticate方法
Spring Security Oauth2 认证(获取token/刷新token)流程(password模式)
4.此处是③中调用的AbstractUserDetailsAuthenticationProvider类的authenticate方法
Spring Security Oauth2 认证(获取token/刷新token)流程(password模式)
5.此处是④中调用的DaoAuthenticationProvider类的retrieveUser方法
Spring Security Oauth2 认证(获取token/刷新token)流程(password模式)
6.此处为⑤中调用的ClientDetailsUserDetailsService类的loadUserByUsername方法,执行完后接着返回执行④之后的方法
Spring Security Oauth2 认证(获取token/刷新token)流程(password模式)
7.此处为④中调用的DaoAuthenticationProvider类的additionalAuthenticationChecks方法,此处执行完则主要过滤器执行完毕,后续会进入/oauth/token映射的方法。
Spring Security Oauth2 认证(获取token/刷新token)流程(password模式)
8.此处进入/oauth/token映射的TokenEndpoint类的postAccessToken方法
Spring Security Oauth2 认证(获取token/刷新token)流程(password模式)
9.此处为⑧中调用的AbstractTokenGranter类的grant方法
Spring Security Oauth2 认证(获取token/刷新token)流程(password模式)
10.此处为⑨中调用的ResourceOwnerPasswordTokenGranter类中的getOAuth2Authentication方法
Spring Security Oauth2 认证(获取token/刷新token)流程(password模式)
11.此处为⑩中调用的自定义的CustomUserAuthenticationProvider类中的authenticate方法,此处校验用户密码是否正确,此处执行完则返回⑨执行后续方法。
Spring Security Oauth2 认证(获取token/刷新token)流程(password模式)
12.此处为⑨中调用的DefaultTokenServices中的createAccessToken方法
Spring Security Oauth2 认证(获取token/刷新token)流程(password模式)
13.此处为12中调用的RedisTokenStore中的getAccessToken方法等,此处执行完,则一直向上返回到⑧中执行后续方法。
Spring Security Oauth2 认证(获取token/刷新token)流程(password模式)
14.此处为⑧中获取到token后需要包装返回流操作
Spring Security Oauth2 认证(获取token/刷新token)流程(password模式)

3.2.示例中spring-security.xml的部分配置

<!-- 认证地址 -->
<sec:http pattern="/oauth/token" create-session="stateless"
              authentication-manager-ref="authenticationManager" >
    <sec:intercept-url pattern="/oauth/token" access="IS_AUTHENTICATED_FULLY" />
    <sec:anonymous enabled="false" />
    <sec:http-basic entry-point-ref="clientAuthenticationEntryPoint" />
    <sec:custom-filter ref="clientCredentialsTokenEndpointFilter" before="BASIC_AUTH_FILTER" />
    <sec:access-denied-handler ref="oauthAccessDeniedHandler" />
</sec:http>

<bean id="clientAuthenticationEntryPoint"
          class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint">
    <property name="realmName" value="springsec/client" />
    <property name="typeName" value="Basic" />
</bean>

<bean id="clientCredentialsTokenEndpointFilter"
      class="org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter">
    <property name="authenticationManager" ref="authenticationManager" />
</bean>

<bean id="oauthAccessDeniedHandler"
      class="org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler">
</bean>


<!-- 认证管理器-->
<sec:authentication-manager alias="authenticationManager">
    <sec:authentication-provider user-service-ref="clientDetailsUserService" />
</sec:authentication-manager>

<!-- 注入自定义clientDetails-->
<bean id="clientDetailsUserService"
      class="org.springframework.security.oauth2.provider.client.ClientDetailsUserDetailsService">
    <constructor-arg ref="clientDetails" />
</bean>

<!-- 自定义clientDetails-->
<bean id="clientDetails" class="com.xxx.core.framework.oauth.CustomClientDetailsServiceImpl">
</bean>
<!-- 注入自定义provider-->
<sec:authentication-manager id="userAuthenticationManager">
    <sec:authentication-provider ref="customUserAuthenticationProvider" />
</sec:authentication-manager>
<!--自定义用户认证provider-->
<bean id="customUserAuthenticationProvider"
      class="com.xxx.core.framework.oauth.CustomUserAuthenticationProvider">
</bean>

<oauth:authorization-server
        client-details-service-ref="clientDetails" token-services-ref="tokenServices" check-token-enabled="true" >
    <oauth:authorization-code />
    <oauth:implicit/>
    <oauth:refresh-token/>
    <oauth:client-credentials />
    <oauth:password authentication-manager-ref="userAuthenticationManager"/>
</oauth:authorization-server>

<!-- 自定义tokenStore-->
<bean id="tokenStore"
      class="com.xxx.core.framework.oauth.RedisTokenStore" />

<!-- 设置access_token有效期,设置支持refresh_token,refresh_token有效期默认为30天-->
<bean id="tokenServices"
      class="org.springframework.security.oauth2.provider.token.DefaultTokenServices">
    <property name="tokenStore" ref="tokenStore" />
    <property name="supportRefreshToken" value="true" />
    <property name="accessTokenValiditySeconds" value="43200"></property>
    <property name="clientDetailsService" ref="clientDetails" />
</bean>

4.总结

本文中的流程能够结果的问题:

  • 需要自定义修改获取到的token
  • token单点问题
  • 使用refresh_token的情况

PS:梳理这个认证流程也是因为最近工作需要设置token超时机制,刷新token,检查token等,需要对认证这块了解的特别清楚才行,后面的代码截图只是详细的介绍了获取access_token的流程,其实refresh_token与access_token的流程基本是一样的,如果您在使用refresh_token过程中有什么问题,也可以详细看下上面的截图,或许会有一些收获。

                        <li class="tool-item tool-active is-like "><a href="javascript:;"><svg class="icon" aria-hidden="true">
                            <use xlink:href="#csdnc-thumbsup"></use>
                        </svg><span class="name">点赞</span>
                        <span class="count">28</span>
                        </a></li>
                        <li class="tool-item tool-active is-collection "><a href="javascript:;" data-report-click="{&quot;mod&quot;:&quot;popu_824&quot;}"><svg class="icon" aria-hidden="true">
                            <use xlink:href="#icon-csdnc-Collection-G"></use>
                        </svg><span class="name">收藏</span></a></li>
                        <li class="tool-item tool-active is-share"><a href="javascript:;" data-report-click="{&quot;mod&quot;:&quot;1582594662_002&quot;}"><svg class="icon" aria-hidden="true">
                            <use xlink:href="#icon-csdnc-fenxiang"></use>
                        </svg>分享</a></li>
                        <!--打赏开始-->
                                                <!--打赏结束-->
                                                <li class="tool-item tool-more">
                            <a>
                            <svg t="1575545411852" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5717" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M179.176 499.222m-113.245 0a113.245 113.245 0 1 0 226.49 0 113.245 113.245 0 1 0-226.49 0Z" p-id="5718"></path><path d="M509.684 499.222m-113.245 0a113.245 113.245 0 1 0 226.49 0 113.245 113.245 0 1 0-226.49 0Z" p-id="5719"></path><path d="M846.175 499.222m-113.245 0a113.245 113.245 0 1 0 226.49 0 113.245 113.245 0 1 0-226.49 0Z" p-id="5720"></path></svg>
                            </a>
                            <ul class="more-box">
                                <li class="item"><a class="article-report">文章举报</a></li>
                            </ul>
                        </li>
                                            </ul>
                </div>
                            </div>
            <div class="person-messagebox">
                <div class="left-message"><a href="https://blog.csdn.net/bluuusea">
                    <img src="https://profile.csdnimg.cn/E/3/2/3_bluuusea" class="avatar_pic" username="bluuusea">
                                            <img src="https://g.csdnimg.cn/static/user-reg-year/1x/8.png" class="user-years">
                                    </a></div>
                <div class="middle-message">
                                        <div class="title"><span class="tit"><a href="https://blog.csdn.net/bluuusea" data-report-click="{&quot;mod&quot;:&quot;popu_379&quot;}" target="_blank">昵称2019</a></span>
                                            </div>
                    <div class="text"><span>发布了59 篇原创文章</span> · <span>获赞 79</span> · <span>访问量 16万+</span></div>
                </div>
                                <div class="right-message">
                                            <a href="https://im.csdn.net/im/main.html?userName=bluuusea" target="_blank" class="btn btn-sm btn-red-hollow bt-button personal-letter">私信
                        </a>
                                                            <a class="btn btn-sm  bt-button personal-watch" data-report-click="{&quot;mod&quot;:&quot;popu_379&quot;}">关注</a>
                                    </div>
                            </div>
                    </div>
    </article>
    
    相关标签: oauth2