最近做的项目要求进行oauth2认证,使用了spring security框架。使用体验,emmmm应该是肥肠不好。这可能和我自己的个人能力有关,security给人的感觉就是非常的重,扩展起来也比较麻烦。总之下次应该不会回购(希望不要真香)。不过既来之则安之,还是要写一下初步使用体验的。
认证过程
这次的认证过程是标准的授权码过程,简要概括如下:
客户端发出请求-》换取授权码-》授权码换取token-》使用token访问受保护的资源。
demo示例可以参考前一篇博客。
换取授权码过程
换取授权码的过程相对比较复杂,会涉及到几个页面之间的跳转。访问流程如下:

下面对以上的几个判断过程进行分别分析:
跳转login页面
从浏览器访问/oauth/authorize,首先会通过一系列的过滤器,如下所示:
通过一级一级的过滤器,最终到达最后一个过滤器FilterSecurityInterceptor,在这个过滤器里面的核心逻辑是以下的代码片:
public void invoke(FilterInvocation fi) throws IOException, ServletException {
if ((fi.getRequest() != null)
&& (fi.getRequest().getAttribute(FILTER_APPLIED) != null)
&& observeOncePerRequest) {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}
else {
// first time this request being called, so perform security checking
if (fi.getRequest() != null) {
fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
}
//判断是否有权限访问endpoint
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}finally {
super.finallyInvocation(token);
}
super.afterInvocation(token, null);
}
}
通过调用父类的beforeInvocation()方法,使用决策器判断
默认的决策器是AffirmativeBased,只要有一票deny就会拒绝访问,这里将抛出AccessDeniedException,被ExceptionTranslationFilter所捕获。
捕获异常之后,调用核心方法handleSpringSecurityException()来进行处理。跟进去会发现,这里会把这次的请求保存下来:
protected void sendStartAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, AuthenticationException reason) throws ServletException, IOException {
SecurityContextHolder.getContext().setAuthentication((Authentication)null);
//保存本次的请求
this.requestCache.saveRequest(request, response);
this.logger.debug("Calling Authentication entry point.");
//重定向
this.authenticationEntryPoint.commence(request, response, reason);
}
另外就是执行commence方法进行重定向,该方法执行的是LoginUrlAuthenticationEntryPoint类中的方法,根据类名和注释就可以很明确的知悉,这里将重定向到登录,首先是生成重定向地址/login,然后进行重定向操作
/**
* Performs the redirect (or forward) to the login form URL.
*/
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
String redirectUrl = null;
if (useForward) {
if (forceHttps && "http".equals(request.getScheme())) {
// First redirect the current request to HTTPS.
// When that request is received, the forward to the login page will be
// used.
redirectUrl = buildHttpsRedirectUrlForRequest(request);
}
if (redirectUrl == null) {
String loginForm = determineUrlToUseForThisRequest(request, response,
authException);
if (logger.isDebugEnabled()) {
logger.debug("Server side forward to: " + loginForm);
}
RequestDispatcher dispatcher = request.getRequestDispatcher(loginForm);
dispatcher.forward(request, response);
return;
}
}
else {
// redirect to login page. Use https if forceHttps true
redirectUrl = buildRedirectUrlToLoginPage(request, response, authException);
}
redirectStrategy.sendRedirect(request, response, redirectUrl);
}
登录操作
重定向后会出现默认的登录页面
获取这个默认页面的方式很简单,在DefaultLoginPageGeneratingFilter中会生成login页面,核心代码如下:
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
boolean loginError = isErrorPage(request);
boolean logoutSuccess = isLogoutSuccess(request);
if (isLoginUrlRequest(request) || loginError || logoutSuccess) {
//生成登录页面
String loginPageHtml = generateLoginPageHtml(request, loginError,
logoutSuccess);
response.setContentType("text/html;charset=UTF-8");
response.setContentLength(loginPageHtml.length());
response.getWriter().write(loginPageHtml);
return;
}
chain.doFilter(request, response);
}
授权流程
点击登陆后,再次进入filter chain,从之前缓存的request中拿到获取授权码的URL进行访问,到这一步,终于进入了AuthorizationEndpoint的/oauth/authorize。这里会先判断用户是否授权,如果没有授权则进入授权页面,源码如下:
换取授权码
点击授权之后,会再次发起请求,拿到缓存的request,将请求发送至/oauth/authorize的endpoint,这时授权状态变为true,会生成code,并重定向到请求中的URL地址
总结
spring security通过很多filter来控制认证授权的流程,另外也可以对filter chain进行自定义扩展,来实现自定义的认证。在认证过程中,较为复杂的是换取认证码。
当换取认证码成功后,再次访问相同的换取认证码URL,投票器不再投出deny的票,不会再定向到login page。而如果授权还在有效期之内,也不会再重定向到授权页面,将直接换取code。
参考文献
集成指南
登录分析