(七)springcloud Oauth2授權-Spring Cloud Oauth2


spring-security-oauth2

客戶端四種授權模式:

  • 授權碼模式(authorization code):第三方應用先申請一個授權碼,然后再用該碼獲取令牌。
  • 簡化模式(implicit)
  • 密碼模式(resource owner password credentials)
  • 客戶端模式(client credentials)

授權碼模式:

1、第三方應用向授權端點URI

GET https://b.com/oauth/authorize?
  response_type=code&			// 表示授權碼模式
  client_id=CLIENT_ID&		        // 表示客戶端id
  redirect_uri=CALLBACK_URL&	        // 授權后跳轉的url
  scope=read&							// 要求的授權范圍
  state=state							// 推薦的。一個不透明的值用於維護請求和回調之間的狀態。授權服務器在將用戶代理重定向會客戶端的時候會帶上該參數。

2、返回授權碼

https://a.com/callback?code=AUTHORIZATION_CODE

3、POST 請求令牌,向令牌端點發出請求

POST https://b.com/oauth/token?
 client_id=CLIENT_ID&
 client_secret=CLIENT_SECRET&
 grant_type=authorization_code&
 code=AUTHORIZATION_CODE&
 redirect_uri=CALLBACK_URL

4、驗證通過后返回令牌

{    
  "access_token":"ACCESS_TOKEN",
  "token_type":"bearer",
  "expires_in":2592000,
  "refresh_token":"REFRESH_TOKEN",
  "scope":"read",
  "uid":100101,
  "info":{...}
}

簡化模式:

RFC 6749 就規定了第二種方式,允許直接向前端頒發令牌。這種方式沒有授權碼這個中間步驟,所以稱為(授權碼)"隱藏式"(implicit)

https://b.com/oauth/authorize?
  response_type=token&
  client_id=CLIENT_ID&
  redirect_uri=CALLBACK_URL&
  scope=read

用戶認證后,直接返回令牌

https://a.com/callback#token=ACCESS_TOKEN

密碼模式:

如果你高度信任某個應用,RFC 6749 也允許用戶把用戶名和密碼,直接告訴該應用。該應用就使用你的密碼,申請令牌,這種方式稱為"密碼式"(password)。

1、客戶端直接獲取用戶在資源服務器的賬號密碼,然后向認證服務器獲取令牌

https://oauth.b.com/token?
  grant_type=password&
  username=USERNAME&
  password=PASSWORD&
  client_id=CLIENT_ID
POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded

grant_type=password&username=johndoe&password=A3ddj3w

客戶端模式:

用於沒有前端的命令行應用,即在命令行下請求令牌。

https://oauth.b.com/token?
  grant_type=client_credentials&
  client_id=CLIENT_ID&
  client_secret=CLIENT_SECRET

一搬 client_id 和 client_secret 加密作為請求頭傳輸

POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials

授權服務器發放令牌:

  • access_token:必須的。
  • token_type:必須的。比如:"bearer","mac"等等
  • expires_in:推薦的。
  • refresh_token:可選的。
  • scope:可選的。

media type是application/json,參數被序列化成JSON對象。

授權服務器必須包含"Cache-Control"HTTP頭,並且值必須是"no-store"。

HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache

{
  "access_token":"2YotnFZFEjr1zCsicMWpAA",
  "token_type":"example",
  "expires_in":3600,
  "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
  "example_parameter":"example_value"
}

刷新token:

  • grant_type:必須的。值必須是"refresh_token"。
  • refresh_token:必須的。
  • scope:可選的。
POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token&refresh_token=tGzv3JOkF0XG5Qx2TlKWIA

Spring-Cloud-Oauth2流程

獲取token:

1、if (!(principal instanceof Authentication)):校驗用戶的身份信息(身份校驗由spring-security (filter,authenticationManager, provider...完成)

2、getClientDetailsService().loadClientByClientId(clientId):通過clientId獲取客戶端信息

3、創建TokenRequest

4、校驗用戶scope this.oAuth2RequestValidator.validateScope(tokenRequest, authenticatedClient)

5、判斷用戶授權類型

6、TokenGranter:根據用戶授權類型,創建access_token和refresh_token,默認實現為CompositeTokenGranter

  • TokenEndpoint
@RequestMapping(
    value = {"/oauth/token"},
    method = {RequestMethod.POST}
)
public ResponseEntity<OAuth2AccessToken> postAccessToken(Principal principal, @RequestParam Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
    if (!(principal instanceof Authentication)) {
        throw new InsufficientAuthenticationException("There is no client authentication. Try adding an appropriate authentication filter.");
    } else {
        String clientId = this.getClientId(principal);
        ClientDetails authenticatedClient = this.getClientDetailsService().loadClientByClientId(clientId);
        TokenRequest tokenRequest = this.getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient);
        if (clientId != null && !clientId.equals("") && !clientId.equals(tokenRequest.getClientId())) {
            throw new InvalidClientException("Given client ID does not match authenticated client");
        } else {
            if (authenticatedClient != null) {
                this.oAuth2RequestValidator.validateScope(tokenRequest, authenticatedClient);
            }

            if (!StringUtils.hasText(tokenRequest.getGrantType())) {
                throw new InvalidRequestException("Missing grant type");
            } else if (tokenRequest.getGrantType().equals("implicit")) {
                throw new InvalidGrantException("Implicit grant type not supported from token endpoint");
            } else {
                if (this.isAuthCodeRequest(parameters) && !tokenRequest.getScope().isEmpty()) {
                    this.logger.debug("Clearing scope of incoming token request");
                    tokenRequest.setScope(Collections.emptySet());
                }

                if (this.isRefreshTokenRequest(parameters)) {
                    tokenRequest.setScope(OAuth2Utils.parseParameterList((String)parameters.get("scope")));
                }

                OAuth2AccessToken token = this.getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);
                if (token == null) {
                    throw new UnsupportedGrantTypeException("Unsupported grant type: " + tokenRequest.getGrantType());
                } else {
                    return this.getResponse(token);
                }
            }
        }
    }
}

private boolean isRefreshTokenRequest(Map<String, String> parameters) {
    return "refresh_token".equals(parameters.get("grant_type")) && parameters.get("refresh_token") != null;
}

private boolean isAuthCodeRequest(Map<String, String> parameters) {
  	return "authorization_code".equals(parameters.get("grant_type")) && parameters.get("code") != null;
}
  • AbstractTokenGranter
public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {

		if (!this.grantType.equals(grantType)) {
			return null;
		}
		
		String clientId = tokenRequest.getClientId();
		ClientDetails client = clientDetailsService.loadClientByClientId(clientId);
		validateGrantType(grantType, client);
		
		logger.debug("Getting access token for: " + clientId);
		
		return getAccessToken(client, tokenRequest);

}

// 委托給 tokenServices 創建token
protected OAuth2AccessToken getAccessToken(ClientDetails client, TokenRequest tokenRequest) {
  	return tokenServices.createAccessToken(getOAuth2Authentication(client, tokenRequest));
}

protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
  	OAuth2Request storedOAuth2Request = requestFactory.createOAuth2Request(client, tokenRequest);
  	return new OAuth2Authentication(storedOAuth2Request, null);
}
  • TokenServices:默認為DefaultTokenServices

1、從store中獲取token(key為client_id, username, scope的map進行md5拼上前綴),未查找則創建token(uuid), 存入store

@Transactional
public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException {

  OAuth2AccessToken existingAccessToken = tokenStore.getAccessToken(authentication);
  OAuth2RefreshToken refreshToken = null;
  if (existingAccessToken != null) {
    if (existingAccessToken.isExpired()) {
      if (existingAccessToken.getRefreshToken() != null) {
        refreshToken = existingAccessToken.getRefreshToken();
        // The token store could remove the refresh token when the
        // access token is removed, but we want to
        // be sure...
        tokenStore.removeRefreshToken(refreshToken);
      }
      tokenStore.removeAccessToken(existingAccessToken);
    }
    else {
      // Re-store the access token in case the authentication has changed
      tokenStore.storeAccessToken(existingAccessToken, authentication);
      return existingAccessToken;
    }
  }

  // Only create a new refresh token if there wasn't an existing one
  // associated with an expired access token.
  // Clients might be holding existing refresh tokens, so we re-use it in
  // the case that the old access token
  // expired.
  if (refreshToken == null) {
    refreshToken = createRefreshToken(authentication);
  }
  // But the refresh token itself might need to be re-issued if it has
  // expired.
  else if (refreshToken instanceof ExpiringOAuth2RefreshToken) {
    ExpiringOAuth2RefreshToken expiring = (ExpiringOAuth2RefreshToken) refreshToken;
    if (System.currentTimeMillis() > expiring.getExpiration().getTime()) {
      refreshToken = createRefreshToken(authentication);
    }
  }

  OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken);
  tokenStore.storeAccessToken(accessToken, authentication);
  // In case it was modified
  refreshToken = accessToken.getRefreshToken();
  if (refreshToken != null) {
    tokenStore.storeRefreshToken(refreshToken, authentication);
  }
  return accessToken;

}
  • TokenStore:對Token的CRUD,解析

1、InMemoryTokenStore

2、JwtTokenStore

3、JdbcTokenStore

4、JwkTokenStore

5、RedisTokenStore


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM