Springsecurity-oauth2之/oauth/token的處理

    Springsecurity-oauth2的版本是2.2.1.RELEASE.java

    使用postman進行/oauth/token的時候,服務端Springsecurity是怎麼處理的呢?spring

                     

                                                                                            圖1數據庫

                           

                                                                                              圖2app

                   

                                                                                              圖3dom

    上面的圖2和圖3,咱們就會從服務端得到token。ide

    來看BasicAuthenticationFilter的實現,以下List-1所示工具

    List-1源碼分析

protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
    boolean debug = this.logger.isDebugEnabled();
    String header = request.getHeader("Authorization");
    if (header != null && header.startsWith("Basic ")) {
        try {
            String[] tokens = this.extractAndDecodeHeader(header, request);

            assert tokens.length == 2;

            String username = tokens[0];
            if (debug) {
                this.logger.debug("Basic Authentication Authorization header found for user '" + username + "'");
            }

            if (this.authenticationIsRequired(username)) {
                UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, tokens[1]);
                authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
                Authentication authResult = this.authenticationManager.authenticate(authRequest);
                if (debug) {
                    this.logger.debug("Authentication success: " + authResult);
                }

                SecurityContextHolder.getContext().setAuthentication(authResult);
                this.rememberMeServices.loginSuccess(request, response, authResult);
                this.onSuccessfulAuthentication(request, response, authResult);
            }
        } catch (AuthenticationException var10) {
            SecurityContextHolder.clearContext();
            if (debug) {
                this.logger.debug("Authentication request for failed: " + var10);
            }

            this.rememberMeServices.loginFail(request, response);
            this.onUnsuccessfulAuthentication(request, response, var10);
            if (this.ignoreFailure) {
                chain.doFilter(request, response);
            } else {
                this.authenticationEntryPoint.commence(request, response, var10);
            }

            return;
        }

        chain.doFilter(request, response);
    } else {
        chain.doFilter(request, response);
    }
}

private String[] extractAndDecodeHeader(String header, HttpServletRequest request) throws IOException {
    byte[] base64Token = header.substring(6).getBytes("UTF-8");

    byte[] decoded;
    try {
        decoded = Base64.getDecoder().decode(base64Token);
    } catch (IllegalArgumentException var7) {
        throw new BadCredentialsException("Failed to decode basic authentication token");
    }

    String token = new String(decoded, this.getCredentialsCharset(request));
    int delim = token.indexOf(":");
    if (delim == -1) {
        throw new BadCredentialsException("Invalid basic authentication token");
    } else {
        return new String[]{token.substring(0, delim), token.substring(delim + 1)};
    }
}

    BasicAuthenticationFilter會判斷request頭部是否有Authorization,且該字段的值是否以"Basic  "開頭,以後得到"Basic  "後面的值,看extractAndDecodeHeader的實現,獲得ClientID和ClientSecrect,以後會調用ClientDetailsService,得到Client及Client secrect的信息。將獲得的Authentication放入SecurityContextHolder.getContext().setAuthentication()放入到Context中,這樣SpringSecurity的FilterChainProxy後續Filter就不會跑出異常,這樣請求就能順利到達處理/oauth/token的EndPoint——看org.springframework.security.oauth2.provider.endpoint.TokenEndpoint。post

    List-2ui

@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 {

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

}

    如上List-2所示,到了/oauth/token後,還會再次調用ClientDetailService獲取ClientId和ClientSecrect,以後用咱們請求的幾個參數,構造TokenRequest,這個類就是POJO,沒有什麼。以後用TokenGranter構造OAuth2AccessToken,TokenGranter的OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest)方法,用咱們請求的參數,構造OAuth2AccessToken。TokenGranter間接調用ResourceOwnerPasswordTokenGranter,以後調用ProviderManager,ProviderManager再調用AuthenticationManager,AuthenticationManager調用DaoAuthenticationProvider,從數據庫中獲取用戶信息,以後移除password,以後建立Token。

    通過源碼分析,圖3中的access_token是JDK的UUID值,以下List-3中,new DefaultOAuth2AccessToken時,UUID.randoUUID().toString()的值做爲參數傳入。

    List-3

public class DefaultTokenServices implements AuthorizationServerTokenServices, ResourceServerTokenServices,
		ConsumerTokenServices, InitializingBean {
......

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

......

    如List-4所示,先建立OAuth2RefreshToken(是interface,真實是其實現類DefaultOAuth2RefreshToken),在方法createRefreshToken中能夠看到,refresh_token的值也是JDK的UUID,以後在建立OAuth2AccessToken(是interface,真實是其實現類DefaultOAuth2AccessToken),傳入做爲返回客戶端的refresh_token,也就是圖3中的refresh_token,因此有源碼可知,access_token和refresh_token都是JDK的UUID.randomUUID().toString。

 List-4

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

	OAuth2AccessToken existingAccessToken = tokenStore.getAccessToken(authentication);
	OAuth2RefreshToken refreshToken = null;

	if (refreshToken == null) {
		refreshToken = createRefreshToken(authentication);
	}
    ......
	OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken);
	tokenStore.storeAccessToken(accessToken, authentication);
	......
	return accessToken;

}

private OAuth2RefreshToken createRefreshToken(OAuth2Authentication authentication) {
	if (!isSupportRefreshToken(authentication.getOAuth2Request())) {
		return null;
	}
	int validitySeconds = getRefreshTokenValiditySeconds(authentication.getOAuth2Request());
	String value = UUID.randomUUID().toString();
	if (validitySeconds > 0) {
		return new DefaultExpiringOAuth2RefreshToken(value, new Date(System.currentTimeMillis()
				+ (validitySeconds * 1000L)));
	}
	return new DefaultOAuth2RefreshToken(value);
}

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

    在使用oauth2會遇到clientId、clientSecret、accessTokenValiditySeconds、refreshTokenValiditySeconds、additionalInformation,這些能夠在ClientDetails的實現類BaseClientDetails中看到。

    accessTokenValiditySeconds是accessToken過時時間,refreshTokenValiditySeconds是refreshToken過時時間。

    OAuth2AccessTokenJackson1Serializer/OAuth2AccessTokenJackson2Serializer用這個作的序列化,OAuth2AccessToken這個類上有註解。OAuth2AccessToken的實現類DefaultOAuth2AccessToken也只是POJO,並沒有額外的邏輯,在序列化到HttpResponse時用了jackson的序列化工具,因此咱們能夠看到返回有access_token、refresh_token字段

相關文章
相關標籤/搜索