1. 程式人生 > >Spring Security Oauth2 認證(獲取token/重新整理token)流程(password模式)

Spring Security Oauth2 認證(獲取token/重新整理token)流程(password模式)

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=50575
tyL86xp29O380t1
  • 檢查頭肯是否有效請求(/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.一個比較重要的過濾器
這裡寫圖片描述
2.此處是①中的attemptAuthentication方法
這裡寫圖片描述
3.此處是②中呼叫的authenticate方法
這裡寫圖片描述
4.此處是③中呼叫的AbstractUserDetailsAuthenticationProvider類的authenticate方法
這裡寫圖片描述
5.此處是④中呼叫的DaoAuthenticationProvider類的retrieveUser方法
這裡寫圖片描述
6.此處為⑤中呼叫的ClientDetailsUserDetailsService類的loadUserByUsername方法,執行完後接著返回執行④之後的方法
這裡寫圖片描述
7.此處為④中呼叫的DaoAuthenticationProvider類的additionalAuthenticationChecks方法,此處執行完則主要過濾器執行完畢,後續會進入/oauth/token對映的方法。
這裡寫圖片描述
8.此處進入/oauth/token對映的TokenEndpoint類的postAccessToken方法
這裡寫圖片描述
9.此處為⑧中呼叫的AbstractTokenGranter類的grant方法
這裡寫圖片描述
10.此處為⑨中呼叫的ResourceOwnerPasswordTokenGranter類中的getOAuth2Authentication方法
這裡寫圖片描述
11.此處為⑩中呼叫的自定義的CustomUserAuthenticationProvider類中的authenticate方法,此處校驗使用者密碼是否正確,此處執行完則返回⑨執行後續方法。
這裡寫圖片描述
12.此處為⑨中呼叫的DefaultTokenServices中的createAccessToken方法
這裡寫圖片描述
13.此處為12中呼叫的RedisTokenStore中的getAccessToken方法等,此處執行完,則一直向上返回到⑧中執行後續方法。
這裡寫圖片描述
14.此處為⑧中獲取到token後需要包裝返回流操作
這裡寫圖片描述

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過程中有什麼問題,也可以詳細看下上面的截圖,或許會有一些收穫。