spring security oauth2通過(guò)授權(quán)碼獲取access_token接口源碼流程

獲取access_token

路徑為 :http://ip:port/oauth/token?client_id=CLIENT_ID&client_secret=CLIENT_SECRET&scope=SCOPE&redirect_uri=REDIRECT_URI&grant_type=authorization_code&code=CODE

  • 其實(shí)debug發(fā)現(xiàn)描馅,
    clientId和clientSecret也會(huì)在org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter進(jìn)行校驗(yàn)眠寿,形成一個(gè)UsernamePasswordAuthenticationToken 姨丈,權(quán)限就是oauth_client_details表中配置的authorities衩婚。

這里需要注意,設(shè)置了security.allowFormAuthenticationForClients();粥惧,使得clientId和clientSecret可以以拼接的方式加上參數(shù)抛腕,如果不配置得用basic來(lái)裝載,如果是使用basic啰劲,就不是走ClientCredentialsTokenEndpointFilter,而是走BasicAuthenticationFilter檀何,在返回統(tǒng)一json結(jié)果時(shí)蝇裤,需要走BasicAuthenticationFilter,在文章《http://www.reibang.com/p/5a76d246b37f

》說(shuō)明频鉴。

@Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
            throws AuthenticationException, IOException, ServletException {

        if (allowOnlyPost && !"POST".equalsIgnoreCase(request.getMethod())) {
            throw new HttpRequestMethodNotSupportedException(request.getMethod(), new String[] { "POST" });
        }

        String clientId = request.getParameter("client_id");
        String clientSecret = request.getParameter("client_secret");

        // If the request is already authenticated we can assume that this
        // filter is not needed
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication != null && authentication.isAuthenticated()) {
            return authentication;
        }

        if (clientId == null) {
            throw new BadCredentialsException("No client credentials presented");
        }

        if (clientSecret == null) {
            clientSecret = "";
        }

        clientId = clientId.trim();
        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(clientId,
                clientSecret);

        return this.getAuthenticationManager().authenticate(authRequest);

    }
  • 查看源碼:
    org.springframework.security.oauth2.provider.endpoint.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.");
        }

        String clientId = getClientId(principal);
        ClientDetails authenticatedClient = getClientDetailsService().loadClientByClientId(clientId);

        TokenRequest tokenRequest = getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient);

        if (clientId != null && !clientId.equals("")) {
            // Only validate the client details if a client authenticated during this
            // request.
            if (!clientId.equals(tokenRequest.getClientId())) {
                // double check to make sure that the client ID in the token request is the same as that in the
                // authenticated client
                throw new InvalidClientException("Given client ID does not match authenticated client");
            }
        }
        if (authenticatedClient != null) {
            oAuth2RequestValidator.validateScope(tokenRequest, authenticatedClient);
        }
        if (!StringUtils.hasText(tokenRequest.getGrantType())) {
            throw new InvalidRequestException("Missing grant type");
        }
        if (tokenRequest.getGrantType().equals("implicit")) {
            throw new InvalidGrantException("Implicit grant type not supported from token endpoint");
        }

        if (isAuthCodeRequest(parameters)) {
            // The scope was requested or determined during the authorization step
            if (!tokenRequest.getScope().isEmpty()) {
                logger.debug("Clearing scope of incoming token request");
                tokenRequest.setScope(Collections.<String> emptySet());
            }
        }

        if (isRefreshTokenRequest(parameters)) {
            // A refresh token has its own default scopes, so we should ignore any added by the factory here.
            tokenRequest.setScope(OAuth2Utils.parseParameterList(parameters.get(OAuth2Utils.SCOPE)));
        }

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

        return getResponse(token);

    }
  • 獲取access_token栓辜,本來(lái)是post請(qǐng)求的,而且clientId和clientSecret不能放在url中垛孔,要想它們可以這樣做藕甩,需要設(shè)置一下,在 繼承 AuthorizationServerConfigurerAdapter類中重寫中

  • configure(AuthorizationServerEndpointsConfigurer endpoints) 方法 allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);

  • configure(AuthorizationServerSecurityConfigurer security) 方法 allowFormAuthenticationForClients()

設(shè)置如下:

@Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
    }

@Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.passwordEncoder(passwordEncoder);
        // 開啟/oauth/check_token驗(yàn)證端口認(rèn)證權(quán)限訪問(wèn)
        security.checkTokenAccess("isAuthenticated()");
        // 開啟/oauth/token_key驗(yàn)證端口無(wú)權(quán)限訪問(wèn)
        security.tokenKeyAccess("permitAll()");
        /*
         *
         * 主要是讓/oauth/token支持client_id和client_secret做登陸認(rèn)證
         * 如果開啟了allowFormAuthenticationForClients周荐,那么就在BasicAuthenticationFilter之前
         * 添加ClientCredentialsTokenEndpointFilter,使用ClientDetailsUserDetailsService來(lái)進(jìn)行登陸認(rèn)證
         *
         */
        security.allowFormAuthenticationForClients();
    }
  • 請(qǐng)求后狭莱,一如既往,還是根據(jù)clientId去查詢相關(guān)信息概作,比較scope是否一致腋妙、是否存在該clientId,是否存在grantType參數(shù)以及它是不是為authorization_code讯榕,有無(wú)code授權(quán)碼骤素,都驗(yàn)證無(wú)問(wèn)題,就生成access_token,其實(shí)就是一個(gè)uuid愚屁。

這里要注意的是

  • org.springframework.security.oauth2.provider.token.DefaultTokenServices 這個(gè)類中創(chuàng)建access_token济竹,注意有效期
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;
    }
  • 具體以下,得知access_token第一選擇是數(shù)據(jù)庫(kù)中配置的集绰,其次是DefaultTokenService中的默認(rèn)12個(gè)小時(shí)规辱。其實(shí)我們還可以在配置中進(jìn)行設(shè)置的,還有refresh_token也是該原理栽燕!
private int accessTokenValiditySeconds = 60 * 60 * 12; // default 12 hours.

protected int getAccessTokenValiditySeconds(OAuth2Request clientAuth) {
        if (clientDetailsService != null) {
            ClientDetails client = clientDetailsService.loadClientByClientId(clientAuth.getClientId());
            Integer validity = client.getAccessTokenValiditySeconds();
            if (validity != null) {
                return validity;
            }
        }
        return accessTokenValiditySeconds;
    }
public class Oauth2JdbcAuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
     @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.tokenServices(customTokenService());
    }
}

@Bean
    public DefaultTokenServices customTokenService(){
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setSupportRefreshToken(true);
        tokenServices.setAccessTokenValiditySeconds(3);
        tokenServices.setRefreshTokenValiditySeconds(6);
        return tokenServices;
    }
  • 授權(quán)碼code參數(shù)只能用一次,因?yàn)樵讷@取access_token的時(shí)候改淑,已經(jīng)做了刪除code的操作碍岔,已存在的token,再用新code請(qǐng)求朵夏,只要token還在有效期內(nèi)蔼啦,就會(huì)返回已存在的token,只是有效期是延續(xù)之前的仰猖,而不是重新完整有效期捏肢!沿著代碼追尋下去可得:


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);

        if (logger.isDebugEnabled()) {
            logger.debug("Getting access token for: " + clientId);
        }

        return getAccessToken(client, tokenRequest);

    }

protected OAuth2AccessToken getAccessToken(ClientDetails client, TokenRequest tokenRequest) {
        return tokenServices.createAccessToken(getOAuth2Authentication(client, tokenRequest));
    }

//找到這個(gè)類的授權(quán)碼模式得繼承類奈籽,如下圖
protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
        OAuth2Request storedOAuth2Request = requestFactory.createOAuth2Request(client, tokenRequest);
        return new OAuth2Authentication(storedOAuth2Request, null);
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市鸵赫,隨后出現(xiàn)的幾起案子衣屏,更是在濱河造成了極大的恐慌,老刑警劉巖辩棒,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件狼忱,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡一睁,警方通過(guò)查閱死者的電腦和手機(jī)钻弄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)者吁,“玉大人窘俺,你說(shuō)我怎么就攤上這事「吹剩” “怎么了瘤泪?”我有些...
    開封第一講書人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)染坯。 經(jīng)常有香客問(wèn)我均芽,道長(zhǎng),這世上最難降的妖魔是什么单鹿? 我笑而不...
    開封第一講書人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任掀宋,我火速辦了婚禮,結(jié)果婚禮上仲锄,老公的妹妹穿的比我還像新娘劲妙。我一直安慰自己,他們只是感情好儒喊,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開白布镣奋。 她就那樣靜靜地躺著,像睡著了一般怀愧。 火紅的嫁衣襯著肌膚如雪侨颈。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評(píng)論 1 297
  • 那天芯义,我揣著相機(jī)與錄音哈垢,去河邊找鬼。 笑死扛拨,一個(gè)胖子當(dāng)著我的面吹牛耘分,可吹牛的內(nèi)容都是我干的匣椰。 我是一名探鬼主播驮俗,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了蚀同?” 一聲冷哼從身側(cè)響起堪滨,我...
    開封第一講書人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤桑驱,失蹤者是張志新(化名)和其女友劉穎将塑,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體枉氮,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡志衍,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了聊替。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片楼肪。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖惹悄,靈堂內(nèi)的尸體忽然破棺而出春叫,到底是詐尸還是另有隱情,我是刑警寧澤泣港,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布暂殖,位于F島的核電站,受9級(jí)特大地震影響当纱,放射性物質(zhì)發(fā)生泄漏呛每。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一坡氯、第九天 我趴在偏房一處隱蔽的房頂上張望晨横。 院中可真熱鬧,春花似錦箫柳、人聲如沸手形。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)库糠。三九已至,卻和暖如春涮毫,著一層夾襖步出監(jiān)牢的瞬間瞬欧,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工罢防, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留黍判,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓篙梢,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親美旧。 傳聞我的和親對(duì)象是個(gè)殘疾皇子渤滞,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容