spring security oauth2.0 根据token信息获取用户信息

项目场景:

既然前面有说到spring security 是如何验证当前用户以及获取到当前用户信息,哪么spring security auth2.0又是如何验证当前用户信息的呢?


技术详解:

spring security auth2.0验证用户信息其实更加简单,具体的逻辑就在OAuth2AuthenticationProcessingFilter,我们先一起看看OAuth2AuthenticationProcessingFilter的源码吧.

OAuth2AuthenticationProcessingFilter.java
public class OAuth2AuthenticationProcessingFilter implements Filter, InitializingBean {
    
    
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException,
			ServletException {

		final boolean debug = logger.isDebugEnabled();
		final HttpServletRequest request = (HttpServletRequest) req;
		final HttpServletResponse response = (HttpServletResponse) res;

		try {

			Authentication authentication = tokenExtractor.extract(request);
			
			if (authentication == null) {
				if (stateless && isAuthenticated()) {
					if (debug) {
						logger.debug("Clearing security context.");
					}
					SecurityContextHolder.clearContext();
				}
				if (debug) {
					logger.debug("No token in request, will continue chain.");
				}
			}
			else {
				request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE, authentication.getPrincipal());
				if (authentication instanceof AbstractAuthenticationToken) {
					AbstractAuthenticationToken needsDetails = (AbstractAuthenticationToken) authentication;
					needsDetails.setDetails(authenticationDetailsSource.buildDetails(request));
				}
				Authentication authResult = authenticationManager.authenticate(authentication);

				if (debug) {
					logger.debug("Authentication success: " + authResult);
				}

				eventPublisher.publishAuthenticationSuccess(authResult);
				SecurityContextHolder.getContext().setAuthentication(authResult);

			}
		}
		catch (OAuth2Exception failed) {
			SecurityContextHolder.clearContext();

			if (debug) {
				logger.debug("Authentication request failed: " + failed);
			}
			eventPublisher.publishAuthenticationFailure(new BadCredentialsException(failed.getMessage(), failed),
					new PreAuthenticatedAuthenticationToken("access-token", "N/A"));

			authenticationEntryPoint.commence(request, response,
					new InsufficientAuthenticationException(failed.getMessage(), failed));

			return;
		}

		chain.doFilter(request, response);
	}
    
}

内部的逻辑可以细分为以下几个步骤:

第一步:获取token的信息

第二步:如果获取token结果为null,哪么就清空security context

第三步:根据token信息获取到对应的用户信息

第四步:如果执行异常,哪么就抛出异常

我们现在来每一步进行分析,首先是获取token信息。 

获取token信息主要使用tokenExtractor,而这里我们使用BearerTokenExtractor即可。我们先看看BearerTokenExtractor内部是如何操作的吧。

BearerTokenExtractor.java
public class BearerTokenExtractor implements TokenExtractor {

	private final static Log logger = LogFactory.getLog(BearerTokenExtractor.class);

	@Override
	public Authentication extract(HttpServletRequest request) {
		String tokenValue = extractToken(request);
		if (tokenValue != null) {
			PreAuthenticatedAuthenticationToken authentication = new PreAuthenticatedAuthenticationToken(tokenValue, "");
			return authentication;
		}
		return null;
	}

	protected String extractToken(HttpServletRequest request) {
		// first check the header...
		String token = extractHeaderToken(request);

		// bearer type allows a request parameter as well
		if (token == null) {
			logger.debug("Token not found in headers. Trying request parameters.");
			token = request.getParameter(OAuth2AccessToken.ACCESS_TOKEN);
			if (token == null) {
				logger.debug("Token not found in request parameters.  Not an OAuth2 request.");
			}
			else {
				request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_TYPE, OAuth2AccessToken.BEARER_TYPE);
			}
		}

		return token;
	}

	/**
	 * Extract the OAuth bearer token from a header.
	 * 
	 * @param request The request.
	 * @return The token, or null if no OAuth authorization header was supplied.
	 */
	protected String extractHeaderToken(HttpServletRequest request) {
		Enumeration<String> headers = request.getHeaders("Authorization");
		while (headers.hasMoreElements()) { // typically there is only one (most servers enforce that)
			String value = headers.nextElement();
			if ((value.toLowerCase().startsWith(OAuth2AccessToken.BEARER_TYPE.toLowerCase()))) {
				String authHeaderValue = value.substring(OAuth2AccessToken.BEARER_TYPE.length()).trim();
				// Add this here for the auth details later. Would be better to change the signature of this method.
				request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_TYPE,
						value.substring(0, OAuth2AccessToken.BEARER_TYPE.length()).trim());
				int commaIndex = authHeaderValue.indexOf(',');
				if (commaIndex > 0) {
					authHeaderValue = authHeaderValue.substring(0, commaIndex);
				}
				return authHeaderValue;
			}
		}

		return null;
	}

}

 BearerTokenExtractor获取token的逻辑其实很简单,首先根据请求头header里面的Authorization,然后再根据Bearer的编码方式进行解码,获取到token信息,如果没有获取到哪么再根据请求地址上是否存在access_token这个参数。因此如果请求的时候可以有两种方式携带token,一种是请求头Authorization,另外一种是请求url携带access_token。

第二步:将token信息封装成PreAuthenticatedAuthenticationToken

第三步:认证PreAuthenticatedAuthenticationToken

认证的过程中,就需要使用authenticationManager,而这里使用的并不是ProviderManager而是OAuth2AuthenticationManager,我们现在看看OAuth2AuthenticationManager里面到底做了什么吧

public class OAuth2AuthenticationManager implements AuthenticationManager, InitializingBean {

    public Authentication authenticate(Authentication authentication) throws AuthenticationException {

		if (authentication == null) {
			throw new InvalidTokenException("Invalid token (token not found)");
		}
		String token = (String) authentication.getPrincipal();
		OAuth2Authentication auth = tokenServices.loadAuthentication(token);
		if (auth == null) {
			throw new InvalidTokenException("Invalid token: " + token);
		}

		Collection<String> resourceIds = auth.getOAuth2Request().getResourceIds();
		if (resourceId != null && resourceIds != null && !resourceIds.isEmpty() && !resourceIds.contains(resourceId)) {
			throw new OAuth2AccessDeniedException("Invalid token does not contain resource id (" + resourceId + ")");
		}

		checkClientDetails(auth);

		if (authentication.getDetails() instanceof OAuth2AuthenticationDetails) {
			OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) authentication.getDetails();
			// Guard against a cached copy of the same details
			if (!details.equals(auth.getDetails())) {
				// Preserve the authentication details from the one loaded by token services
				details.setDecodedDetails(auth.getDetails());
			}
		}
		auth.setDetails(authentication.getDetails());
		auth.setAuthenticated(true);
		return auth;

	}

}

可以从源码里面看到,正在根据token信息获取用户信息主要是ResourceServerTokenServices,而ResourceServerTokenServices也是一个接口,主要有多个实现类,分别是

DefaultTokenServices:默认实现,主要是从TokenStore获取到对应的用户信息

RemoteTokenServices:主要是资源服使用,资源服务通过HTTP请求授权服,然后根据授权服务获取到对应的用户信息

UserInfoTokenServices:作用和 RemoteTokenServices差不多,只不过请求的地址是userInfoEndpointUrl

SpringSocialTokenServices:老实说,这个我不知道是干嘛的....囧

然后在OAuth2AuthenticationManager使用的是DefaultTokenServices,我们这回就看看DefaultTokenServices是做了什么吧。

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

    public OAuth2Authentication loadAuthentication(String accessTokenValue) throws AuthenticationException,
			InvalidTokenException {
		OAuth2AccessToken accessToken = tokenStore.readAccessToken(accessTokenValue);
		if (accessToken == null) {
			throw new InvalidTokenException("Invalid access token: " + accessTokenValue);
		}
		else if (accessToken.isExpired()) {
			tokenStore.removeAccessToken(accessToken);
			throw new InvalidTokenException("Access token expired: " + accessTokenValue);
		}

		OAuth2Authentication result = tokenStore.readAuthentication(accessToken);
		if (result == null) {
			// in case of race condition
			throw new InvalidTokenException("Invalid access token: " + accessTokenValue);
		}
		if (clientDetailsService != null) {
			String clientId = result.getOAuth2Request().getClientId();
			try {
				clientDetailsService.loadClientByClientId(clientId);
			}
			catch (ClientRegistrationException e) {
				throw new InvalidTokenException("Client not valid: " + clientId, e);
			}
		}
		return result;
	}
    
}

从代码里面就可以看出来,DefaultTokenServices主要使用的也是TokenStore,而TokenStore也是一个接口,这里包含了多个实现类,具体如下:

InMemoryTokenStore:基于内存存储,默认是基于内存存储

JdbcTokenStore:基于数据库存储

JwtTokenStore:jwt,主要是通过加密解密的方式来解析token信息

RedisTokenStore:基于redis存储

这里就不一一描述了,感兴趣的可以自己去看看。好的,言归正传我们假设已经根据token信息获取到了用户信息,然后再根据源码我们就知道如果获取了用户信息

第四步:将内容放到SecurityContext

代码如下SecurityContextHolder.getContext().setAuthentication(authResult);

第五步:捕获异常OAuth2AuthenticationEntryPoint

这里也可以自定义捕获异常,然后再根据异常信息返回对应的结果信息。

后记:

综上所述,spring security oauth校验token信息是否有效,核心点就是OAuth2AuthenticationProcessingFilter,如果感兴趣的话可以仔细去看看。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值