Spring Security 实战干货:客户端OAuth2授权请求的入口
1. 前言
在Spring Security 实战干货:OAuth2第三方授权初体验一文中我先对OAuth2.0涉及的一些常用概念进行介绍,然后直接通过一个DEMO来让大家切身感受了OAuth2.0第三方授权功能。今天我们来一步一步分析这其中的机制。
2. 抓住源头
上面这个请求URL是我们在上一篇文章中提到的客户端进行第三方认证操作的起点,默认格式为{baseUrl}/oauth2/authorization/{clientRegistrationId}
,其中clientRegistrationId
代表着一个第三方标识,可以是微信、支付宝等开放平台,这里为gitee
。用户点击了这个请求后就开始了授权之旅。假如大家都是从零开始的小白,肯定是要从这个入口来一步一步探寻其中的机制的。Spring Security一定是拦截到了/oauth2/authorization
后才启用了OAuth2相关的处理逻辑。那就去抓住这个源头!从源码中搜索嘛!IDEA 快捷键CTRL SHIFT R
就可以全局搜索结果了。
不出所料找到了三个地方,记下来一个一个看!
OAuth2AuthorizationRequestRedirectWebFilter
先来看第一个OAuth2AuthorizationRequestRedirectWebFilter
,它实现了Spring Webflux的WebFilter
接口,这显然是Webflux的东西,如果你用到Webflux的话这个会有用,但是不是现在我们用的。
DefaultOAuth2AuthorizationRequestResolver
第二个是干嘛的呢,从名称上看着是一个默认OAuth2授权请求解析器。有时候名称起的好就知道这个东西大致上干嘛的,不得不说优秀框架细节抓的很好。它实现了接口OAuth2AuthorizationRequestResolver
:
public interface OAuth2AuthorizationRequestResolver {
/**
* 从HttpServletRequest对象中解析封装 OAuth2AuthorizationRequest
*/
OAuth2AuthorizationRequest resolve(HttpServletRequest request);
/**
* 从HttpServletRequest对象以及clientRegistrationId中解析封装 OAuth2AuthorizationRequest
*/
OAuth2AuthorizationRequest resolve(HttpServletRequest request, String clientRegistrationId);
}
也就是说当我们请求/oauth2/authorization
时,DefaultOAuth2AuthorizationRequestResolver
会从/oauth2/authorization
对应的HttpServletRequest
中提取数据封装到OAuth2AuthorizationRequest
请求对象中做进一步使用。
注意:
/oauth2/authorization
这个默认拦截标识也是可以自定义的。
OAuth2AuthorizationRequest
这里简单提一下OAuth2AuthorizationRequest
封装了我们上一文所描述的一些OAuth2相关概念参数,后续这个请求类我们会用到它。
public final class OAuth2AuthorizationRequest implements Serializable {
private static final long serialVersionUID = 520L;
private String authorizationUri;
private AuthorizationGrantType authorizationGrantType;
private OAuth2AuthorizationResponseType responseType;
private String clientId;
private String redirectUri;
private Set<String> scopes;
private String state;
private Map<String, Object> additionalParameters;
private String authorizationRequestUri;
private Map<String, Object> attributes;
// 其它方法略
}
OAuth2AuthorizationRequestRedirectFilter
就剩下这个线索了,一看到它继承了OncePerRequestFilter
我就知道肯定是他了。甚至它的成员变量包含了用来解析OAuth2请求的OAuth2AuthorizationRequestResolver
。到这里我们的路子就走对了,开始分析这个过滤器,下面是其核心过滤逻辑,这就是我们想要知道的OAuth2授权请求是如何被拦截处理的逻辑。
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
try {
OAuth2AuthorizationRequest authorizationRequest = this.authorizationRequestResolver.resolve(request);
if (authorizationRequest != null) {
this.sendRedirectForAuthorization(request, response, authorizationRequest);
return;
}
} catch (Exception failed) {
this.unsuccessfulRedirectForAuthorization(request, response, failed);
return;
}
try {
filterChain.doFilter(request, response);
} catch (IOException ex) {
throw ex;
} catch (Exception ex) {
// Check to see if we need to handle ClientAuthorizationRequiredException
Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(ex);
ClientAuthorizationRequiredException authzEx = (ClientAuthorizationRequiredException) this.throwableAnalyzer
.getFirstThrowableOfType(ClientAuthorizationRequiredException.class, causeChain);
if (authzEx != null) {
try {
OAuth2AuthorizationRequest authorizationRequest = this.authorizationRequestResolver.resolve(request, authzEx.getClientRegistrationId());
if (authorizationRequest == null) {
throw authzEx;
}
this.sendRedirectForAuthorization(request, response, authorizationRequest);
this.requestCache.saveRequest(request, response);
} catch (Exception failed) {
this.unsuccessfulRedirectForAuthorization(request, response, failed);
}
return;
}
if (ex instanceof ServletException) {
throw (ServletException) ex;
} else if (ex instanceof RuntimeException) {
throw (RuntimeException) ex;
} else {
throw new RuntimeException(ex);
}
}
}
doFilterInternal
对应的流程如下:
根据这个流程,如果要搞清楚Spring Security OAuth2是如何重定向到第三方的话就要深入研究sendRedirectForAuthorization
方法,基于篇幅原因我会在下一篇进行分析。
3. 总结
今天我们从源头一步一步找到OAuth2授权的处理入口,并初步分析了几个关键组件的作用以及核心拦截器的拦截逻辑。后续我们将层层深入循序渐进地搞清楚其运作流程,不要走开,锁定公众号:码农小胖哥 循序渐进学习Spring Security OAuth2 。
评论系统未开启,无法评论!