OAuth2AuthenticationManager源码跟踪
本文仅助那些在Security集成OAuth2.0路上踩坑的人理解认证过程。
OAuth2AuthenticationManager
首先,我觉得分析OAuth2AuthenticationManager,我们需要先理解它是如何被使用,何时被使用,那我们就从这里开始逐一分析吧,我手画了一张图作为索引,以便我们理解,其中蓝色的为类,绿色的为接口,箭头指向的方向是实现类或者父类。
因为里面接口和方法过多,所以不做展示,下面具体来进行代码分析。
public class OAuth2AuthenticationManager implements AuthenticationManager, InitializingBean {
private ResourceServerTokenServices tokenServices;
private ClientDetailsService clientDetailsService;
private String resourceId;
public void setResourceId(String resourceId) {
this.resourceId = resourceId;
}
public void setClientDetailsService(ClientDetailsService clientDetailsService) {
this.clientDetailsService = clientDetailsService;
}
public void setTokenServices(ResourceServerTokenServices tokenServices) {
this.tokenServices = tokenServices;
}
public void afterPropertiesSet() {
Assert.state(tokenServices != null, "TokenServices are required");
}
(3) public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if (authentication == null) {
throw new InvalidTokenException("Invalid token (token not found)");
}
(3).1 String token = (String) authentication.getPrincipal();
(3).2 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 + ")");
}
(3).3 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());
(3).4 auth.setAuthenticated(true);
return auth;
}
private void checkClientDetails(OAuth2Authentication auth) {
if (clientDetailsService != null) {
ClientDetails client;
try {
client = clientDetailsService.loadClientByClientId(auth.getOAuth2Request().getClientId());
}
catch (ClientRegistrationException e) {
throw new OAuth2AccessDeniedException("Invalid token contains invalid client id");
}
Set<String> allowed = client.getScope();
for (String scope : auth.getOAuth2Request().getScope()) {
if (!allowed.contains(scope)) {
throw new OAuth2AccessDeniedException(
"Invalid token contains disallowed scope (" + scope + ") for this client");
}
}
}
}
}
OAuth2AuthenticationManager的成员变量
ResourceServerTokenServices
以上是它的关系图,关于该接口,了解即可,不必过于深究,有兴趣的小伙伴们可以去翻看源码,学习一下代码结构也是不错的。
DefaultTokenServices 利用Security内置的令牌存储器(Tokenstore)接口进行数据库的CRUD操作,查看数据库结构及表说明
ClientDetailsService
ClientDetailsService 用于加载客户端,有两种实现方式,一种是基于内存,一种是基于存储库的方式。
auth-server: http://localhost:18081/uac
server:
port: 18082
security:
oauth2:
client:
client-id: client1
client-secret: 201314
user-authorization-uri: ${auth-server}/oauth/authorize
access-token-uri: ${auth-server}/oauth/token
resource:
jwt:
key-uri: ${auth-server}/oauth/token_key
key-value: 201314
上面是关于客户端的配置,auth-server 为资源服务器路径。
authenticate()
- (3).1处的代码,期望传入的身份验证请求具有一个主体值,该主体值是一个访问令牌值(一般在(authorization header)请求头中)
- (3).2处的代码,ResourceServerTokenServices通过查询数据库中 oauth_client_details该表,加载身份验证。
- (3).3处的代码,检查资源id是否包含在授权请求中。
- (3).4,通过身份认证,Security将OAuth2Authentication对象存入Session中,然后跳转到AuthorizationEndpoint的authorize()。该方法跳出一个授权页面,提供授权的通过权或否决权。
以上就是我对于OAuth2AuthenticationManager源码的理解,仅供参考,如有不正确的地方,请提出指正。