關於Oauth2是什麼以及Oauth2的四種受權模式請移步Oauth2官網。spring
下面簡單介紹一下關於Spring Security OAuth基本的原理。這也是理解pig及其pigx的第一步。數據庫
下面這張圖涉及到了Spring OAuth的一些核心類和接口。app
上圖藍色的方塊表明執行過程當中調用的具體的類,綠色的方塊表明整個執行流程中調用的類,綠色的括號中表明的是該接口調用的具體的實現類。dom
整個流程的入口點是在TokenEndpoint,由它來處理獲取令牌的請求,獲取令牌的請求默認是**/oauth/token**這個路徑。curl
1.當TokenEndpoint收到請求時,它首先會調用ClientDetailsService,ClientDetaisService從名字上看就很能夠知道是一個相似於UserDetailsService的接口,只不過UserDetailsService讀取的是用戶的信息,而ClientDetailsService讀取的是第三方應用的信息。ide
2.登陸請求頭中帶上Client的信息,而這個類就能夠作到根據ClientId讀取相應的配置信息。而ClientDetailsSevice讀取到的信息都會封裝到ClientDetails這個對象中。測試
3.同時,TokenEndpoint還會建立一個TokenRequests的對象,這個對象中封裝了除了第三方應用之外的其餘信息。好比說grant_type,scope,username,password(限密碼模式)等等信息,而這些信息都是封裝在TokenRequests裏面的。同時,ClientDetails也會被放到TokenRequests中,由於第三方應用的信息也是令牌請求的一部分。url
4.以後利用TokenRequests去調用一個叫作TokenGranter的令牌受權者的接口,這個接口實際上是對四種不一樣的受權模式進行的一個封裝。在這個接口裏,它會根據請求傳遞過來的grant_type去挑一個具體的實現來執行令牌生成的邏輯。spa
5.不論採用哪一種方式進行令牌的生成,在這個生成的過程當中都會產生兩個對象,一個是OAuth2Request,這個對象其實是以前的ClientDetails和TokenRequests這兩個對象的一個整合。另外一個Authorization封裝的其實是當前受權用戶的一些信息,也就是誰在進行受權行爲,Authorization裏封裝的就是誰的信息。這裏的用戶信息是經過UserDetailsService進行讀取的。3d
6.OAuth2Request和Authorization這兩個對象組合起來,會造成一個OAuth2Authorization對象,而這個最終產生的對象它的裏面就包含了當前是哪一個第三方應用在請求哪一個用戶以哪一種受權模式(包括受權過程當中的一些其餘參數)進行受權,也就是這個對象會彙總以前的幾個對象的信息都會封裝到OAuth2Authorization這個對象中。
7.而後這個對象會傳遞到一個叫作AuthorizationServerTokenServices的接口的實現類,它拿到OAuth2Authorization中全部的信息以後最終會生成一個OAuth2的令牌OAuth2AccessToken。
下面的是一個標準的POST請求而且在URL中攜帶參數的請求,可是這個請求不符合咱們這邊測試的要求,緣由看下面的注意事項。
curl -H "Authorization:Basic dGVzdDp0ZXN0" -X POST http://localhost:8000/auth/oauth/token?username=admin&password=123456&grant_type=password&scope=server
回車之後咱們能夠看到首先會通過網關的密碼解密過濾器,而且參數通過咱們的一通改造以後已經能夠獲取到正確的值了。
通過上面的一通操做,咱們已經拿到了獲取token的一些必要的請求了。clientId,clientSecret,grant_type,usename,password,scope,終於能夠帶着咱們的參數深刻源碼啦!
這裏結合上文提到的核心類圖來看效果更好
上文提過,OAuth2.0的認證的入口點位於TokenEndPoint。咱們也能夠看到,代碼確實已經進來了。
咱們能夠看到這個類上有一個@RequestMapping
註解,它來處理/oauth/token
的POST請求。
進來以後的第一步,就是在代碼的95行,獲取請求頭中的clientId。
而後在96行調用getClientDetailsService().loadClientByClientId(clientId)
方法獲取整個第三方應用的詳細配置。
具體的參數的意義能夠看spring-oauth-server 數據庫表說明
在拿到客戶端的信息以後在代碼的98行經過傳遞進來的參數和查詢出來的第三方應用信息構建TokenRequest。
建立TokenRequest的代碼很簡單,以下:
public TokenRequest createTokenRequest(Map<String, String> requestParameters, ClientDetails authenticatedClient) {
String clientId = requestParameters.get(OAuth2Utils.CLIENT_ID);
if (clientId == null) {
// if the clientId wasn't passed in in the map, we add pull it from the authenticated client object
clientId = authenticatedClient.getClientId();
}
else {
// otherwise, make sure that they match
if (!clientId.equals(authenticatedClient.getClientId())) {
throw new InvalidClientException("Given client ID does not match authenticated client");
}
}
String grantType = requestParameters.get(OAuth2Utils.GRANT_TYPE);
Set<String> scopes = extractScopes(requestParameters, clientId);
TokenRequest tokenRequest = new TokenRequest(requestParameters, clientId, scopes, grantType);
return tokenRequest;
}
因此其實它就幹了一件事,校驗傳遞進來clientId和查詢出來的clientId,若是匹配的話,就根據以前傳遞進來的clientId和和查詢出來的第三方應用構建TokenRequest。
而後咱們就拿到TokenRequest了,後面的代碼很簡單了:
無非就是對下面這些參數的校驗:
clientId:是否有值,值是否和查詢結果匹配
scope:請求的一些受權內容,所請求的受權必須是第三方應用能夠發送的受權集合的子集,不然沒法經過校驗)
grant_type:必須顯式指定按照哪一種受權模式獲取令牌
判斷傳遞的受權模式是不是簡化模式,若是是簡化模式也會拋異常。由於簡化模式實際上是對受權碼模式的一種簡化:在用戶的第一步的受權行爲的時候就直接返回令牌,因此是不會有調用請求令牌服務的機會的
判斷是否是受權碼模式,由於受權碼模式包含兩個步驟,在受權碼模式中發出的令牌中擁有的權限不是由發令牌的請求決定的,而是在發令牌以前的受權的請求裏就已經決定好了。所以它會對請求過來的scope進行置空操做,而後根據以前發出去的受權碼裏的權限從新設置你的scope,所以它根本不會使用請求令牌的這個請求中攜帶的scope參數。
以後判斷是否是刷新令牌的請求,應爲刷新令牌的請求有本身的scope,因此也會進行從新設置scope的操做。
通過一系列的校驗以後,最終TokenRequest會在132行傳遞給TokenGranter,而後由granter產生最終的accessToken。以後直接將accessToken寫入響應裏就能夠了。
TokenGranter中總共封裝了四種受權模式加一個刷新令牌的操做,咱們看看其中的一些細節。
CompositeTokenGranter中有一個集合,這個集合裏封裝着的就是五個會產生令牌的操做。
它會對遍歷這五種狀況,並根據以前請求中攜帶的grant_type在五種狀況中挑一種進行最終的accessToken的生成。
而後咱們看這個代碼的第38行的具體的grant
方法。
首先在org.springframework.security.oauth2.provider.token.AbstractTokenGranter中判斷當前攜帶的受權類型和這個類所支持的受權類型是否匹配,若是不匹配就返回空值,若是匹配的話就進行令牌的生成操做。
59到第63行是從新獲取一下clientId和客戶端信息跟受權類型再作一個校驗,67行的getAccessToken
方法會產生最終的一個令牌。
這個方法也很是簡單:
protected OAuth2AccessToken getAccessToken(ClientDetails client, TokenRequest tokenRequest) {
return tokenServices.createAccessToken(getOAuth2Authentication(client, tokenRequest));
}
它實際上就是對tokenServices的一個調用,而tokenSerives其實就是從37行咱們能夠看到其實就是AuthorizationServerTokenServices。這個類要想建立accessToken須要一個OAuth2Authentication對象,因此createAccessToken中包含了一個方法getOAuth2Authentication
。
這個方法不一樣的受權模式會有不一樣的實現。
在Spring Security OAuth核心類圖解析中咱們已經知道最終產生的Oauth2Authorization包含兩部分信息,一部分是請求中的一些信息,另外一部分是根據請求獲取的受權用戶的信息。而在不一樣的受權模式下獲取受權用戶的信息的方式是不一樣的,好比說pigx所使用的密碼模式就是使用請求中攜帶的用戶名和密碼來獲取當前受權用戶中的受權信息,而在受權碼模式的兩個步驟中是根據第一步發出受權碼的同時會記錄相關用戶的信息,以後對第二步進行受權的時候根據第三方應用請求過來的受權碼再讀取該受權碼對應的用戶信息。因此getOAuth2Authentication對於不一樣的受權類型有不一樣的實現。
咱們以pigx所使用的密碼模式繼續下面的流程。密碼模式對應的是org.springframework.security.oauth2.provider.password.ResourceOwnerPasswordTokenGranter
而這個方法咱們能夠看到它其實就是根據所請求的用戶名和密碼去建立UsernamePasswordAuthenticationToken,而後傳遞給authenticationManager作認證,在這個認證過程當中它會去調用com.pig4cloud.pigx.common.security.service.PigxUserDetailsServiceImpl
的loadUserByUsername
方法,根據用戶名和密碼去讀取用戶的信息,以後咱們其實就已經拿到Authorization的信息,而Oauth2Request根據第85行咱們能夠知道是根據傳進來的第三方應用詳情和tokenRequest產生出來的,而86行的OAuth2Authentication
也是由Oauth2Request
和Authorization
這兩個對象拼接起來的。而拼接的方式就是調用 org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestFactory
的createOAuth2Request
方法。
public OAuth2Request createOAuth2Request(ClientDetails client, TokenRequest tokenRequest) {
return tokenRequest.createOAuth2Request(client);
}
這個方法最終會建立一個由clientDetails和tokenRequest組合而成的OAuth2Request。
拿到OAuth2Request
就能夠去生成OAuth2Authentication
了。
而OAuth2Authentication
就是org.springframework.security.oauth2.provider.token.AbstractTokenGranter
第71到73行最終傳遞進去生成accessToken的對象。
而OAuth2Authentications生成成功以後進行返回的話就能夠執行AuthorizationServerTokenServices
的createAccessToken
方法,而一旦這個access token生成成功並寫入響應進行返回那麼整個流程也就結束了,最終咱們就拿到了想要的訪問令牌。
protected OAuth2AccessToken getAccessToken(ClientDetails client, TokenRequest tokenRequest) {
return tokenServices.createAccessToken(getOAuth2Authentication(client, tokenRequest));
}
具體建立accessToken的代碼,咱們須要仔細讀一讀org.springframework.security.oauth2.provider.token.DefaultTokenServices
的createAccessToken
方法。
首先這個類一進來就會嘗試在tokenStore中獲取accessToken,由於同一個用戶只要令牌沒過時那麼再次請求令牌的時候會把以前發送的令牌再次發還。所以一開始就會找當前用戶已經存在的令牌。
若是已經發送的令牌不爲空,那麼會在87行判斷當前的令牌是否已通過期,若是令牌過時了,那麼就會在tokenStore裏把accessToken和refreshToken一塊兒刪掉,若是令牌沒過時,那麼就把這個沒過時的令牌從新再存一下。由於可能用戶是使用另外的方式來訪問令牌的,好比說一開始用受權碼模式,後來用密碼模式,而這兩種模式須要存的信息是不同的,因此這個令牌要從新store一次。以後直接返回這個不過時的令牌。
若是令牌已通過期了或者說這個是第一次請求,令牌壓根沒生成,就會走下面的邏輯。
首先看看刷新的令牌有沒有,若是刷新的令牌沒有的話,那麼建立一枚刷新的令牌。而後在121行根據authentication, refreshToken建立accessToken。而這個建立accessToken的方法也很是簡單:
private OAuth2AccessToken createAccessToken(OAuth2Authentication authentication, OAuth2RefreshToken refreshToken) {
DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(UUID.randomUUID().toString());
int validitySeconds = getAccessTokenValiditySeconds(authentication.getOAuth2Request());
if (validitySeconds > 0) {
token.setExpiration(new Date(System.currentTimeMillis() + (validitySeconds * 1000L)));
}
token.setRefreshToken(refreshToken);
token.setScope(authentication.getOAuth2Request().getScope());
return accessTokenEnhancer != null ? accessTokenEnhancer.enhance(token, authentication) : token;
}
OAuth2AccessToken其實就是用UUID建立一個accessToken,而後把過時時間,刷新令牌和scope這些OAuth協議規定的必需要存在的參數設置上,設置完了之後它會判斷是否存在tokenEnhancer,若是存在tokenEnhancer它就會按照定製的tokenEnhancer加強生成出來的token。
拿到返回的令牌以後,在122行tokenStore會把拿到的令牌存起來,而後拿refreshToken存起來,最後把生成的令牌返回回去。
因而咱們就獲取到了令牌。
以上源碼參考我的項目基於Spring Cloud、OAuth2.0開發基於Vue先後分離的開發平臺