pigx登錄代碼走查
大體流程
6. /oauth/token 接收
-
因為發送的是 POST /oauth/token,所以會進入
org.springframework.security.oauth2.provider.endpoint.TokenEndpoint#postAccessToken
-
在這個方法里面有個參數是
principal
,根據調試可以發現,它的實際類型是UsernamePasswordAuthenticationToken
。在這個方法里面還有個參數是parameters
,它是一個map,里面存放了password
、grant_type
、username
這三個字段 -
方法一開始判斷
principal
是否是org.springframework.security.core.Authentication
的子類,不是的話拋出異常,告知用戶增加一個合適的認證過濾器 -
principal
里面實際存儲的是clientId
和clientSecret
,所以方法從principal
里面取出clientId
,然后通過ClientDetailsService
將數據庫里面的Client詳細信息獲取出來 -
用parameters里面的
password
、grant_type
、username
,以及剛才獲取到的Client詳細信息(clientId
和scopes
),構造出一個tokenRequest
-
然后方法中寫了一個很奇怪的邏輯,將
clientId
與tokenRequest.getClientId
進行比較。這個比較有意義嗎?難道還會不同?理解不了老外的想法 -
對第4步獲取到的Client詳細信息,將它與第5步獲取到的
tokenRequest
進行比較,比較scope是否合法。太奇怪了,tokenRequest不是從Client詳細信息那里構造出來的嗎?為啥還要判斷scope是否合法?老外的想法太奇怪了 -
判斷
grant_type
是否是implicit
,是的話拋出異常,告知用戶/oauth/token
不支持implicit
方式登錄 -
判斷
grant_type
是否是authorization_code
(授權碼模式),並且code
是否不為空,是的話說明是授權碼模式登錄,我們用的是password
(密碼模式),所以這段邏輯先不看 -
判斷
grant_type
是否是refresh_token
(刷新token模式),並且refresh_token
是否不為空,是的話說明是刷新token模式,我們用的是password
(密碼模式),所以這段邏輯先不看 -
取出tokenGranter(我不知道這是啥東西),然后調用它的
grant
方法,方法的參數是授權類型(也就是password
)和第5步生成的tokenRequest
-
grant
方法里面,是調用代理對象delegate
(我不太清楚這個是什么東西)的grant
方法 -
代理對象是
CompositeTokenGranter
類,它里面有個tokenGranters
屬性,該屬性存儲了五個TokenGranter
其中ResourceOwnerPasswordTokenGranter
是我們所需要的TokenGranter
-
在
org.springframework.security.oauth2.provider.token.AbstractTokenGranter#grant
方法里面,首先會用clientDetailsService.loadClientByClientId
取出ClientDetails,驗證這個clientId是否有password
(密碼模式)認證類型,沒有的話拋出異常 -
使用
ClientDetails
和tokenRequest
,調用getAccessToken
方法 -
調用
getOAuth2Authentication
方法,進入到org.springframework.security.oauth2.provider.password.ResourceOwnerPasswordTokenGranter#getOAuth2Authentication
-
從
tokenRequest
中取出username
和password
,新建出UsernamePasswordAuthenticationToken
,再將tokenRequest
里面的parameters Map表設置到該token的details屬性里面 -
進入
org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter.AuthenticationManagerDelegator#authenticate
。然后再進入到org.springframework.security.authentication.ProviderManager#authenticate
-
在
ProviderManager#authenticate
方法里面,有兩個AuthenticationProvider
,它們分別是AnonymousAuthenticationProvider
和MobileAuthenticationProvider
,恰好兩個Provider的supports
方法都表明UsernamePasswordAuthenticationToken
不是它們繼承的類,所以這些Provider的authenticate
方法是不會進入的 -
如果上面兩個都沒有做過認證處理,那么再找父類,讓父類來認證。父類也有
providers
,它里面只有一個DaoAuthenticationProvider
,這個AuthenticationProvider支持UsernamePasswordAuthenticationToken
,所以調用DaoAuthenticationProvider
的authenticate方法 -
從
UsernamePasswordAuthenticationToken
取出username
。通過username
從userCache
取出UserDetails
,如果是null,則調用retrieveUser
方法獲取用戶信息,在這個方法里面調用了loadUserByUsername
-
對於返回的用戶詳情,首先進行預檢查,依次檢查是否被鎖定、是否被禁用、是否賬號過期。
-
然后再進入
additionalAuthenticationChecks
,在這個方法里面,取出UsernamePasswordAuthenticationToken
里面的密碼原文,對比UserDetails
里面的密碼密文,使用passwordEncoder.matches
進行密碼校驗 -
最后進入
org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider.DefaultPostAuthenticationChecks#check
,檢查密碼是否過期 -
經過前面三步,都沒有報錯的話,說明用戶輸入的賬號密碼是正確的。將用戶詳情存入緩存。調用
createSuccessAuthentication
-
在
createSuccessAuthentication
方法里面,首先要判斷密碼是否要更新。我們在登錄呢,不需要更新密碼。然后進入父類的createSuccessAuthentication
-
在父類的
createSuccessAuthentication
里面,使用賬號、密碼、權限構造出一個token,再將認證里面的詳情信息(也就是那個Map的信息)寫入token的details里面,返回UsernamePasswordAuthenticationToken
-
回到21步,擦除密碼,將密碼廣播出去。返回上級AuthenticationProvider,也擦除一遍密碼,返回認證信息
-
很多函數結束返回了,最終又返回到
org.springframework.security.oauth2.provider.password.ResourceOwnerPasswordTokenGranter#getOAuth2Authentication
,調用getRequestFactory().createOAuth2Request(client, tokenRequest)
,移除掉map中的password,構造出modifiable,使用new OAuth2Request(modifiable, client.getClientId(), client.getAuthorities(), true, this.getScope(), client.getResourceIds(), null, null, null)
生成OAuth2Request
-
使用剛才生成的
OAuth2Request
和28步返回的userAuth
,構造出OAuth2Authentication -
返回上層,使用
tokenServices.createAccessToken
創建token
。進入到org.springframework.security.oauth2.provider.token.DefaultTokenServices#createAccessToken(org.springframework.security.oauth2.provider.OAuth2Authentication)
,調用tokenStore.getAccessToken
-
在
tokenStore.getAccessToken
里面,先調用authenticationKeyGenerator.extractKey
生成一個字符串,在它前面加上auth_to_access:
再調用serializeKey
,獲取redis連接從里面獲取bytes,將bytes反序列化就是token了…… 我不看了,現在大概意思就是,先看看redis里面有沒有token,有的話返回token;沒有的話,就調用org.springframework.security.oauth2.provider.token.DefaultTokenServices#createAccessToken(org.springframework.security.oauth2.provider.OAuth2Authentication, org.springframework.security.oauth2.common.OAuth2RefreshToken)
創建token,如果有token增強器就增強token,將token存儲到tokenStore里面,返回token