博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Spring Security 源码解析(一)AbstractAuthenticationProcessingFilter
阅读量:6403 次
发布时间:2019-06-23

本文共 9915 字,大约阅读时间需要 33 分钟。

# 前言

 

最近在做 Spring OAuth2 登录,并在登录之后保存 Cookies。具体而言就是 Spring OAuth2 和 Spring Security 集成。Google一下竟然没有发现一种能满足我的要求。最终只有研究源码了。

有时间会画个 UML 图。

 

# 一些基础知识

 

  • Spring Security 验证身份的方式是利用 Filter,再加上 HttpServletRequest 的一些信息进行过滤。
  • 类 Authentication 保存的是身份认证信息。
  • 类 AuthenticationProvider 提供身份认证途径。
  • 类 AuthenticationManager 保存的 AuthenticationProvider 集合,并调用 AuthenticationProvider 进行身份认证。

 

# AbstractAuthenticationProcessingFilter 

 

## 设计模式

 

### 抽象工厂模式

 

AbstractAuthenticationProcessingFilter 是一个抽象类,主要的功能是身份认证。OAuth2ClientAuthenticationProcessingFilter(Spriing OAuth2)、RememberMeAuthenticationFilter(RememberMe)都继承了 AbstractAuthenticationProcessingFilter ,并重写了方法 attemptAuthentication 进行身份认证。

/**     * Performs actual authentication. 进行真正的认证。     * 

* The implementation should do one of the following: 具体实现需要做如下事情: *

    *
  1. Return a populated authentication token for the authenticated user, indicating * successful authentication
  2. 返回一个具体的 Authentication认证对象。 *
  3. Return null, indicating that the authentication process is still in progress. * Before returning, the implementation should perform any additional work required to * complete the process.
  4. 返回 null,表示实现的子类不能处理该身份认证,还需要别的类进行身份认证(往 FilterChain 传递)。 *
  5. Throw an AuthenticationException if the authentication process fails
  6. 抛出异常 AuthenticationException 表示认证失败。 *
* * @param request from which to extract parameters and perform the authentication * @param response the response, which may be needed if the implementation has to do a * redirect as part of a multi-stage authentication process (such as OpenID). * * @return the authenticated user token, or null if authentication is incomplete. * * @throws AuthenticationException if authentication fails. */ public abstract Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException;

这个方法的目的很明确,就是需要子类提供身份认证的具体实现。子类根据 HttpServletRequest 等信息进行身份认证,并返回 Authentication 对象、 null、异常,分别表示认证成功返回的身份认证信息、需要其他 Filter 继续进行身份认证、认证失败。下面是一个 OAuth2ClientAuthenticationProcessingFilter 对于方法 attemptAuthentication 的实现,具体代码的行为就不解释了。

@Override    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)            throws AuthenticationException, IOException, ServletException {        OAuth2AccessToken accessToken;        try {            accessToken = restTemplate.getAccessToken();        } catch (OAuth2Exception e) {            BadCredentialsException bad = new BadCredentialsException("Could not obtain access token", e);            publish(new OAuth2AuthenticationFailureEvent(bad));            throw bad;                    }        try {            OAuth2Authentication result = tokenServices.loadAuthentication(accessToken.getValue());            if (authenticationDetailsSource!=null) {                request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE, accessToken.getValue());                request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_TYPE, accessToken.getTokenType());                result.setDetails(authenticationDetailsSource.buildDetails(request));            }            publish(new AuthenticationSuccessEvent(result));            return result;        }        catch (InvalidTokenException e) {            BadCredentialsException bad = new BadCredentialsException("Could not obtain user details from token", e);            publish(new OAuth2AuthenticationFailureEvent(bad));            throw bad;                    }    }

 

至于方法 attemptAuthentication 是怎么被调用的?身份认证流程很简单,但是身份认证完成之前、完成之后,也需要做很多的操作。大部分操作都是一尘不变的,身份认证之前确认是否要进行身份验证、保存身份认证信息、成功处理、失败处理等。具体流程,在下面的方法中体现。可以看出这就是个工厂,已经确定好身份认证的流程,所以我们需要做的事情就是重写身份认证机制(方法 attemptAuthentication)就可以了。

/**     * Invokes the     * {
@link #requiresAuthentication(HttpServletRequest, HttpServletResponse) * requiresAuthentication} method to determine whether the request is for * authentication and should be handled by this filter. If it is an authentication * request, the * {
@link #attemptAuthentication(HttpServletRequest, HttpServletResponse) * attemptAuthentication} will be invoked to perform the authentication. There are * then three possible outcomes: *
    *
  1. An Authentication object is returned. The configured * {
    @link SessionAuthenticationStrategy} will be invoked (to handle any * session-related behaviour such as creating a new session to protect against * session-fixation attacks) followed by the invocation of * {
    @link #successfulAuthentication(HttpServletRequest, HttpServletResponse, FilterChain, Authentication)} * method
  2. *
  3. An AuthenticationException occurs during authentication. The * {
    @link #unsuccessfulAuthentication(HttpServletRequest, HttpServletResponse, AuthenticationException) * unsuccessfulAuthentication} method will be invoked
  4. *
  5. Null is returned, indicating that the authentication process is incomplete. The * method will then return immediately, assuming that the subclass has done any * necessary work (such as redirects) to continue the authentication process. The * assumption is that a later request will be received by this method where the * returned Authentication object is not null. *
*/ public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; // 是否需要身份认证 if (!requiresAuthentication(request, response)) { // 不需要身份认证,传递到 FilterChain 继续过滤 chain.doFilter(request, response); return; } if (logger.isDebugEnabled()) { logger.debug("Request is to process authentication"); } Authentication authResult; try { // 进行身份认证,该方法需要子类重写 authResult = attemptAuthentication(request, response); if (authResult == null) { // return immediately as subclass has indicated that it hasn't completed // authentication return; } // 身份认证成功,保存 session sessionStrategy.onAuthentication(authResult, request, response); } // 身份认证代码出错 catch (InternalAuthenticationServiceException failed) { logger.error( "An internal error occurred while trying to authenticate the user.", failed); // 身份认证失败一系列事物处理,包括调用 RememberMeServices 等 unsuccessfulAuthentication(request, response, failed); return; } // 身份认证失败异常 catch (AuthenticationException failed) { // Authentication failed // 身份认证失败一系列事物处理,包括调用 RememberMeServices 等 unsuccessfulAuthentication(request, response, failed); return; } // Authentication success // 身份认证成功之后是否需要传递到 FilterChain if (continueChainBeforeSuccessfulAuthentication) { chain.doFilter(request, response); } // 身份认证成功一系列事物处理,包括调用 RememberMeServices 等 successfulAuthentication(request, response, chain, authResult); }}

 

### 策略模式

 

这里还可以看一下方法 doFilter 的内部调用,比如下面这个方法。

/**     * Default behaviour for successful authentication.     * 
    *
  1. Sets the successful Authentication object on the * {
    @link SecurityContextHolder}
  2. *
  3. Informs the configured RememberMeServices of the successful login
  4. *
  5. Fires an {
    @link InteractiveAuthenticationSuccessEvent} via the configured * ApplicationEventPublisher
  6. *
  7. Delegates additional behaviour to the {
    @link AuthenticationSuccessHandler}.
  8. *
* * Subclasses can override this method to continue the {
@link FilterChain} after * successful authentication. * @param request * @param response * @param chain * @param authResult the object returned from the attemptAuthentication * method. * @throws IOException * @throws ServletException */ protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { if (logger.isDebugEnabled()) { logger.debug("Authentication success. Updating SecurityContextHolder to contain: " + authResult); } // 认证成功设置身份认证信息 SecurityContextHolder.getContext().setAuthentication(authResult); // RememberMeServices 设置成功登录信息,如 Cookie 等 rememberMeServices.loginSuccess(request, response, authResult); // 认证成功发送事件 // Fire event if (this.eventPublisher != null) { eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent( authResult, this.getClass())); } // 认证成功处理器 successHandler.onAuthenticationSuccess(request, response, authResult); }

Spring Security 还是很贴心的把这个方法的修饰符设定成了 protected,以满足我们重写身份认证成功之后的机制,虽然大多数情况下并不需要。不需要的原因是认证成功之后的流程基本最多也就是这样,如果想改变一些行为,可以直接传递给 AbstractAuthenticationProcessingFilter 一些具体实现即可,如 AuthenticationSuccessHandler(认证成功处理器)。根据在这个处理器内可以进行身份修改、返回结果修改等行为。下面是该对象的定义。

private AuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();

各种各样的 AuthenticationSuccessHandler 可以提供多种多样的认证成功行为,这是一种策略模式。

 

# 后记

 

Spring Security 采取了多种设计模式,这是 Spring 家族代码的一贯特性。让人比较着急的是,Spring Security 虽然可以做到开箱即用,但是想要自定义代码的话,必须要熟悉 Spring Security 代码。比如如何使用 RememberMeServices。RememberMeService 有三个方法,登录成功操作、登录失败操作、自动登录操作。你可以重写这些方法,但你如果不看源码,你无法得知这些方法会在什么时候调用、在哪个 Filter 中调用、需要做什么配置。

 

转载于:https://www.cnblogs.com/Piers/p/8620523.html

你可能感兴趣的文章
Hikari连接池使用SpringBoot配置JMX监控
查看>>
15.当心局部块函数声明笨拙的作用域
查看>>
BFC模型浅识
查看>>
maven入门总结
查看>>
在Angular中操作DOM:意料之外的结果及优化技术
查看>>
编写一个webpack的loader(1)
查看>>
《金三银四面试系列》— jvm与性能优化
查看>>
iOS K线三方库 - ZXKLine
查看>>
必须明白的浏览器渲染机制
查看>>
Linux 内核101:异步IO
查看>>
UINavigationBar 的详解 (基于 API)
查看>>
太坊智能合约开发第一篇:IDE对solidity语法的支持
查看>>
web-audio-api可视化音乐播放器,实现暂停切换歌曲功能,粉色系专场~
查看>>
Fiddler抓包和修改WebSocket数据,支持wss
查看>>
Python知识点总结篇(五)
查看>>
戴老师的学习验收(一,二)
查看>>
站在巨人的肩膀上:原生JS实现基于Promise/a+规范的Promise(篇一)
查看>>
MySQL数据库优化分析
查看>>
jQuery源码解析之width()
查看>>
【从蛋壳到满天飞】JS 数据结构解析和算法实现-红黑树(二)
查看>>