Springsecurity-oauth2之/oauth/token的处理

a 夏天 提交于 2019-12-07 17:39:43

    Springsecurity-oauth2的版本是2.2.1.RELEASE.

    使用postman进行/oauth/token的时候,服务端Springsecurity是怎么处理的呢?

                     

                                                                                            图1

                           

                                                                                              图2

                   

                                                                                              图3

    上面的图2和图3,我们就会从服务端获得token。

    来看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。

    List-2

@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字段

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!