Spring Security小教程 Vol 3. 身份验证的入口-AbstractAuthenticationProcessingFilter

前言

结合上一期咱们介绍的AuthenticationManager为入口的身份验证的核心模块,咱们此次讨论的是为了使SpringSecurity对Spring Web项目提供支持,做为验证请求入口的\color{red}{AbstractAuthenticationProcessingFilter}html

第三期 Authentication核心简介

本期的任务清单

  1. AbstractAuthenticationProcessingFilter的依赖组件;
  2. AbstractAuthenticationProcessingFilter依赖组件的主要职责和相关设计动机。

1. AbstractAuthenticationProcessingFilter处理Request及与AuthenticationManager交互的流程

AbstractAuthenticationProcessingFilter的主要职责和依赖组件

了解AbstractAuthenticationProcessingFilter大概是干吗的的最简单的方法就是直接去读api-doc。git

Abstract processor of browser-based HTTP-based authentication requests.github

官方文档说的很明白了:处理基于浏览器交互的HTTP验证请求。因此AbstractAuthenticationProcessingFilter的职责也就很是明确——处理全部HTTP Request和Response对象,并将其封装成AuthenticationMananger能够处理的Authentication。而且在身份验证成功或失败以后将对应的行为转换为HTTP的Response。同时还要处理一些Web特有的资源好比Session和Cookie。总结成一句话,就是替AuthenticationMananger把全部和Authentication不要紧的事情所有给包圆了。 web

雷锋AbstractAuthenticationProcessingFilter

继续读JavaDoc能够得知AbstractAuthenticationProcessingFilter为了完成组织上交代的与浏览器和HTTP请求的验证任务。它将大任务拆成了几个子任务并交给了如下组件完成:spring

  1. AuthenticationManager用于处理身份验证的核心逻辑;
  2. AuthenticationSuccessHandler用于处理验证成功的后续流程;
  3. AuthenticationFailureHandler用于处理失败的后续流程;
  4. 在验证成功后发布一个名为InteractiveAuthenticationSuccessEvent的事件通知给到应用上下文,用于告知身份验证已经成功;
  5. 由于是基于浏览器因此相关的会话管理行为交由 SessionAuthenticationStrategy来进行实现。
  6. 文档上还有一点没有写出来的是,若是用户开启了相似“记住我”之类的免密码登陆,AbstractAuthenticationProcessingFilter还有一个名为RememberMeServices来进行管理。

AbstractAuthenticationProcessingFilter主要组件

AbstractAuthenticationProcessingFilter的验证流程

AbstractAuthenticationProcessingFilter本质上仍是个Filter,其核心的业务入口方法就是doFilter方法: api

官方API文档
这里咱们先设置一个问题,而后带着问题去分析AbstractAuthenticationProcessingFilter的doFilter都是怎么设计解决这些问题的?

  1. 怎么判断当前的请求是须要被验证访问的?
  2. 如何进行身份验证?
  3. 如何进行会话验证?动机是什么?
  4. 成功和失败的后续流程都在干什么?
  5. Remember-Me功能实现流程是什么?
  6. 如何在其余服务中监听验证成功的事件?

问题1. 怎么判断当前的请求是须要被验证访问的?

在正式进行身份以前,doFilter会经过Security中的\color{red}{RequestMatcher}。尝试查找是否有匹配记录。 咱们回顾下以前咱们写过的访问控制的代码:浏览器

@Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                // inde.html对应的url容许所任人访问
                .antMatchers("/").permitAll()
                // user.html对应的url,则须要用户有USER的角色才能够访问
                .antMatchers("/user").hasRole("USER")
                .and()
                .formLogin();
    }
复制代码

其中的matcher的规则便会在这个流程中预先被检查,若是须要进行身份验证则会进行写一个阶段:对请求进行必要的身份验证。安全

问题2. 如何进行身份验证?

doFilter中经过调用本身的attemptAuthentication方法,但并不进行身份验证的逻辑处理,而是委托AuthenticationManager去完成相关的身份验证流程。AbstractAuthenticationProcessingFilter将HttpServletRequest包装成了Authentication对象与核心的AuthenticationManager进行交互。这样的设计可使AuthenticationManager不感知外部的Web环境,从而使Security不只能够支持Web应用,同时也能够被全部Java应用进行使用——只要客制化外部参与并将其封装成Authentication与AuthenticationManager的进行身份验证。 bash

身份验证委托与AuthenticationManager进行实现

这里还须要注意的是在AuthenticationManager中实际完成身份验证任务并非AuthenticationManager它本身身。而是将相关的任务针对每一种身份验证协议的AuthenticationProvider去完成相关的身份验证工做。 cookie

官方文档attemptAuthentication方法

问题3. 如何进行会话验证管理?动机是什么?

会话验证策略

第一个概念会话验证\color{red}{SessionAuthentication}是什么的一个概念?咱们知道HTTP的请求其实是无状态的,浏览器为了使HTTP之间能使用同一会话进行操做,在Java Web中一般会将Web容器(特指Tomcat)的JSESSIONID写入请求的cookie中一同发送。

cookie中的JSESSIONID
而服务端中经过中的JSESSIONID是经过request.getSession().getId()获取的,这样便使原本无状态的HTTP经过客户端的cookie中JSESSIONID与服务端的Session关联了起来。 可是从安全角度来讲这样匹配机制存在许多问题,最简单的问题就是Session id在整个会话失效之间是不会变动的,这样就能够经过身份验证经过后获取了Session id从而经过其余客户端伪造cookie与服务端进行交互。有兴趣的同窗能够针对这问题去了解下客户端cookie、服务端session以及一些CSRF攻击的介绍。本人比较推荐这篇http://hengyunabc.github.io/slides/cookie-and-session-and-csrf.html#1。

针对不一样的会话管理策略场景,Security也提供了相应的实现,有机会再单独开一篇单独来介绍相关的策略。这边就先了解下,在完成了AuthenticationManager的身份验证后,还会对其进行必要的会话验证。

提供的几种会话验证策略

问题4. 成功和失败的后续流程都在干什么?

验证成功与失败流程控制

验证成功以后AuthenticationManager会返回一个经过UserDetail构造而且附带上了全部受权信息的Authentication对象。 而验证失败的话则会抛出\color{red}{AuthenticationException},AbstractAuthenticationProcessingFilter捕获异常以后进行进行验证失败的处理。 成功的后续操做最主要的一个操做即是,经过SecurityContextHolder将本次验证以后的Authentication对象塞到当前的SecurityContext中。在后续的操做中如须要使用到Authentication身份信息,则能够直接经过SecurityContextHolder去获取。

//成功后设置上下文二
    SecurityContextHolder.getContext().setAuthentication(authResult);

     //后续操做能够从上下文中获取身份信息
     SecurityContextHolder.getContext().getAuthentication();
复制代码

而后再经过ApplicationEventPublisher发送验证成功的事件信息供其余相关监听器进行相关操做。

操做失败就简单了,既然成功是从新将最新的Authentication对象塞到SecurityContext上下文中,失败即是直接清空了上下文,让其Authentication对象变得“一无全部”。

而其余的对于request的额外操做则能够分别经过\color{red}{AuthenticationSuccessHandler}\color{red}{AuthenticationFailureHandler}两个接口去设置相关操做。

AuthenticationSuccessHandler
那么哪些工做属于验证成功后还须要额外操做的呢?举个最简单的例子,在用户想访问一个受限的资源,他首先被重定向掉了登陆页面让其输入用户名和密码,而在其验证成功以后,那么他是讲指向到指定的某一个页面仍是重定向到本次操做原本想访问的受限资源的路径呢? 这些相关的操做即是在AuthenticationSuccessHandler中进行完成的。 一样的若是登陆失败须要作一些除了身份验证之外,有须要感知HTTP请求、响应对象的操做,一样的也能够在AuthenticationFailureHandler中进行完成。

问题5. Remember-Me功能实现流程是什么?

浏览器特点服务RememberMe快速登陆

Remember-Me是指网站可以在Session之间记住登陆用户的身份,具体来讲就是我成功认证一次以后在必定的时间内我能够不用再输入用户名和密码进行登陆了,系统会自动给我登陆。这一般是经过服务端发送一个cookie给客户端浏览器,下次浏览器再访问服务端时服务端可以自动检测客户端的cookie,根据cookie值触发自动登陆操做。 实现方式有不少种,通常来讲最简单的实现就是将用户名与一些其余字符组合进行编码,而后服务端解码以后提取出相关其中的用户名,经过UserDetailsService获取相关用户信息的用户验证方式。 Spring Security中提供了两种Remember-Me机制进行使用,若是有其余实现方式也能够经过继承AbstractRememberMeServices类进行扩展。请必定牢记Remember-Me机制的现实是依赖浏览器Cookie的,在默认状况下SpringSecurity会将编码后的字符串存于Cookie中的remember-me键位。

RememberMeServices

#问题6. 如何在其余服务中监听验证成功的事件

在完整整个验证流程以后,AbstractAuthenticationProcessingFilter还会经过Spring容器的\color{red}{ApplicationEventPublisher}事件发布器发射一个\color{red}{InteractiveAuthenticationSuccessEvent}。若是须要在应用其余监听器上处理相关验证成功后操做。咱们能够经过Spring中的@EventListener监听InteractiveAuthenticationSuccessEvent事件即可以实现。 写一个示例,若是咱们想在每一个用户登陆成功后,在控制台打印出登陆用户的用户名。

@Component
public class AuthenticationListener {
    @EventListener
    public void register(InteractiveAuthenticationSuccessEvent event)
    {
        //获取登陆成功的Authentication对象
        Authentication authentication = event.getAuthentication();
        //打印用户名
        System.out.println("@EventListener注册信息,用户名:"+authentication.getName());
    }
}
复制代码

结尾

本期花了很大的篇幅介绍了整个Web验证流程的核心组件AbstractAuthenticationProcessingFilter。下一期咱们将结合他最经常使用的实现类UsernamePasswordAuthenticationFilter作一个讲解,但愿经过讲解UsernamePasswordAuthenticationFilter实现使你们了解客制化一个验证协议须要注意的细节。 咱们下期再见。

相关文章
相关标签/搜索