spring-security学习(十二)——spring security oauth源码简析

文章目录

前言

上一篇博客在spring security oauth的基础上实现了资源服务器和认证服务器,使得服务能顺利按照常用的4种授权模式签发token,这一篇博客就是在原来已经实现好的短信验证码登录,表单登录以及社交登录的基础上,实现基于token的认证方式

spring security oauth源码简析

这篇博客的主要目的是使得我们自己的认证模式嫁接到标准的oauth协议上,在此之前,我们需要简单研究一下spring security oauth的源码

先给出一张图

spring-security学习(十二)——spring security oauth源码简析

肯定是懵逼的

这个图只是涉及到了的接口和相关的类,蓝色的表示接口,其中的括号表示实现类

TokenEndPoint

这个是用来处理/oauth/token这个请求的,上篇博客说道,简单集成spring-security-oauth之后,会自动注入一些请求路径,其中就包括oauth/token,这个请求就是获取token的关键请求,而TokenEndPoint可以看成是处理这个请求的controller,其部分源码如下

//以下源码位于org.springframework.security.oauth2.provider.endpoint.TokenEndpoint
@FrameworkEndpoint
public class TokenEndpoint extends AbstractEndpoint {

	private OAuth2RequestValidator oAuth2RequestValidator = new DefaultOAuth2RequestValidator();

	private Set<HttpMethod> allowedRequestMethods = new HashSet<HttpMethod>(Arrays.asList(HttpMethod.POST));

    //针对oauth/token的get请求处理
	@RequestMapping(value = "/oauth/token", method=RequestMethod.GET)
	public ResponseEntity<OAuth2AccessToken> getAccessToken(Principal principal, @RequestParam
	Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
		if (!allowedRequestMethods.contains(HttpMethod.GET)) {
			throw new HttpRequestMethodNotSupportedException("GET");
		}
		return postAccessToken(principal, parameters);
	}
	//针对oauth/token的 post 请求处理
	@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);
        //TODO:注意这里,父类中维护了一个clientDetailsService的属性,这里是获取clientDetailsService,然后调用clientDetailsService的loadClientByClientId方法
		ClientDetails authenticatedClient = getClientDetailsService().loadClientByClientId(clientId);

        //TODO:根据ClientDetails信息封装TokenRequest, 这个tokenRequest中包含请求中的其他信息,比如:认证类型grant_type,以及对应的认证参数
		TokenRequest tokenRequest = getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient);

        //判断clientid是否匹配
		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");
			}
		}
        //校验scope
		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");
		}

        //是否是授权码模式(这里涉及一个稍微复杂点的scope的读取)
		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)));
		}

        //TODO:这里会调用TokenGrant中的grant方法,TokenGrant其实封装的是我们四种认证方式的具体实现,
        //TODO:这里会根据请求中指定的grant_type调用具体的认证方式,并返回相关token
        //TODO:这个token由两部分组成,一部分为OAuth2Request,另一部分为Authentication(这个是具体用户的认证信息)
		OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);
		if (token == null) {
			throw new UnsupportedGrantTypeException("Unsupported grant type: " + tokenRequest.getGrantType());
		}

		return getResponse(token);

	}
	//以下省略
}

ClientDetailsService

可以联想到我们之前的UserDetailsService,这个用于获取用户详细信息的服务类,ClientDetailsService就是用来根据请求中传递进来的clientId,去读取客户端的相关信息,然后封装到ClientDetails中

TokenGranter

这个接口定义了根据不同的认证类型,生成token的方式,其源码只有一行

public interface TokenGranter {

	OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest);

}

TokenEndPoint请求处理方法的最后,会调用TokenGranter中的该方法完成OAuth2AccessToken的生成。下面以密码认证方式为例,从TokenGranter开始,走一遍源码流程。

在之前的分析基础上,oauth/token的请求,会首先走到这里

//TODO:这里会调用TokenGrant中的grant方法,TokenGrant其实封装的是我们四种认证方式的具体实现,
//TODO:这里会根据请求中指定的grant_type调用具体的认证方式,并返回相关token
//TODO:这个token由两部分组成,一部分为OAuth2Request,另一部分为Authentication(这个是具体用户的认证信息)
OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);

之后调用CompositeTokenGranter中的grant方法(CompositeTokenGranter是一个聚合了所有TokenGranter的组件)。

public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
	for (TokenGranter granter : tokenGranters) {
		OAuth2AccessToken grant = granter.grant(grantType, tokenRequest);
		if (grant!=null) {
			return grant;
		}
	}
	return null;
}

逻辑也很简单,根据上传的grantType选择合适的TokenGranter,并调用其中的grant方法

TokenGranter还有一个抽象类,调用grant方法的时候,会先进入到这个抽象类中的方法

//以下代码位于:org.springframework.security.oauth2.provider.token.AbstractTokenGranter#grant
public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
	//TODO:校验grantType
	if (!this.grantType.equals(grantType)) {
		return null;
	}
	//获取ClientDetails
	String clientId = tokenRequest.getClientId();
	ClientDetails client = clientDetailsService.loadClientByClientId(clientId);
	validateGrantType(grantType, client);
	
	logger.debug("Getting access token for: " + clientId);
	//TODO:这里就是利用ClientDetails和tokenRequest创建AccessToken
	return getAccessToken(client, tokenRequest);

}

//org.springframework.security.oauth2.provider.token.AbstractTokenGranter#getAccessToken
protected OAuth2AccessToken getAccessToken(ClientDetails client, TokenRequest tokenRequest) {
	//TODO:在创建AccessToken之前,会先去获取OAuth2Authentication对象
	return tokenServices.createAccessToken(getOAuth2Authentication(client, tokenRequest));
}

构造OAuth2Authentication

构造OAuth2Authentication,不同的授权方式也会有不同,毕竟认证的方式不同。以密码授权模式为例。如果是密码的授权模式,则TokenGranter中getAccessToken中获取OAuth2Authentication对象的方法,最终会走到如下实例中

//以下代码位于:
//org.springframework.security.oauth2.provider.password.ResourceOwnerPasswordTokenGranter#getOAuth2Authentication
@Override
protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {

	Map<String, String> parameters = new LinkedHashMap<String, String>(tokenRequest.getRequestParameters());
	String username = parameters.get("username");
	String password = parameters.get("password");
	// Protect from downstream leaks of password
	parameters.remove("password");

    //初始化一个UsernamePasswordAuthenticationToken
	Authentication userAuth = new UsernamePasswordAuthenticationToken(username, password);
	((AbstractAuthenticationToken) userAuth).setDetails(parameters);
	try {
        //TODO:调用authenticate进行认证,这里实际就会调用我们自定义的认证UserDetailsService
		userAuth = authenticationManager.authenticate(userAuth);
	}
	catch (AccountStatusException ase) {
		//covers expired, locked, disabled cases (mentioned in section 5.2, draft 31)
		throw new InvalidGrantException(ase.getMessage());
	}
	catch (BadCredentialsException e) {
		// If the username/password are wrong the spec says we should send 400/invalid grant
		throw new InvalidGrantException(e.getMessage());
	}
	if (userAuth == null || !userAuth.isAuthenticated()) {
		throw new InvalidGrantException("Could not authenticate user: " + username);
	}
	//初始化 OAuth2Request(包含ClientDetails信息和TokenRequest)
	OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest);		
    //初始化OAuth2Authentication(包含OAuth2Request和认证成功之后的用户认证信息)
	return new OAuth2Authentication(storedOAuth2Request, userAuth);
}

//TODO:org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestFactory#createOAuth2Request(org.springframework.security.oauth2.provider.ClientDetails, org.springframework.security.oauth2.provider.TokenRequest)
//TODO:简单的将client传给tokenRequest创建OAuth2Request
public OAuth2Request createOAuth2Request(ClientDetails client, TokenRequest tokenRequest) {
	return tokenRequest.createOAuth2Request(client);
}

token的存储与创建

在创建完成了OAuth2Authentication之后,就是创建令牌了,spring-security-oauth提供了一个默认的tokenService的实现,名称为DefaultTokenServices,相关的源码如下

//TODO:位于org.springframework.security.oauth2.provider.token.DefaultTokenServices#createAccessToken(org.springframework.security.oauth2.provider.OAuth2Authentication)
@Transactional
public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException {
	//TODO:根据OAuth2Authentication获取之前的 AccessToken
	OAuth2AccessToken existingAccessToken = tokenStore.getAccessToken(authentication);
	OAuth2RefreshToken refreshToken = null;
	if (existingAccessToken != null) {//如果之前的 AccessToken 不为空,
		if (existingAccessToken.isExpired()) {//如果之前的AccessToken过期了
			if (existingAccessToken.getRefreshToken() != null) {
				refreshToken = existingAccessToken.getRefreshToken();
				// The token store could remove the refresh token when the
				// access token is removed, but we want to
				// be sure...
				tokenStore.removeRefreshToken(refreshToken);//移除
			}
			tokenStore.removeAccessToken(existingAccessToken);//移除
		}
		else {//如果没有过期,则重新存储一下原有的AccessToken 
			// Re-store the access token in case the authentication has changed
			tokenStore.storeAccessToken(existingAccessToken, authentication);
			return existingAccessToken;
		}
	}

	// Only create a new refresh token if there wasn't an existing one
	// associated with an expired access token.
	// Clients might be holding existing refresh tokens, so we re-use it in
	// the case that the old access token
	// expired.
	if (refreshToken == null) {//TODO:如果原有的AccessToken 为空,则创建一个刷新令牌,不是初始化的AccessToken
		refreshToken = createRefreshToken(authentication);
	}
	// But the refresh token itself might need to be re-issued if it has
	// expired.
	else if (refreshToken instanceof ExpiringOAuth2RefreshToken) {
		ExpiringOAuth2RefreshToken expiring = (ExpiringOAuth2RefreshToken) refreshToken;
		if (System.currentTimeMillis() > expiring.getExpiration().getTime()) {
			refreshToken = createRefreshToken(authentication);
		}
	}

    //TODO:创建 AccessToken
	OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken);
	tokenStore.storeAccessToken(accessToken, authentication);
	// In case it was modified
	refreshToken = accessToken.getRefreshToken();
	if (refreshToken != null) {
		tokenStore.storeRefreshToken(refreshToken, authentication);
	}
	return accessToken;

}

//TODO:org.springframework.security.oauth2.provider.token.DefaultTokenServices#createAccessToken(org.springframework.security.oauth2.provider.OAuth2Authentication, org.springframework.security.oauth2.common.OAuth2RefreshToken)
//TODO:创建 AccessToken
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());

    //TODO:如果有 accessTokenEnhancer 则调用其enhance方法,这个方法中可以完成对token的自定义
	return accessTokenEnhancer != null ? accessTokenEnhancer.enhance(token, authentication) : token;
}

总结

有点絮叨,有点凌乱,不过看完需要耐心,看到这里,再回过头看总结的图片,或许会清晰很多

spring-security学习(十二)——spring security oauth源码简析

上一篇:2.3.3 mysql 权限系统介绍


下一篇:SV-interface