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
