<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??碼云GVP開源項目 12k star Uniapp+ElementUI 功能強大 支持多語言、二開方便! 廣告
                ## 獲取token 上一篇博客中我們嘗試使用了password模式和client模式,有一個比較關鍵的endpoint:/oauth/token。從這個入口開始分析,spring security oauth2內部是如何生成token的。 首先開啟debug信息: ~~~ logging: level: org.springframework: DEBUG ~~~ 可以完整的看到內部的運轉流程。 client模式稍微簡單一些,使用client模式獲取token `http://localhost:8080/oauth/token? client_id=client_1&client_secret=123456&scope=select&grant_type=client_credentials` 由于debug信息太多了,我簡單按照順序列了一下關鍵的幾個類: ~~~ ClientCredentialsTokenEndpointFilter DaoAuthenticationProvider TokenEndpoint TokenGranter ~~~ ## ClientCredentialsTokenEndpointFilter和DaoAuthenticationProvider 截取關鍵的代碼,可以分析出大概的流程 在請求到達/oauth/token之前經過了ClientCredentialsTokenEndpointFilter這個過濾器,關鍵方法如下 ~~~ public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException { ... String clientId = request.getParameter("client_id"); String clientSecret = request.getParameter("client_secret"); ... clientId = clientId.trim(); UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(clientId, clientSecret); return this.getAuthenticationManager().authenticate(authRequest); } ~~~ 用來從請求中獲取client_id,client_secret,組裝成一個UsernamePasswordAuthenticationToken作為身份標識,使用容器中的頂級身份管理器AuthenticationManager去進行身份認證(AuthenticationManager的實現類一般是ProviderManager。而ProviderManager內部維護了一個List,真正的身份認證是由一系列AuthenticationProvider去完成。而AuthenticationProvider的常用實現類則是DaoAuthenticationProvider,DaoAuthenticationProvider內部又聚合了一個UserDetailsService接口,UserDetailsService才是獲取用戶詳細信息的最終接口,而我們上一篇文章中在內存中配置用戶,就是使用了UserDetailsService的一個實現類InMemoryUserDetailsManager)。UML類圖可以大概理解下這些類的關系,省略了授權部分。 ![](https://box.kancloud.cn/4d677e7e8467a0f879e25058400c7cb7_1289x667.png) 可能機智的讀者會發現一個問題,我前面一片文章已經提到了client模式是不存在“用戶”的概念的,那么這里的身份認證是在認證什么呢?debug可以發現UserDetailsService的實現被適配成了ClientDetailsUserDetailsService,這個設計是將client客戶端的信息(client_id,client_secret)適配成用戶的信息(username,password),這樣我們的認證流程就不需要修改了。 經過ClientCredentialsTokenEndpointFilter之后,身份信息已經得到了AuthenticationManager的驗證。接著便到達了 TokenEndpoint。 ## TokenEndpoint 前面的兩個ClientCredentialsTokenEndpointFilter和DaoAuthenticationProvider可以理解為一些前置校驗,和身份封裝,而這個類一看名字就知道和我們的token是密切相關的。 ~~~ @FrameworkEndpoint public class TokenEndpoint extends AbstractEndpoint { @RequestMapping(value = "/oauth/token", method=RequestMethod.POST) public ResponseEntity<OAuth2AccessToken> postAccessToken(Principal principal, @RequestParam Map<String, String> parameters) throws HttpRequestMethodNotSupportedException { ... String clientId = getClientId(principal); ClientDetails authenticatedClient = getClientDetailsService().loadClientByClientId(clientId); ... TokenRequest tokenRequest = getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient); ... OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest); ... return getResponse(token); } private TokenGranter tokenGranter; } ~~~ 省略了一些校驗代碼之后,真正的/oauth/token端點暴露在了我們眼前,其中方法參數中的Principal經過之前的過濾器,已經被填充了相關的信息,而方法的內部則是依賴了一個TokenGranter 來頒發token。其中OAuth2AccessToken的實現類DefaultOAuth2AccessToken就是最終在控制臺得到的token序列化之前的原始類: ~~~ public class DefaultOAuth2AccessToken implements Serializable, OAuth2AccessToken { private static final long serialVersionUID = 914967629530462926L; private String value; private Date expiration; private String tokenType = BEARER_TYPE.toLowerCase(); private OAuth2RefreshToken refreshToken; private Set<String> scope; private Map<String, Object> additionalInformation = Collections.emptyMap(); //getter,setter } @org.codehaus.jackson.map.annotate.JsonSerialize(using = OAuth2AccessTokenJackson1Serializer.class) @org.codehaus.jackson.map.annotate.JsonDeserialize(using = OAuth2AccessTokenJackson1Deserializer.class) @com.fasterxml.jackson.databind.annotation.JsonSerialize(using = OAuth2AccessTokenJackson2Serializer.class) @com.fasterxml.jackson.databind.annotation.JsonDeserialize(using = OAuth2AccessTokenJackson2Deserializer.class) public interface OAuth2AccessToken { public static String BEARER_TYPE = "Bearer"; public static String OAUTH2_TYPE = "OAuth2"; /** * The access token issued by the authorization server. This value is REQUIRED. */ public static String ACCESS_TOKEN = "access_token"; /** * The type of the token issued as described in <a * href="http://tools.ietf.org/html/draft-ietf-oauth-v2-22#section-7.1">Section 7.1</a>. Value is case insensitive. * This value is REQUIRED. */ public static String TOKEN_TYPE = "token_type"; /** * The lifetime in seconds of the access token. For example, the value "3600" denotes that the access token will * expire in one hour from the time the response was generated. This value is OPTIONAL. */ public static String EXPIRES_IN = "expires_in"; /** * The refresh token which can be used to obtain new access tokens using the same authorization grant as described * in <a href="http://tools.ietf.org/html/draft-ietf-oauth-v2-22#section-6">Section 6</a>. This value is OPTIONAL. */ public static String REFRESH_TOKEN = "refresh_token"; /** * The scope of the access token as described by <a * href="http://tools.ietf.org/html/draft-ietf-oauth-v2-22#section-3.3">Section 3.3</a> */ public static String SCOPE = "scope"; ... } ~~~ 一個典型的樣例token響應,如下所示,就是上述類序列化后的結果: ~~~ { "access_token":"950a7cc9-5a8a-42c9-a693-40e817b1a4b0", "token_type":"bearer", "refresh_token":"773a0fcd-6023-45f8-8848-e141296cb3cb", "expires_in":27036, "scope":"select" } ~~~ ## TokenGranter 先從UML類圖對TokenGranter接口的設計有一個宏觀的認識 ![](https://box.kancloud.cn/bef68c8213e5330299830ce406794ea8_1206x329.png) TokenGranter的設計思路是使用CompositeTokenGranter管理一個List列表,每一種grantType對應一個具體的真正授權者,在debug過程中可以發現CompositeTokenGranter 內部就是在循環調用五種TokenGranter實現類的grant方法,而granter內部則是通過grantType來區分是否是各自的授權類型。 ~~~ public class CompositeTokenGranter implements TokenGranter { private final List<TokenGranter> tokenGranters; public CompositeTokenGranter(List<TokenGranter> tokenGranters) { this.tokenGranters = new ArrayList<TokenGranter>(tokenGranters); } public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) { for (TokenGranter granter : tokenGranters) { OAuth2AccessToken grant = granter.grant(grantType, tokenRequest); if (grant!=null) { return grant; } } return null; } } ~~~ 五種類型分別是: * ResourceOwnerPasswordTokenGranter ==> password密碼模式 * AuthorizationCodeTokenGranter ==> authorization_code授權碼模式 * ClientCredentialsTokenGranter ==> client_credentials客戶端模式 * ImplicitTokenGranter ==> implicit簡化模式 * RefreshTokenGranter ==>refresh_token 刷新token專用 以客戶端模式為例,思考如何產生token的,則需要繼續研究5種授權者的抽象類:AbstractTokenGranter ~~~ public abstract class AbstractTokenGranter implements TokenGranter { protected final Log logger = LogFactory.getLog(getClass()); //與token相關的service,重點 private final AuthorizationServerTokenServices tokenServices; //與clientDetails相關的service,重點 private final ClientDetailsService clientDetailsService; //創建oauth2Request的工廠,重點 private final OAuth2RequestFactory requestFactory; private final String grantType; ... public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) { ... String clientId = tokenRequest.getClientId(); ClientDetails client = clientDetailsService.loadClientByClientId(clientId); validateGrantType(grantType, client); logger.debug("Getting access token for: " + clientId); return getAccessToken(client, tokenRequest); } 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); } ... } ~~~ 回過頭去看TokenEndpoint中,正是調用了這里的三個重要的類變量的相關方法。由于篇幅限制,不能延展太多,不然沒完沒了,所以重點分析下AuthorizationServerTokenServices是何方神圣。 ## AuthorizationServerTokenServices ~~~ public interface AuthorizationServerTokenServices { //創建token OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException; //刷新token OAuth2AccessToken refreshAccessToken(String refreshToken, TokenRequest tokenRequest) throws AuthenticationException; //獲取token OAuth2AccessToken getAccessToken(OAuth2Authentication authentication); } ~~~ 在默認的實現類DefaultTokenServices中,可以看到token是如何產生的,并且了解了框架對token進行哪些信息的關聯。 ~~~ @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; } ~~~ 簡單總結一下AuthorizationServerTokenServices的作用,他提供了創建token,刷新token,獲取token的實現。在創建token時,他會調用tokenStore對產生的token和相關信息存儲到對應的實現類中,可以是Redis,數據庫,內存,jwt。 ## 總結 本篇總結了使用客戶端模式獲取Token時,spring security oauth2內部的運作流程,其他模式有一定的不同,但抽象功能是固定的,只是具體的實現類會被響應地替換。閱讀spring的源碼,會發現它的設計中出現了非常多的抽象接口,這對我們理清楚內部工作流程產生了不小的困擾,我的方式是可以借助UML類圖,先從宏觀理清楚作者的設計思路,這會讓我們的分析事半功倍。 下一篇文章重點分析用戶攜帶token訪問受限資源時,spring security oauth2內部的工作流程。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看