前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Springsecurity-oauth2之/oauth/token的处理

Springsecurity-oauth2之/oauth/token的处理

作者头像
克虏伯
发布2019-04-15 10:09:15
1.8K0
发布2019-04-15 10:09:15
举报

    Springsecurity-oauth2的版本是2.2.1.RELEASE.

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

                                                                                            图1

                                                                                              图2

                                                                                              图3

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

    来看BasicAuthenticationFilter的实现,如下List-1所示

List-1

代码语言:javascript
复制
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

代码语言:javascript
复制
@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

代码语言:javascript
复制
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

代码语言:javascript
复制
@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字段

(adsbygoogle = window.adsbygoogle || []).push({});

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档