zoukankan      html  css  js  c++  java
  • SpringSecurity原理(一)

    一、认证的两种方式的介绍      

          1. 基于Session的认证方式

           在之前的单体架构时代,我们认证成功之后都会将信息存入到Session中,然后响应给客户端的是对应的Session中数据的key,客户端会将这个key存储在cookie中,之后都请求都会携带这个cookie中的信息,结构图如下

        

       但是随着技术的更新迭代,我们在项目架构的时候更多的情况下会选择前后端分离或者分布式架构,那么在这种情况下基于session的认证方式就显露了很多的不足,列举几个明显的特点:

    1. cookie存储的内容有限制4k
    2. cookie的有效范围是当前域名下,所以在分布式环境下或者前后端分离的项目中都不适用,及时要用也会很麻烦
    3. 服务端存储了所有认证过的用户信息

      2.基于Token的认证方式

        相较于Session对需求的兼容,基于Token的方式便是我们在当下项目中处理认证和授权的实现方式的首先了,Token的方式其实就是在用户认证成功后便把用户信息通过加密封装到了Token中,在响应客户端的时候会将Token信息传回给客户端,当下一次请求到来的时候在请求的Http请求的head的Authentication中会携带token

    二、SSO和OAuth2.0流程浅析

         前面介绍了下认证信息的实现方式,接下来看下我们在分布式环境下会经常碰到的两种解决方案SSO和OAuth2.0

       1.SSO

          SSO也就是我们经常听到的单点登录,是我们在分布式环境下认证实现的解决方案,具体流程如下:当系统发生第一次访问请求时,系统1肯定是没有提供认证的,这时请求就会跳转到认证中心,如果没有认证就会跳转到登录页面,在登录页面输入正确的帐号密码就可以完成认证过程;认证完成后就会创建一个全局对话及权限令牌;然后再次访问系统1时就会效验令牌信息是否正确,如果正确就可以正常访问业务流程;

          2.OAuth2.0

                  2.1、定义

      OAuth是一个开发标准,在这种标准下允许用户让第三方应用访问该用户在某一网站上存储的私密的资源数据,无须将用户名和密码提供给第三方应用。OAuth2.0是OAuth协议的第二个版本,关注客户端开发者的简易性,同时为Web应用、桌面应用和手机等设备提供专门的认证流程。

            2.2、基本原理

    OAuth在第三方应用与服务提供商之间设置了一个授权层。第三方应用不能直接登录服务提供商,只能登录授权层,以此将用户与客户端区分开来。第三方应用登录授权层所用的令牌,与用户的密码不同。用户可以在登录授权的时候,指定授权层令牌的权限范围和有效期。 第三方应用登录授权层以后,服务提供商根据令牌的权限范围和有效期,向第三方应用开放用户资源。
    2.3、作用

    让客户端安全可控地获取用户的授权,与服务提供商之间进行交互。可以免去用户同步的麻烦,同时也增加了用户信息的安全。

    2.4、常用应用场景

       单点登录解决的分布式系统中统一认证的问题,还有一种情况是一个新的系统用户就需要去注册一个账号,用户管理的账号越多越麻烦,为了解决这个问题,当前系统就期望使用你的其他系统的资料来作为认证的信息,比如 微信,QQ,微博等,这时候就该OAuth2.0,实现流程如下

    三、SpringSecurity介绍

        1.基础环境准备

        我们要深入的研究SpringSecurity的原理,那么我们首先需要搭建这样的一个环境,在现在的实际开发中使用SpringBoot来构建项目的占比越来越高,所以我们应该要通过SpringBoot来研究SpringSecurity的原理,但是SpringSecurity的封装比较厉害,所以对于源码的分析会不是很友好,所以我们会先基于XML构架的方式来介绍,然后再在SpringBoot项目中来研究。 

            1.1基于XML文件的方式构建项目

         我们通过Spring+SpringMVC+SpringSecurity来构建基于XML的项目

    pom.xml

    <?xml version="1.0" encoding="UTF-8"?>

    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>SpringSecurityDemo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <name>SpringSecurityDemo Maven Webapp</name>
    <!-- FIXME change it to the project's website -->
    <url>http://www.example.com</url>

    <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.7</maven.compiler.source>
    <maven.compiler.target>1.7</maven.compiler.target>
    </properties>

    <dependencies>
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.2.1.RELEASE</version>
    </dependency>
    <dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>servlet-api</artifactId>
    <version>2.5</version>
    <scope>provided</scope>
    </dependency>
    <dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.25</version>
    </dependency>
    <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.11</version>
    <scope>test</scope>
    </dependency>
    <dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-config</artifactId>
    <version>5.1.5.RELEASE</version>
    </dependency>
    <dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-taglibs</artifactId>
    <version>5.1.5.RELEASE</version>
    </dependency>
    </dependencies>

    <build>
    <finalName>SpringSecurityDemo</finalName>
    <plugins>
    <plugin>
    <groupId>org.apache.tomcat.maven</groupId>
    <artifactId>tomcat7-maven-plugin</artifactId>
    <version>2.2</version>
    <configuration>
    <port>8080</port> <!-- 访问端口-->
    <path>/</path> <!-- 访问路径-->
    </configuration>
    </plugin>
    </plugins>
    <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
    <plugins>
    <plugin>
    <artifactId>maven-clean-plugin</artifactId>
    <version>3.1.0</version>
    </plugin>
    <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
    <plugin>
    <artifactId>maven-resources-plugin</artifactId>
    <version>3.0.2</version>
    </plugin>
    <plugin>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.8.0</version>
    </plugin>
    <plugin>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.22.1</version>
    </plugin>
    <plugin>
    <artifactId>maven-war-plugin</artifactId>
    <version>3.2.2</version>
    </plugin>
    <plugin>
    <artifactId>maven-install-plugin</artifactId>
    <version>2.5.2</version>
    </plugin>
    <plugin>
    <artifactId>maven-deploy-plugin</artifactId>
    <version>2.8.2</version>
    </plugin>
    </plugins>
    </pluginManagement>
    </build>
    </project>

     WEB.XML

    <!DOCTYPE web-app PUBLIC
            "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
            "http://java.sun.com/dtd/web-app_2_3.dtd" >
    
    <web-app version="2.5" id="WebApp_ID" xmlns="http://java.sun.com/xml/ns/javaee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
     http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
      <display-name>Archetype Created Web Application</display-name>
    
      <!-- 初始化spring容器 -->
      <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext.xml</param-value>
      </context-param>
      <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
      </listener>
    
      <!-- post乱码过滤器 -->
      <filter>
        <filter-name>CharacterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
          <param-name>encoding</param-name>
          <param-value>utf-8</param-value>
        </init-param>
      </filter>
      <filter-mapping>
        <filter-name>CharacterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
      </filter-mapping>
      <!-- 前端控制器 -->
      <servlet>
        <servlet-name>dispatcherServletb</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- contextConfigLocation不是必须的, 如果不配置contextConfigLocation,
        springmvc的配置文件默认在:WEB-INF/servlet的name+"-servlet.xml" -->
        <init-param>
          <param-name>contextConfigLocation</param-name>
          <param-value>classpath:springmvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
      </servlet>
      <servlet-mapping>
        <servlet-name>dispatcherServletb</servlet-name>
        <!-- 拦截所有请求jsp除外 -->
        <url-pattern>/</url-pattern>
      </servlet-mapping>
    
      <!-- 配置过滤器链 springSecurityFilterChain名称固定-->
      <filter>
        <filter-name>springSecurityFilterChain</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
      </filter>
      <filter-mapping>
        <filter-name>springSecurityFilterChain</filter-name>
        <url-pattern>/*</url-pattern>
      </filter-mapping>
    
    
    </web-app>
    

    login.jsp

    <%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
    <html>
    <head>
        <title>Title</title>
    </head>
    <body>
    <h1>登录管理</h1>
    
    <form action="/login" method="post">
        账号:<input type="text" name="username"><br>
        密码:<input type="password" name="password"><br>
        <input type="submit" value="登录"><br>
    </form>
    </body>
    </html>
    

    springsecurity.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:p="http://www.springframework.org/schema/p"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xmlns:security="http://www.springframework.org/schema/security"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
        http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.2.xsd
        http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-4.2.xsd">
    
        <!--
            auto-config="true" 表示自动加载SpringSecurity的配置文件
            use-expressions="true" 使用Spring的EL表达式
         -->
        <security:http auto-config="true" use-expressions="true">
    
            <security:intercept-url pattern="/login.jsp" access="permitAll()"></security:intercept-url>
            <!--<security:intercept-url pattern="/login.do" access="permitAll()"></security:intercept-url>-->
    
            <!--
                拦截资源
                pattern="/**" 拦截所有的资源
                access="hasAnyRole('role1')" 表示只有role1这个角色可以访问资源
             -->
            <security:intercept-url pattern="/**" access="hasAnyRole('ROLE_USER')"></security:intercept-url>
    
            <!--
                配置认证信息
                 login-page="/login.jsp"  自定义的登录页面
                 login-processing-url="/login" security中处理登录的请求
                 default-target-url="/home.jsp" 默认的跳转地址
                 authentication-failure-url="/failure.jsp" 登录失败的跳转地址
    
            <security:form-login
                login-page="/login.jsp"
                login-processing-url="/login"
                default-target-url="/home.jsp"
                authentication-failure-url="/failure.jsp"
            />-->
            <!-- 配置退出的登录信息
            <security:logout logout-url="/logout"
                             logout-success-url="/login.jsp" />
            <security:csrf disabled="true"/>-->
        </security:http>
    
        <!-- 设置认证用户来源  noop:SpringSecurity中默认 密码验证是要加密的  noop表示不加密 -->
        <security:authentication-manager>
            <security:authentication-provider>
                <security:user-service>
                    <security:user name="zhang" password="{noop}123" authorities="ROLE_USER"></security:user>
                    <security:user name="lisi" password="{noop}123" authorities="ROLE_ADMIN"></security:user>
                </security:user-service>
            </security:authentication-provider>
        </security:authentication-manager>
    
    </beans>
    

    springmvc.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
        http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.2.xsd">
    
        <!-- 配置扫描路径-->
        <context:component-scan base-package="com.ghy.security.controller"
                                use-default-filters="false">
            <context:include-filter type="annotation"
                                    expression="org.springframework.stereotype.Controller" />
        </context:component-scan>
    </beans>
    
    log4j.properties
    log4j.rootCategory=debug, stdout

    log4j.appender.stdout=org.apache.log4j.ConsoleAppender
    log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
    log4j.appender.stdout.layout.ConversionPattern=[ghy] %p [%t] %C.%M(%L) | %m%n

    applicationContext.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:p="http://www.springframework.org/schema/p"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
        http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.2.xsd">
    
        <import resource="classpath:springsecurity.xml"/>
    
    </beans>
    

      启动项目然后访问,发现是可以访问到登录页面的,这个页面是默认的

    输入帐号密码可以发现能跳转

     

    然后输入注销网址发现也能注销进入登录页面

     

     这一顿操作下来发现,这登录、注销页面都不是我们定义的,是默认的,这默认页面怎么来的,怎么替换;这是第一个问题,第二,现在的帐号密码是在配置文件中配置的,如果想从数据库中获取怎么获取以及怎么效验

     2.原理分析

          首先启动我们构建的项目然后访问 http://localhost:8080/index.html 因为访问的是非匿名访问的页面,所以会直接调整到系统默认的登陆界面,那么如果我们要使用自定义的登陆界面应该怎么去实现?还有我们要探究下进入默认登陆界面前整个SpringSecurity到底做了什么事情?所以我们可以从下面这三方面来深入的研究
    1.  系统启动springSecurity做了啥
    2. 页面是怎么出来的
    3. 认证流程是怎么实现的
    想要搞清楚上面问题,首先我们要请求一个web请求的基本流程如下

     

    请求从客户端发起(例如浏览器),然后穿过层层 Filter,最终来到 Servlet 上,被 Servlet 所处理。那么权限管理框架在设计的时候肯定也是基于Filter来实现的。那么SpringSecurity的处理结构应该是这样的

     

    有了这个基本的认知后我们就可以来具体的分析SpringSecurity的核心原理了,为了便于大家的理解,我们先从客户端发送请求开始到显示登陆界面这条流程开始来分析。

      2.1.第一次请求的流程梳理

           基于XML的方式分析DelegatingFilterProxy

     当我们在浏览器地址栏发送http://localhost:8080/index.html的时候在服务端会被 web.xml 中配置的DelegatingFilterProxy这个过滤器拦截 

     

      <!-- 配置过滤器链 springSecurityFilterChain名称固定-->
      <filter>
        <filter-name>springSecurityFilterChain</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
      </filter>
      <filter-mapping>
        <filter-name>springSecurityFilterChain</filter-name>
        <url-pattern>/*</url-pattern>
      </filter-mapping>
    

     所以请求的流程图应该是这样的 

         但是要注意 DelegatingFilterProxy 并不是SpringSecurity提供给我们的,而是Spring框架之前就提供的有的,DelegatingFilterProxy就是一个对于servlet filter的代理,用这个类的好处主要是通过Spring容器来管理servlet filter的生命周期,还有就是如果filter中需要一些Spring容器的实例,可以通过spring直接注入,另外读取一些配置文件这些便利的操作都可以通过Spring来配置实现。DelegatingFilterProxy继承自GenericFilterBean,在Servlet容器启动的时候回执行GenericFilterBean的init方法,

     

      public final void init(FilterConfig filterConfig) throws ServletException {
            Assert.notNull(filterConfig, "FilterConfig must not be null");
            this.filterConfig = filterConfig;
            PropertyValues pvs = new GenericFilterBean.FilterConfigPropertyValues(filterConfig, this.requiredProperties);
            if (!pvs.isEmpty()) {
                try {
                    BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
                    ResourceLoader resourceLoader = new ServletContextResourceLoader(filterConfig.getServletContext());
                    Environment env = this.environment;
                    if (env == null) {
                        env = new StandardServletEnvironment();
                    }
    
                    bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, (PropertyResolver)env));
                    this.initBeanWrapper(bw);
                    bw.setPropertyValues(pvs, true);
                } catch (BeansException var6) {
                    String msg = "Failed to set bean properties on filter '" + filterConfig.getFilterName() + "': " + var6.getMessage();
                    this.logger.error(msg, var6);
                    throw new NestedServletException(msg, var6);
                }
            }
                         // 另外在init方法中调用了initFilterBean()方法,该方法是GenericFilterBean类是特地留给子类扩展用的 
            this.initFilterBean();
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Filter '" + filterConfig.getFilterName() + "' configured for use");
            }
    
        }
    

      

    DelegatingFilterProxy的initFilterBean方法,这个方法在系统启动时就会执行这个方法,因为这是一个初始化的方法。这里面是一个同步代码块
     protected void initFilterBean() throws ServletException {
            synchronized(this.delegateMonitor) {
    //这个delegate是一个过滤器 if (this.delegate == null) { if (this.targetBeanName == null) {
                                             // 获取的是在web.xml文件中设置的DelegatingFilterProxy过滤器对应的
                                              // filterName【springSecurityFilterChain】 
                        this.targetBeanName = this.getFilterName();
                    }
                                         // 获取IoC容器对象 
                    WebApplicationContext wac = this.findWebApplicationContext();
                    if (wac != null) {
                                                // 获取委托处理请求的过滤器,在此处获取的是 FilterChainProxy 
                        this.delegate = this.initDelegate(wac);
                    }
                }
    
            }
        }
    

    上面的方法首先获取在web.xml中配置的FilterName的值也就是 springSecurityFilterChain,然后再获取Spring的IoC容器对象,如果容器对象不为空,然后执行this.initDelegate(wac);方法

    protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
                        // 获取的值为 springSecurityFilterChain 
            String targetBeanName = this.getTargetBeanName();
            Assert.state(targetBeanName != null, "No target bean name set");
                        // 从SpringIoC容器中根据 springSecurityFilterChain 这个名称获取bean对象
                       // 所以在web.xml中我们配置的FilterName必须得是springSecurityFilterChain否则的话是获取不到的
            Filter delegate = (Filter)wac.getBean(targetBeanName, Filter.class);
            if (this.isTargetFilterLifecycle()) {
                       // 如果有设置targetFilterLifecycle为true就会执行委托过滤器的init方法 
                delegate.init(this.getFilterConfig());
            }
    
            return delegate;
        }
    

      

    通过上面这个流程init()初始化过程就结束了,通过上面源码的解析我们能够发现DelegatingFilterProxy这个过滤器在初始的时候从Spring容器中获取了 FilterChainProxy 这个过滤器链的代理对象,并且把这个对象保存在了DelegatingFilterProxy的delegate属性中。那么当请求到来的时候会执行DelegatingFilterProxy的doFilter方法,那么我们就可以来看下这个方法里面又执行了什么

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
            Filter delegateToUse = this.delegate;
                       // if中的代码已经在初始化阶段完成了 
            if (delegateToUse == null) {
                synchronized(this.delegateMonitor) {
                    delegateToUse = this.delegate;
                    if (delegateToUse == null) {
                        WebApplicationContext wac = this.findWebApplicationContext();
                        if (wac == null) {
                            throw new IllegalStateException("No WebApplicationContext found: no ContextLoaderListener or DispatcherServlet registered?");
                        }
    
                        delegateToUse = this.initDelegate(wac);
                    }
    
                    this.delegate = delegateToUse;
                }
            }
                     // 核心流程就是该代码 
            this.invokeDelegate(delegateToUse, request, response, filterChain);
        }
    

      invokeDelegate是doFilter中的核心代码,字面含义就是调用委托对象。从具体源码来看也确实如此

    protected void invokeDelegate(Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
                 // 调用FilterChainProxy的doFilter方法
         delegate.doFilter(request, response, filterChain);
        }
    

      通过上面的代码分析我们发现,当一个请求到来的时候先通过DelegatingFilterProxy来拦截,但是DelegatingFilterProxy不处理具体的逻辑,而是将具体的处理操作交给了delegate过滤器来处理也就是FilterChainProxy来处理。

         FilterChainProxy分析 
           通过上面的分析我们也能发现FilterChainProxy在整个请求过程中的位置了,如下
          

    至于FilterChainProxy怎么来的会在介绍系统初始化的时候会介绍到这块儿的内容的。对于FilterChainProxy怎么处理请求的,根据上面的内容我们知道我们可以直接看doFilter方法即可

     public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
            boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
            if (clearContext) {
                try {
                    request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
                    this.doFilterInternal(request, response, chain);
                } finally {
                    SecurityContextHolder.clearContext();
                    request.removeAttribute(FILTER_APPLIED);
                }
            } else {
    //跟进 this.doFilterInternal(request, response, chain); } }

     

     private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
                          // 对request对象做了一次防火墙检查,校验提交的方式是否合法【post,get等】
                         // 校验请求的地址是否在黑名单中 
            FirewalledRequest fwRequest = this.firewall.getFirewalledRequest((HttpServletRequest)request);
            HttpServletResponse fwResponse = this.firewall.getFirewalledResponse((HttpServletResponse)response);
                       // 获取处理这个请求的过滤器链中的所有的过滤器
            List<Filter> filters = this.getFilters((HttpServletRequest)fwRequest);
            if (filters != null && filters.size() != 0) {
                                 // 如果有过滤器则构建一个虚拟的过滤器链路,然后执行 
                FilterChainProxy.VirtualFilterChain vfc = new FilterChainProxy.VirtualFilterChain(fwRequest, chain, filters);
                vfc.doFilter(fwRequest, fwResponse);
            } else {
                if (logger.isDebugEnabled()) {
                    logger.debug(UrlUtils.buildRequestUrl(fwRequest) + (filters == null ? " has no matching filters" : " has an empty filter list"));
                }
    
                fwRequest.reset();
                               // 如果没有要执行的过滤器 则通过外部的过滤器链执行下一个servlet 过滤器来处理 
                chain.doFilter(fwRequest, fwResponse);
            }
        }
    

     上面方法中的核心代码第一个是this.getFilters((HttpServletRequest)fwRequest);在这个方法要注意一个概念就是在SpringSecurity中可以存在多个过滤器链,而每个过滤器链又可以包含多个过滤器

     

     

     

    那么在第一次访问到来的时候会选择哪个过滤器链然后又包含哪些具体的过滤器呢?
    可以看到此处有15个过滤器。 通过vfc.doFilter(fwRequest, fwResponse);我们可以发现是被这15过滤器按照固定先后顺序来执行的。
     public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
                if (this.currentPosition == this.size) {
                    if (FilterChainProxy.logger.isDebugEnabled()) {
                        FilterChainProxy.logger.debug(UrlUtils.buildRequestUrl(this.firewalledRequest) + " reached end of additional filter chain; proceeding with original chain");
                    }
    
                    this.firewalledRequest.reset();
                    this.originalChain.doFilter(request, response);
                } else {
                                        // size=15 currentPosition会从0~15 一个个的取出过滤器 
                    ++this.currentPosition;
                    Filter nextFilter = (Filter)this.additionalFilters.get(this.currentPosition - 1);
                    if (FilterChainProxy.logger.isDebugEnabled()) {
                        FilterChainProxy.logger.debug(UrlUtils.buildRequestUrl(this.firewalledRequest) + " at position " + this.currentPosition + " of " + this.size + " in additional filter chain; firing Filter: '" + nextFilter.getClass().getSimpleName() + "'");
                    }
                                // 调用取出来的过滤器的doFilter方法,此处有用到设计模式中的责任链模式 
                    nextFilter.doFilter(request, response, this);
                }
    
            }
        }
    

     

    ExceptionTranslationFilter 

     在整个过滤器链中,ExceptionTranslationFilter是倒数第二个执行的过滤器,它的作用是通过catch处理下一个Filter【也就是FilterSecurityInterceptor】或应用逻辑产生的异常

    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
            HttpServletRequest request = (HttpServletRequest)req;
            HttpServletResponse response = (HttpServletResponse)res;
    
            try {
                                 // 如果下个过滤器出现异常进入下一个过滤器 FilterSecurityInterceptor中 
                chain.doFilter(request, response);
                this.logger.debug("Chain processed normally");
            } catch (IOException var9) {
                throw var9;
            } catch (Exception var10) {
                                 // FilterSecurityInterceptor有显示的抛出异常 就处理 
                Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(var10);
                RuntimeException ase = (AuthenticationException)this.throwableAnalyzer.getFirstThrowableOfType(AuthenticationException.class, causeChain);
                if (ase == null) {
                    ase = (AccessDeniedException)this.throwableAnalyzer.getFirstThrowableOfType(AccessDeniedException.class, causeChain);
                }
    
                if (ase == null) {
                    if (var10 instanceof ServletException) {
                        throw (ServletException)var10;
                    }
    
                    if (var10 instanceof RuntimeException) {
                        throw (RuntimeException)var10;
                    }
    
                    throw new RuntimeException(var10);
                }
    
                if (response.isCommitted()) {
                    throw new ServletException("Unable to handle the Spring Security Exception because the response is already committed.", var10);
                }
    
                this.handleSpringSecurityException(request, response, chain, (RuntimeException)ase);
            }
    
        }
    

      

    handleSpringSecurityException返回就是处理下一个Filter抛出的异常
    private void handleSpringSecurityException(HttpServletRequest request, HttpServletResponse response, FilterChain chain, RuntimeException exception) throws IOException, ServletException {
            if (exception instanceof AuthenticationException) {
                this.logger.debug("Authentication exception occurred; redirecting to authentication entry point", exception);
                this.sendStartAuthentication(request, response, chain, (AuthenticationException)exception);
            } else if (exception instanceof AccessDeniedException) {
                Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
                if (!this.authenticationTrustResolver.isAnonymous(authentication) && !this.authenticationTrustResolver.isRememberMe(authentication)) {
                    this.logger.debug("Access is denied (user is not anonymous); delegating to AccessDeniedHandler", exception);
                    this.accessDeniedHandler.handle(request, response, (AccessDeniedException)exception);
                } else {
                    this.logger.debug("Access is denied (user is " + (this.authenticationTrustResolver.isAnonymous(authentication) ? "anonymous" : "not fully authenticated") + "); redirecting to authentication entry point", exception);
                    this.sendStartAuthentication(request, response, chain, new InsufficientAuthenticationException(this.messages.getMessage("ExceptionTranslationFilter.insufficientAuthentication", "Full authentication is required to access this resource")));
                }
            }
    
        }
    
    关键方法:sendStartAuthentication 

      

      protected void sendStartAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, AuthenticationException reason) throws ServletException, IOException {
            SecurityContextHolder.getContext().setAuthentication((Authentication)null);
                        // 保存本次请求的request信息,当认证成功会跳转到这个请求的地址 
            this.requestCache.saveRequest(request, response);
            this.logger.debug("Calling Authentication entry point.");
            this.authenticationEntryPoint.commence(request, response, reason);
        }
    
    AuthenticationEntryPoint【认证入口点】中的commence方法 

      

    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
            String redirectUrl = null;
            if (this.useForward) {
                if (this.forceHttps && "http".equals(request.getScheme())) {
                    redirectUrl = this.buildHttpsRedirectUrlForRequest(request);
                }
    
                if (redirectUrl == null) {
                    String loginForm = this.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 {
                               // 获取重定向的地址 默认是 http://localhost:8080/login 
                redirectUrl = this.buildRedirectUrlToLoginPage(request, response, authException);
            }
                            // 获取重定向的策略 然后发送重定向的地址
            this.redirectStrategy.sendRedirect(request, response, redirectUrl);
        }
    

     sendRedirect 

     public void sendRedirect(HttpServletRequest request, HttpServletResponse response, String url) throws IOException {
            String redirectUrl = this.calculateRedirectUrl(request.getContextPath(), url);
            redirectUrl = response.encodeRedirectURL(redirectUrl);
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Redirecting to '" + redirectUrl + "'");
            }
                         // 重定向跳转 如果是自定义的登陆界面 那么此处就直接跳转到自定义的认证界面了。
            response.sendRedirect(redirectUrl);
        }
    

      此处重定向的地址是 .../login 该请求会被DefaultLoginPageGeneratingFilter过滤器拦截,具体看下面对DefaultLoginPageGeneratingFilter的介绍 

    FilterSecurityInterceptor
       FilterSecurityInterceptor是SpringSecurity过滤器链中的最后一个过滤器,作用是先判断是否身份验证,然后在做权限的验证。第一次访问的时候处理的在doFilter中的方法的关键代码如下:
     public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
            FilterInvocation fi = new FilterInvocation(request, response, chain);
            this.invoke(fi);
        }
    

      decide方法在做投票选举,第一次的时候回抛出AccessDeniedException异常,而抛出的异常会被ExceptionTranslationFilter中的catch语句块捕获,进而执行handleSpringSecurityException方法。 

     
    DefaultLoginPageGeneratingFilter 
         DefaultLoginPageGeneratingFilter该过滤器的作用从字面含义也能够看出来就是生成默认登陆界面的一个过滤器
         
     private void init(UsernamePasswordAuthenticationFilter authFilter, AbstractAuthenticationProcessingFilter openIDFilter) {
                      // 初始化方法中定义的有要拦截的 登陆页面的Url 
            this.loginPageUrl = "/login";
            this.logoutSuccessUrl = "/login?logout";
            this.failureUrl = "/login?error";
            if (authFilter != null) {
                this.formLoginEnabled = true;
                this.usernameParameter = authFilter.getUsernameParameter();
                this.passwordParameter = authFilter.getPasswordParameter();
                if (authFilter.getRememberMeServices() instanceof AbstractRememberMeServices) {
                    this.rememberMeParameter = ((AbstractRememberMeServices)authFilter.getRememberMeServices()).getParameter();
                }
            }
    
            if (openIDFilter != null) {
                this.openIdEnabled = true;
                this.openIDusernameParameter = "openid_identifier";
                if (openIDFilter.getRememberMeServices() instanceof AbstractRememberMeServices) {
                    this.openIDrememberMeParameter = ((AbstractRememberMeServices)openIDFilter.getRememberMeServices()).getParameter();
                }
            }
    
        }
    

      然后在doFilter方法中有判断请求的地址是否是'/login',如果是的话就拦截,否则就放过

         
     public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
            HttpServletRequest request = (HttpServletRequest)req;
            HttpServletResponse response = (HttpServletResponse)res;
            boolean loginError = this.isErrorPage(request);
            boolean logoutSuccess = this.isLogoutSuccess(request);
            if (!this.isLoginUrlRequest(request) && !loginError && !logoutSuccess) {
                               // 不是默认进入登陆界面的请求放过 
                chain.doFilter(request, response);
            } else {
                                  // 生成默认页面的html页面 
                String loginPageHtml = this.generateLoginPageHtml(request, loginError, logoutSuccess);
                response.setContentType("text/html;charset=UTF-8");
                                  // 响应请求,中断了过滤器链的执行
                response.setContentLength(loginPageHtml.getBytes(StandardCharsets.UTF_8).length);
                response.getWriter().write(loginPageHtml);
            }
        }
    

      

    3.基于SpringBoot方式的分析

          基于配置文件的方式我们清楚了DelegatingFilterProxy是如何处理的,下面分析下在SpringBoot中也是如何处理的

            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-security</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
    

     然后再写一个登录后页面

    启动项目后会发现控制台生成了一个密码

     登录到登录页面帐号是user,密码就是控制台生成的密码

     点击登录 可以进入我们定义的登录后页面

     接下来就要分析入口,基于SpringBoot的自动装配我们知道整合第三方框架要加载的信息是在spring.factories中,如下

     

    其中有三个我们需要关注的会自动装配的配置类 

    //初始化的核心配置类
    org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguratio n,
    //默认的USER帐号配置类
    org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoCo nfiguration,
    org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfig uration,

      这三个中和DelegatingFilterProxy有关系的是第三个 SecurityFilterAutoConfiguration 所以我们就直接先来看这个

    @Bean
    	@ConditionalOnBean(name = DEFAULT_FILTER_NAME)
    	public DelegatingFilterProxyRegistrationBean securityFilterChainRegistration(
    			SecurityProperties securityProperties) {
    //这段代码和前面讲的拦截器比较类似,我们可以点进去看下做了啥 DelegatingFilterProxyRegistrationBean registration = new DelegatingFilterProxyRegistrationBean( DEFAULT_FILTER_NAME); registration.setOrder(securityProperties.getFilter().getOrder()); registration.setDispatcherTypes(getDispatcherTypes(securityProperties)); return registration; }

      进去之后看下结构,我们发现有个ServletContextInitializer,这个是上下文初始化的接口,我们进入看下

    进入ServletContextInitializer类,进入发现只有一个入口onStartup,先他的RegistraitonBean实现
    @FunctionalInterface
    public interface ServletContextInitializer {
        void onStartup(ServletContext servletContext) throws ServletException;
    }
    

     

    进入RegistrationBean的onStartup方法中
    public final void onStartup(ServletContext servletContext) throws ServletException {
            String description = this.getDescription();
            if (!this.isEnabled()) {
                logger.info(StringUtils.capitalize(description) + " was not registered (disabled)");
            } else {
                            // 进入register方法中查看 
                this.register(description, servletContext);
            }
        }
    
    进入DynamicRegistrationBean中查看(不会找实现的可以看我上面的类关系图)
    protected final void register(String description, ServletContext servletContext) {
                        // 此处会添加对应的过滤器,也就是DelegatingFilterProxy  
            D registration = this.addRegistration(description, servletContext);
            if (registration == null) {
                logger.info(StringUtils.capitalize(description) + " was not registered (possibly already registered?)");
            } else {
                this.configure(registration);
            }
        }
    
    先查看下addRegistration方法 进入的是AbstractFilterRegistrationBean它中的方法 
    protected Dynamic addRegistration(String description, ServletContext servletContext) {
            Filter filter = this.getFilter();
                         // 改过滤器会被添加到servletContext容器中,那么当有满足添加的请求到来的时候就会触发该过滤器拦截
            return servletContext.addFilter(this.getOrDeduceName(filter), filter);
        }
    

     然后再看getFilter方法,可以看到真实的创建了一个DelegatingFilterProxy对象并且指定的名声是springSecurityFilterChain也就是DelegatingFilterProxy代理的Spring容器的Filter的名称

      public DelegatingFilterProxy getFilter() {
               //这个过滤器名称就是外面写死的那个
    //这new的玩意其实就和前面XML文件配置的springSecurityFilterChain过滤器一样
          return new DelegatingFilterProxy(this.targetBeanName, this.getWebApplicationContext()) {
    protected void initFilterBean() throws ServletException { } }; }

     找到过滤器后我们向上返回一级,servletContext.addFilter(this.getOrDeduceName(filter), filter);把过滤器加载到上下文中去,然后再向上返一级看下this.configure(registration)方法,注意这个方法我们要进入AbstractFilterRegistrationBean中查看 

     

    protected void configure(Dynamic registration) {
            super.configure(registration);
            EnumSet<DispatcherType> dispatcherTypes = this.dispatcherTypes;
            if (dispatcherTypes == null) {
                T filter = this.getFilter();
                if (ClassUtils.isPresent("org.springframework.web.filter.OncePerRequestFilter", filter.getClass().getClassLoader()) && filter instanceof OncePerRequestFilter) {
                    dispatcherTypes = EnumSet.allOf(DispatcherType.class);
                } else {
                    dispatcherTypes = EnumSet.of(DispatcherType.REQUEST);
                }
            }
    
            Set<String> servletNames = new LinkedHashSet();
            Iterator var4 = this.servletRegistrationBeans.iterator();
    
            while(var4.hasNext()) {
                ServletRegistrationBean<?> servletRegistrationBean = (ServletRegistrationBean)var4.next();
                servletNames.add(servletRegistrationBean.getServletName());
            }
    
            servletNames.addAll(this.servletNames);
            if (servletNames.isEmpty() && this.urlPatterns.isEmpty()) {
                               // 如果没有指定对应的servletNames和urlPartterns的话就使用默认的名称和拦截地址 /* 
                registration.addMappingForUrlPatterns(dispatcherTypes, this.matchAfter, DEFAULT_URL_MAPPINGS);
            } else {
                if (!servletNames.isEmpty()) {
                    registration.addMappingForServletNames(dispatcherTypes, this.matchAfter, StringUtils.toStringArray(servletNames));
                }
    
                if (!this.urlPatterns.isEmpty()) {
                    registration.addMappingForUrlPatterns(dispatcherTypes, this.matchAfter, StringUtils.toStringArray(this.urlPatterns));
                }
            }
    
        }
    

     

    DEFAULT_URL_MAPPINGS默认的是 /*

     

     至此我们看到了在SpringBoot中是通过DelegatingFilterProxyRegistrationBean 帮我们创建了一个DelegatingFilterProxy过滤器并且指定了拦截的地址,默认是 /* ,之后的逻辑就和前面介绍的XML中的就是一样的了,请求会进入FilterChainProxy中开始处理

    4.SpringSecurity初始化到底经历了什么 

     通过对 第一次请求的流程梳理 会看到一个FilterChainProxy 至于他是啥时候创建的及默认的过滤器链和过滤器是怎么来的,下面就看下SpringSecurity初始化的时候到底做了哪些事情,基于XML的初始化阶段其实就是各种解析器对标签的解析,过程比较繁琐这里我们就不去分析了,我们直接在SpringBoot项目中来分析,在SpringBoot项目中分析SpringSecurity的初始化过程显然我们需要从 spring.factories 中的SecurityAutoConfiguration开始  

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(DefaultAuthenticationEventPublisher.class)
    @EnableConfigurationProperties(SecurityProperties.class)
    @Import({ SpringBootWebSecurityConfiguration.class, WebSecurityEnablerConfiguration.class,
    SecurityDataConfiguration.class })
    public class SecurityAutoConfiguration {
            // 定义了一个默认的事件发布器 

    @Bean
    @ConditionalOnMissingBean(AuthenticationEventPublisher.class)
    public DefaultAuthenticationEventPublisher authenticationEventPublisher(ApplicationEventPublisher publisher) {
    return new DefaultAuthenticationEventPublisher(publisher);
    }

    }
    从上面注解可以看到一个配置类的注解@EnableConfigurationProperties(SecurityProperties.class),点击SecurityProperties类,看到里面配置
    @ConfigurationProperties(
        prefix = "spring.security"
    )
    public class SecurityProperties {
        public static final int BASIC_AUTH_ORDER = 2147483642;
        public static final int IGNORED_ORDER = -2147483648;
        public static final int DEFAULT_FILTER_ORDER = -100;
        private final SecurityProperties.Filter filter = new SecurityProperties.Filter();
        private final SecurityProperties.User user = new SecurityProperties.User();
    
        public SecurityProperties() {
        }
    
        public SecurityProperties.User getUser() {
            return this.user;
        }
    
        public SecurityProperties.Filter getFilter() {
            return this.filter;
        }
    
        public static class User {
            private String name = "user";
            private String password = UUID.randomUUID().toString();
            private List<String> roles = new ArrayList();
            private boolean passwordGenerated = true;
    
            public User() {
            }

    从上面我们很容易看到,如果我们想要更改他的原有帐号密码只用在配置文件 中如下配置

    spring.security.user.name=admin
    spring.security.user.password=admin

    返回上一层继续,该类引入了 SpringBootWebSecurityConfiguration WebSecurityEnablerConfiguration  SecurityDataConfiguration 这三个类,如果还要分析的话入口就只能是这三个类了

    4.1、SpringBootWebSecurityConfiguration

    @Configuration(
        proxyBeanMethods = false
    )
    @ConditionalOnClass({WebSecurityConfigurerAdapter.class})
    @ConditionalOnMissingBean({WebSecurityConfigurerAdapter.class})
    @ConditionalOnWebApplication(
        type = Type.SERVLET
    )
    public class SpringBootWebSecurityConfiguration {
        public SpringBootWebSecurityConfiguration() {
        }
    
        @Configuration(
            proxyBeanMethods = false
        )
    //加载的优先级 @Order(2147483642) static class DefaultConfigurerAdapter extends WebSecurityConfigurerAdapter { DefaultConfigurerAdapter() { } } }
    这个配置的作用是在如果开发者没有自定义 WebSecurityConfigurerAdapter 的话,这里提供一个默认的实现。@ConditionalOnMissingBean({WebSecurityConfigurerAdapter.class}) 如果有自定义的WebSecurityConfigurerAdapter那么这个配置也就不会起作用了 

    4.2、WebSecurityEnablerConfiguration

    该配置类会在SpringBootWebSecurityConfiguration 注入 Spring IoC 容器后启用 @EnableWebSecurity 注解。也就是说 WebSecurityEnablerConfiguration 目的仅仅就是在某些条件下激活 @EnableWebSecurity 注解。

    @Configuration(
        proxyBeanMethods = false
    )
    @ConditionalOnBean({WebSecurityConfigurerAdapter.class})
    @ConditionalOnMissingBean(
        name = {"springSecurityFilterChain"}
    )
    @ConditionalOnWebApplication(
        type = Type.SERVLET 
    )
    @EnableWebSecurity
    public class WebSecurityEnablerConfiguration {
        public WebSecurityEnablerConfiguration() {
        }
    }
    最关键的就是@EnableWebSecurity注解了,我们点进去玩玩
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.TYPE})
    @Documented
    @Import({WebSecurityConfiguration.class, SpringWebMvcImportSelector.class, OAuth2ImportSelector.class})
    @EnableGlobalAuthentication
    @Configuration
    public @interface EnableWebSecurity {
        boolean debug() default false;
    }
    
    在这个接口中我们看到做的事情还是蛮多的,导入了三个类型和一个@EnableGlobalAuthentication注解,我们需要重点来看下WebSecurityConfiguration配置类和@EnableGlobalAuthentication注解

    4.3、WebSecurityConfiguration

    该配置类WebSecurityConfiguration使用一个WebSecurity对象基于用户指定的或者默认的安全配置,你可以通过继承 WebSecurityConfigurerAdapter 或者实现 WebSecurityConfigurer 来定制 WebSecurity 创建一个FilterChainProxy Bean来对用户请求进行安全过滤。这个FilterChainProxy的名称就是 WebSecurityEnablerConfiguration上的 BeanIds.SPRING_SECURITY_FILTER_CHAIN 也就是 springSecurityFilterChain,它是一个Filter,最终会被作为Servlet过滤器链中的一个Filter应用到Servlet容器中。安全处理的策略主要是过滤器的调用顺序。WebSecurityConfiguration 最终会通过 @EnableWebSecurity 应用到系统。

     package org.springframework.security.config.annotation.web.configuration;
     
      import java.util.Collections;
      import java.util.List;
      import java.util.Map;
     
      import javax.servlet.Filter;
     
      import org.springframework.beans.factory.BeanClassLoaderAware;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.beans.factory.annotation.Value;
      import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      import org.springframework.context.annotation.DependsOn;
      import org.springframework.context.annotation.ImportAware;
      import org.springframework.core.OrderComparator;
      import org.springframework.core.Ordered;
      import org.springframework.core.annotation.AnnotationAttributes;
      import org.springframework.core.annotation.AnnotationUtils;
      import org.springframework.core.annotation.Order;
      import org.springframework.core.type.AnnotationMetadata;
      import org.springframework.security.access.expression.SecurityExpressionHandler;
      import org.springframework.security.config.annotation.ObjectPostProcessor;
      import org.springframework.security.config.annotation.SecurityConfigurer;
      import org.springframework.security.config.annotation.web.WebSecurityConfigurer;
      import org.springframework.security.config.annotation.web.builders.WebSecurity;
      import org.springframework.security.context.DelegatingApplicationListener;
      import org.springframework.security.web.FilterChainProxy;
      import org.springframework.security.web.FilterInvocation;
      import org.springframework.security.web.access.WebInvocationPrivilegeEvaluator;
      import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
     
     
      /**
       * Spring Web Security 的配置类 :
       *  1. 使用一个 WebSecurity 对象基于安全配置创建一个 FilterChainProxy 对象来对用户请求进行安全过滤。
       *  2. 也会暴露诸如 安全SpEL表达式处理器 SecurityExpressionHandler 等一些类。
       *
       * @see EnableWebSecurity
       * @see WebSecurity
       *
       * @author Rob Winch
       * @author Keesun Baik
       * @since 3.2
       */
      @Configuration
      public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware {
          private WebSecurity webSecurity;
       // 是否启用了调试模式,来自注解 @EnableWebSecurity 的属性 debug,缺省值 false
          private Boolean debugEnabled;
     
          private List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers;
     
          private ClassLoader beanClassLoader;
     
          @Autowired(required = false)
          private ObjectPostProcessor<Object> objectObjectPostProcessor;
        /**
         *
         * 代理监听器 应该时监听 DefaultAuthenticationEventPublisher 的一些处理策略
         */   
          @Bean
          public static DelegatingApplicationListener delegatingApplicationListener() {
              return new DelegatingApplicationListener();
          }
         /**
          *
          * 安全SpEL表达式处理器 SecurityExpressionHandler 缺省为一个 DefaultWebSecurityExpressionHandler
          */   
          @Bean
          @DependsOn(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
          public SecurityExpressionHandler<FilterInvocation> webSecurityExpressionHandler() {
              return webSecurity.getExpressionHandler();
          }
     
          /**
           *  Spring Security 核心过滤器  Spring Security Filter Chain  , Bean ID 为 springSecurityFilterChain
           * @return the {@link Filter} that represents the security filter chain
           * @throws Exception
           */
          @Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
          public Filter springSecurityFilterChain() throws Exception {
              boolean hasConfigurers = webSecurityConfigurers != null
                      && !webSecurityConfigurers.isEmpty();
              if (!hasConfigurers) {
                  WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor
                          .postProcess(new WebSecurityConfigurerAdapter() {
                          });
                  webSecurity.apply(adapter);
              }
    // 获取到的就是FilterChainProxy 该对象会被保存到IoC容器中 且name
    springSecurityFilterChain
              return webSecurity.build();
          }
     
          /**
         *
         * 用于模板 如JSP Freemarker 的一些页面标签按钮控制支持
           * Creates the {@link WebInvocationPrivilegeEvaluator} that is necessary for the JSP
           * tag support.
           * @return the {@link WebInvocationPrivilegeEvaluator}
           * @throws Exception
           */
          @Bean
          @DependsOn(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
          public WebInvocationPrivilegeEvaluator privilegeEvaluator() throws Exception {
              return webSecurity.getPrivilegeEvaluator();
          }
     
          /**
         *
         * 用于创建web configuration的SecurityConfigurer实例,
         * 注意该参数通过@Value(...)方式注入,对应的bean autowiredWebSecurityConfigurersIgnoreParents
         * 也在该类中定义
         *
           * @param objectPostProcessor the {@link ObjectPostProcessor} used to create a
           * {@link WebSecurity} instance
           * @param webSecurityConfigurers the
           * {@code <SecurityConfigurer<FilterChainProxy, WebSecurityBuilder>} instances used to
           * create the web configuration
           * @throws Exception
           */
          @Autowired(required = false)
          public void setFilterChainProxySecurityConfigurer(
                  ObjectPostProcessor<Object> objectPostProcessor,
                  @Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers)
                  throws Exception {
              webSecurity = objectPostProcessor
                      .postProcess(new WebSecurity(objectPostProcessor));
              if (debugEnabled != null) {
                  webSecurity.debug(debugEnabled);
              }
     
              Collections.sort(webSecurityConfigurers, AnnotationAwareOrderComparator.INSTANCE);
     
              Integer previousOrder = null;
              Object previousConfig = null;
              for (SecurityConfigurer<Filter, WebSecurity> config : webSecurityConfigurers) {
                  Integer order = AnnotationAwareOrderComparator.lookupOrder(config);
                  if (previousOrder != null && previousOrder.equals(order)) {
                      throw new IllegalStateException(
                              "@Order on WebSecurityConfigurers must be unique. Order of "
                                      + order + " was already used on " + previousConfig + ", so it cannot be used on "
                                      + config + " too.");
                  }
                  previousOrder = order;
                  previousConfig = config;
              }
              for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) {
                  webSecurity.apply(webSecurityConfigurer);
              }
              this.webSecurityConfigurers = webSecurityConfigurers;
          }
         /**
          * 从当前bean容器中获取所有的WebSecurityConfigurer bean。
          * 这些WebSecurityConfigurer通常是由开发人员实现的配置类,并且继承自WebSecurityConfigurerAdapter
          *
          */   
          @Bean
          public static AutowiredWebSecurityConfigurersIgnoreParents autowiredWebSecurityConfigurersIgnoreParents(
                  ConfigurableListableBeanFactory beanFactory){
              return new AutowiredWebSecurityConfigurersIgnoreParents(beanFactory);
          }
     
          /**
           * A custom verision of the Spring provided AnnotationAwareOrderComparator that uses
           * {@link AnnotationUtils#findAnnotation(Class, Class)} to look on super class
           * instances for the {@link Order} annotation.
           *
           * @author Rob Winch
           * @since 3.2
           */
          private static class AnnotationAwareOrderComparator extends OrderComparator {
              private static final AnnotationAwareOrderComparator INSTANCE = new AnnotationAwareOrderComparator();
     
              @Override
              protected int getOrder(Object obj) {
                  return lookupOrder(obj);
              }
     
              private static int lookupOrder(Object obj) {
                  if (obj instanceof Ordered) {
                      return ((Ordered) obj).getOrder();
                  }
                  if (obj != null) {
                      Class<?> clazz = (obj instanceof Class ? (Class<?>) obj : obj.getClass());
                      Order order = AnnotationUtils.findAnnotation(clazz, Order.class);
                      if (order != null) {
                          return order.value();
                      }
                  }
                  return Ordered.LOWEST_PRECEDENCE;
              }
          }
     
          /*
           * 要是为了获取注解 @EnableWebSecurity 的属性 debugEnabled
           *
           * @see org.springframework.context.annotation.ImportAware#setImportMetadata(org.
           * springframework.core.type.AnnotationMetadata)
           */
          public void setImportMetadata(AnnotationMetadata importMetadata) {
              Map<String, Object> enableWebSecurityAttrMap = importMetadata
                      .getAnnotationAttributes(EnableWebSecurity.class.getName());
              AnnotationAttributes enableWebSecurityAttrs = AnnotationAttributes
                      .fromMap(enableWebSecurityAttrMap);
              debugEnabled = enableWebSecurityAttrs.getBoolean("debug");
              if (webSecurity != null) {
                  webSecurity.debug(debugEnabled);
              }
          }
     
          /*
           * (non-Javadoc)
           *
           * @see
           * org.springframework.beans.factory.BeanClassLoaderAware#setBeanClassLoader(java.
           * lang.ClassLoader)
           */
          public void setBeanClassLoader(ClassLoader classLoader) {
              this.beanClassLoader = classLoader;
          }
      }

     4.4、SpringWebMvcImportSelector

     该类是为了对 Spring Mvc 进行支持的。一旦发现应用使用 Spring Mvc 的核心前置控制器 DispatcherServlet 就会引入 WebMvcSecurityConfiguration 。主要是为了适配 Spring Mvc 。

        4.5、OAuth2ImportSelector

    该类是为了对 OAuth2.0 开放授权协议进行支持。ClientRegistration 如果被引用,具体点也就是 spring-security-oauth2 模块被启用(引入依赖jar)时。会启用 OAuth2 客户端配置 OAuth2ClientConfiguration 。

    4.6、@EnableGlobalAuthentication

    这个类主要引入了 AuthenticationConfiguration 目的主要为了构造 认证管理器 AuthenticationManager ;这个注解就做两件事,第一件是:创建一个ObjectPostProcessorConfiguration后置处理器的对象,帮我们把对象注入容器中;第二件事是创建一个认证的管理器

    @Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
    @Target(value = { java.lang.annotation.ElementType.TYPE })
    @Documented
    //认证的配置类
    @Import(AuthenticationConfiguration.class)
    @Configuration
    public @interface EnableGlobalAuthentication {
    }

    进入认证配置类 AuthenticationConfiguration类

    /*
     * Copyright 2002-2019 the original author or authors.
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *      https://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */
    package org.springframework.security.config.annotation.authentication.configuration;
    
    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    import org.springframework.aop.framework.ProxyFactoryBean;
    import org.springframework.aop.target.LazyInitTargetSource;
    import org.springframework.beans.factory.BeanFactoryUtils;
    import org.springframework.beans.factory.NoSuchBeanDefinitionException;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.ConfigurableApplicationContext;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Import;
    import org.springframework.core.annotation.AnnotationAwareOrderComparator;
    import org.springframework.security.authentication.AuthenticationEventPublisher;
    import org.springframework.security.authentication.AuthenticationManager;
    import org.springframework.security.config.annotation.ObjectPostProcessor;
    import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
    import org.springframework.security.config.annotation.authentication.configurers.provisioning.InMemoryUserDetailsManagerConfigurer;
    import org.springframework.security.config.annotation.authentication.configurers.provisioning.JdbcUserDetailsManagerConfigurer;
    import org.springframework.security.config.annotation.authentication.configurers.userdetails.DaoAuthenticationConfigurer;
    import org.springframework.security.config.annotation.configuration.ObjectPostProcessorConfiguration;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.AuthenticationException;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.crypto.factory.PasswordEncoderFactories;
    import org.springframework.security.crypto.password.PasswordEncoder;
    import org.springframework.util.Assert;
    
    import java.util.Collections;
    import java.util.List;
    import java.util.Map;
    import java.util.ArrayList;
    import java.util.concurrent.atomic.AtomicBoolean;
    
    /**
     * Exports the authentication {@link Configuration}
     *
     * @author Rob Winch
     * @since 3.2
     *
     */
    @Configuration(proxyBeanMethods = false)
    //ObjectPostProcessorConfiguration类是把对象放到容器中去 @Import(ObjectPostProcessorConfiguration.
    class) public class AuthenticationConfiguration { private AtomicBoolean buildingAuthenticationManager = new AtomicBoolean(); private ApplicationContext applicationContext; private AuthenticationManager authenticationManager; private boolean authenticationManagerInitialized; private List<GlobalAuthenticationConfigurerAdapter> globalAuthConfigurers = Collections .emptyList(); private ObjectPostProcessor<Object> objectPostProcessor; @Bean public AuthenticationManagerBuilder authenticationManagerBuilder( ObjectPostProcessor<Object> objectPostProcessor, ApplicationContext context) { LazyPasswordEncoder defaultPasswordEncoder = new LazyPasswordEncoder(context); AuthenticationEventPublisher authenticationEventPublisher = getBeanOrNull(context, AuthenticationEventPublisher.class); DefaultPasswordEncoderAuthenticationManagerBuilder result = new DefaultPasswordEncoderAuthenticationManagerBuilder(objectPostProcessor, defaultPasswordEncoder); if (authenticationEventPublisher != null) { result.authenticationEventPublisher(authenticationEventPublisher); } return result; } @Bean public static GlobalAuthenticationConfigurerAdapter enableGlobalAuthenticationAutowiredConfigurer( ApplicationContext context) { return new EnableGlobalAuthenticationAutowiredConfigurer(context); } @Bean public static InitializeUserDetailsBeanManagerConfigurer initializeUserDetailsBeanManagerConfigurer(ApplicationContext context) { return new InitializeUserDetailsBeanManagerConfigurer(context); } @Bean public static InitializeAuthenticationProviderBeanManagerConfigurer initializeAuthenticationProviderBeanManagerConfigurer(ApplicationContext context) { return new InitializeAuthenticationProviderBeanManagerConfigurer(context); } public AuthenticationManager getAuthenticationManager() throws Exception { if (this.authenticationManagerInitialized) { return this.authenticationManager; } AuthenticationManagerBuilder authBuilder = this.applicationContext.getBean(AuthenticationManagerBuilder.class); if (this.buildingAuthenticationManager.getAndSet(true)) { return new AuthenticationManagerDelegator(authBuilder); } for (GlobalAuthenticationConfigurerAdapter config : globalAuthConfigurers) { authBuilder.apply(config); } //如果想知道这个authenticationManager 的实现是哪个可以断点,会发现是ProviderManager authenticationManager = authBuilder.build(); if (authenticationManager == null) { authenticationManager = getAuthenticationManagerBean(); } this.authenticationManagerInitialized = true; return authenticationManager; } @Autowired(required = false) public void setGlobalAuthenticationConfigurers( List<GlobalAuthenticationConfigurerAdapter> configurers) { configurers.sort(AnnotationAwareOrderComparator.INSTANCE); this.globalAuthConfigurers = configurers; } @Autowired public void setApplicationContext(ApplicationContext applicationContext) { this.applicationContext = applicationContext; } @Autowired public void setObjectPostProcessor(ObjectPostProcessor<Object> objectPostProcessor) { this.objectPostProcessor = objectPostProcessor; } @SuppressWarnings("unchecked") private <T> T lazyBean(Class<T> interfaceName) { LazyInitTargetSource lazyTargetSource = new LazyInitTargetSource(); String[] beanNamesForType = BeanFactoryUtils.beanNamesForTypeIncludingAncestors( applicationContext, interfaceName); if (beanNamesForType.length == 0) { return null; } String beanName; if (beanNamesForType.length > 1) { List<String> primaryBeanNames = getPrimaryBeanNames(beanNamesForType); Assert.isTrue(primaryBeanNames.size() != 0, () -> "Found " + beanNamesForType.length + " beans for type " + interfaceName + ", but none marked as primary"); Assert.isTrue(primaryBeanNames.size() == 1, () -> "Found " + primaryBeanNames.size() + " beans for type " + interfaceName + " marked as primary"); beanName = primaryBeanNames.get(0); } else { beanName = beanNamesForType[0]; } lazyTargetSource.setTargetBeanName(beanName); lazyTargetSource.setBeanFactory(applicationContext); ProxyFactoryBean proxyFactory = new ProxyFactoryBean(); proxyFactory = objectPostProcessor.postProcess(proxyFactory); proxyFactory.setTargetSource(lazyTargetSource); return (T) proxyFactory.getObject(); } private List<String> getPrimaryBeanNames(String[] beanNamesForType) { List<String> list = new ArrayList<>(); if (!(applicationContext instanceof ConfigurableApplicationContext)) { return Collections.emptyList(); } for (String beanName : beanNamesForType) { if (((ConfigurableApplicationContext) applicationContext).getBeanFactory() .getBeanDefinition(beanName).isPrimary()) { list.add(beanName); } } return list; } private AuthenticationManager getAuthenticationManagerBean() { return lazyBean(AuthenticationManager.class); } private static <T> T getBeanOrNull(ApplicationContext applicationContext, Class<T> type) { try { return applicationContext.getBean(type); } catch(NoSuchBeanDefinitionException notFound) { return null; } } private static class EnableGlobalAuthenticationAutowiredConfigurer extends GlobalAuthenticationConfigurerAdapter { private final ApplicationContext context; private static final Log logger = LogFactory .getLog(EnableGlobalAuthenticationAutowiredConfigurer.class); EnableGlobalAuthenticationAutowiredConfigurer(ApplicationContext context) { this.context = context; } @Override public void init(AuthenticationManagerBuilder auth) { Map<String, Object> beansWithAnnotation = context .getBeansWithAnnotation(EnableGlobalAuthentication.class); if (logger.isDebugEnabled()) { logger.debug("Eagerly initializing " + beansWithAnnotation); } } } /** * Prevents infinite recursion in the event that initializing the * AuthenticationManager. * * @author Rob Winch * @since 4.1.1 */ static final class AuthenticationManagerDelegator implements AuthenticationManager { private AuthenticationManagerBuilder delegateBuilder; private AuthenticationManager delegate; private final Object delegateMonitor = new Object(); AuthenticationManagerDelegator(AuthenticationManagerBuilder delegateBuilder) { Assert.notNull(delegateBuilder, "delegateBuilder cannot be null"); this.delegateBuilder = delegateBuilder; } @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { if (this.delegate != null) { return this.delegate.authenticate(authentication); } synchronized (this.delegateMonitor) { if (this.delegate == null) { this.delegate = this.delegateBuilder.getObject(); this.delegateBuilder = null; } } return this.delegate.authenticate(authentication); } @Override public String toString() { return "AuthenticationManagerDelegator [delegate=" + this.delegate + "]"; } } static class DefaultPasswordEncoderAuthenticationManagerBuilder extends AuthenticationManagerBuilder { private PasswordEncoder defaultPasswordEncoder; /** * Creates a new instance * * @param objectPostProcessor the {@link ObjectPostProcessor} instance to use. */ DefaultPasswordEncoderAuthenticationManagerBuilder( ObjectPostProcessor<Object> objectPostProcessor, PasswordEncoder defaultPasswordEncoder) { super(objectPostProcessor); this.defaultPasswordEncoder = defaultPasswordEncoder; } @Override public InMemoryUserDetailsManagerConfigurer<AuthenticationManagerBuilder> inMemoryAuthentication() throws Exception { return super.inMemoryAuthentication() .passwordEncoder(this.defaultPasswordEncoder); } @Override public JdbcUserDetailsManagerConfigurer<AuthenticationManagerBuilder> jdbcAuthentication() throws Exception { return super.jdbcAuthentication() .passwordEncoder(this.defaultPasswordEncoder); } @Override public <T extends UserDetailsService> DaoAuthenticationConfigurer<AuthenticationManagerBuilder, T> userDetailsService( T userDetailsService) throws Exception { return super.userDetailsService(userDetailsService) .passwordEncoder(this.defaultPasswordEncoder); } } static class LazyPasswordEncoder implements PasswordEncoder { private ApplicationContext applicationContext; private PasswordEncoder passwordEncoder; LazyPasswordEncoder(ApplicationContext applicationContext) { this.applicationContext = applicationContext; } @Override public String encode(CharSequence rawPassword) { return getPasswordEncoder().encode(rawPassword); } @Override public boolean matches(CharSequence rawPassword, String encodedPassword) { return getPasswordEncoder().matches(rawPassword, encodedPassword); } @Override public boolean upgradeEncoding(String encodedPassword) { return getPasswordEncoder().upgradeEncoding(encodedPassword); } private PasswordEncoder getPasswordEncoder() { if (this.passwordEncoder != null) { return this.passwordEncoder; } PasswordEncoder passwordEncoder = getBeanOrNull(this.applicationContext, PasswordEncoder.class); if (passwordEncoder == null) { passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder(); } this.passwordEncoder = passwordEncoder; return passwordEncoder; } @Override public String toString() { return getPasswordEncoder().toString(); } } }

    这一段介绍说完了,下面就要看业务了,回退到WebSecurityConfiguration类,里面有个重要方法

        /**
           *  Spring Security 核心过滤器  Spring Security Filter Chain  , Bean ID 为 springSecurityFilterChain
           * @return the {@link Filter} that represents the security filter chain
           * @throws Exception
           */
          @Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
          public Filter springSecurityFilterChain() throws Exception {
              boolean hasConfigurers = webSecurityConfigurers != null
                      && !webSecurityConfigurers.isEmpty();
              if (!hasConfigurers) {
                  WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor
                          .postProcess(new WebSecurityConfigurerAdapter() {
                          });
                  webSecurity.apply(adapter);
              }
    // 获取到的就是FilterChainProxy 该对象会被保存到IoC容器中 且name为
    springSecurityFilterChain
              return webSecurity.build();
          }

    点击build进入AbstractSecurityBuilder类中的build()方法中

     

        public final O build() throws Exception {
            if (this.building.compareAndSet(false, true)) {
    //里面没什么东西,所以继续跟进doBuild()方法
    this.object = doBuild(); return this.object; } throw new AlreadyBuiltException("This object has already been built"); }
    @Override
        protected final O doBuild() throws Exception {
            synchronized (configurers) {
                buildState = BuildState.INITIALIZING;
    
                beforeInit();
    //初始化方法 init(); buildState
    = BuildState.CONFIGURING; beforeConfigure(); configure(); buildState = BuildState.BUILDING; O result = performBuild(); buildState = BuildState.BUILT; return result; } }
    1. package org.springframework.security.config.annotation.web.configuration;
    2.  
       
    3.  
      import java.util.Collections;
    4.  
      import java.util.List;
    5.  
      import java.util.Map;
    6.  
       
    7.  
      import javax.servlet.Filter;
    8.  
       
    9.  
      import org.springframework.beans.factory.BeanClassLoaderAware;
    10.  
      import org.springframework.beans.factory.annotation.Autowired;
    11.  
      import org.springframework.beans.factory.annotation.Value;
    12.  
      import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
    13.  
      import org.springframework.context.annotation.Bean;
    14.  
      import org.springframework.context.annotation.Configuration;
    15.  
      import org.springframework.context.annotation.DependsOn;
    16.  
      import org.springframework.context.annotation.ImportAware;
    17.  
      import org.springframework.core.OrderComparator;
    18.  
      import org.springframework.core.Ordered;
    19.  
      import org.springframework.core.annotation.AnnotationAttributes;
    20.  
      import org.springframework.core.annotation.AnnotationUtils;
    21.  
      import org.springframework.core.annotation.Order;
    22.  
      import org.springframework.core.type.AnnotationMetadata;
    23.  
      import org.springframework.security.access.expression.SecurityExpressionHandler;
    24.  
      import org.springframework.security.config.annotation.ObjectPostProcessor;
    25.  
      import org.springframework.security.config.annotation.SecurityConfigurer;
    26.  
      import org.springframework.security.config.annotation.web.WebSecurityConfigurer;
    27.  
      import org.springframework.security.config.annotation.web.builders.WebSecurity;
    28.  
      import org.springframework.security.context.DelegatingApplicationListener;
    29.  
      import org.springframework.security.web.FilterChainProxy;
    30.  
      import org.springframework.security.web.FilterInvocation;
    31.  
      import org.springframework.security.web.access.WebInvocationPrivilegeEvaluator;
    32.  
      import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
    33.  
       
    34.  
       
    35.  
      /**
    36.  
      * Spring Web Security 的配置类 :
    37.  
      * 1. 使用一个 WebSecurity 对象基于安全配置创建一个 FilterChainProxy 对象来对用户请求进行安全过滤。
    38.  
      * 2. 也会暴露诸如 安全SpEL表达式处理器 SecurityExpressionHandler 等一些类。
    39.  
      *
    40.  
      * @see EnableWebSecurity
    41.  
      * @see WebSecurity
    42.  
      *
    43.  
      * @author Rob Winch
    44.  
      * @author Keesun Baik
    45.  
      * @since 3.2
    46.  
      */
    47.  
      @Configuration
    48.  
      public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware {
    49.  
      private WebSecurity webSecurity;
    50.  
      // 是否启用了调试模式,来自注解 @EnableWebSecurity 的属性 debug,缺省值 false
    51.  
      private Boolean debugEnabled;
    52.  
       
    53.  
      private List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers;
    54.  
       
    55.  
      private ClassLoader beanClassLoader;
    56.  
       
    57.  
      @Autowired(required = false)
    58.  
      private ObjectPostProcessor<Object> objectObjectPostProcessor;
    59.  
      /**
    60.  
      *
    61.  
      * 代理监听器 应该时监听 DefaultAuthenticationEventPublisher 的一些处理策略
    62.  
      */
    63.  
      @Bean
    64.  
      public static DelegatingApplicationListener delegatingApplicationListener() {
    65.  
      return new DelegatingApplicationListener();
    66.  
      }
    67.  
      /**
    68.  
      *
    69.  
      * 安全SpEL表达式处理器 SecurityExpressionHandler 缺省为一个 DefaultWebSecurityExpressionHandler
    70.  
      */
    71.  
      @Bean
    72.  
      @DependsOn(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
    73.  
      public SecurityExpressionHandler<FilterInvocation> webSecurityExpressionHandler() {
    74.  
      return webSecurity.getExpressionHandler();
    75.  
      }
    76.  
       
    77.  
      /**
    78.  
      * Spring Security 核心过滤器 Spring Security Filter Chain , Bean ID 为 springSecurityFilterChain
    79.  
      * @return the {@link Filter} that represents the security filter chain
    80.  
      * @throws Exception
    81.  
      */
    82.  
      @Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
    83.  
      public Filter springSecurityFilterChain() throws Exception {
    84.  
      boolean hasConfigurers = webSecurityConfigurers != null
    85.  
      && !webSecurityConfigurers.isEmpty();
    86.  
      if (!hasConfigurers) {
    87.  
      WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor
    88.  
      .postProcess(new WebSecurityConfigurerAdapter() {
    89.  
      });
    90.  
      webSecurity.apply(adapter);
    91.  
      }
    92.  
      return webSecurity.build();
    93.  
      }
    94.  
       
    95.  
      /**
    96.  
      *
    97.  
      * 用于模板 如JSP Freemarker 的一些页面标签按钮控制支持
    98.  
      * Creates the {@link WebInvocationPrivilegeEvaluator} that is necessary for the JSP
    99.  
      * tag support.
    100.  
      * @return the {@link WebInvocationPrivilegeEvaluator}
    101.  
      * @throws Exception
    102.  
      */
    103.  
      @Bean
    104.  
      @DependsOn(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
    105.  
      public WebInvocationPrivilegeEvaluator privilegeEvaluator() throws Exception {
    106.  
      return webSecurity.getPrivilegeEvaluator();
    107.  
      }
    108.  
       
    109.  
      /**
    110.  
      *
    111.  
      * 用于创建web configuration的SecurityConfigurer实例,
    112.  
      * 注意该参数通过@Value(...)方式注入,对应的bean autowiredWebSecurityConfigurersIgnoreParents
    113.  
      * 也在该类中定义
    114.  
      *
    115.  
      * @param objectPostProcessor the {@link ObjectPostProcessor} used to create a
    116.  
      * {@link WebSecurity} instance
    117.  
      * @param webSecurityConfigurers the
    118.  
      * {@code <SecurityConfigurer<FilterChainProxy, WebSecurityBuilder>} instances used to
    119.  
      * create the web configuration
    120.  
      * @throws Exception
    121.  
      */
    122.  
      @Autowired(required = false)
    123.  
      public void setFilterChainProxySecurityConfigurer(
    124.  
      ObjectPostProcessor<Object> objectPostProcessor,
    125.  
      @Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers)
    126.  
      throws Exception {
    127.  
      webSecurity = objectPostProcessor
    128.  
      .postProcess(new WebSecurity(objectPostProcessor));
    129.  
      if (debugEnabled != null) {
    130.  
      webSecurity.debug(debugEnabled);
    131.  
      }
    132.  
       
    133.  
      Collections.sort(webSecurityConfigurers, AnnotationAwareOrderComparator.INSTANCE);
    134.  
       
    135.  
      Integer previousOrder = null;
    136.  
      Object previousConfig = null;
    137.  
      for (SecurityConfigurer<Filter, WebSecurity> config : webSecurityConfigurers) {
    138.  
      Integer order = AnnotationAwareOrderComparator.lookupOrder(config);
    139.  
      if (previousOrder != null && previousOrder.equals(order)) {
    140.  
      throw new IllegalStateException(
    141.  
      "@Order on WebSecurityConfigurers must be unique. Order of "
    142.  
      + order + " was already used on " + previousConfig + ", so it cannot be used on "
    143.  
      + config + " too.");
    144.  
      }
    145.  
      previousOrder = order;
    146.  
      previousConfig = config;
    147.  
      }
    148.  
      for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) {
    149.  
      webSecurity.apply(webSecurityConfigurer);
    150.  
      }
    151.  
      this.webSecurityConfigurers = webSecurityConfigurers;
    152.  
      }
    153.  
      /**
    154.  
      * 从当前bean容器中获取所有的WebSecurityConfigurer bean。
    155.  
      * 这些WebSecurityConfigurer通常是由开发人员实现的配置类,并且继承自WebSecurityConfigurerAdapter
    156.  
      *
    157.  
      */
    158.  
      @Bean
    159.  
      public static AutowiredWebSecurityConfigurersIgnoreParents autowiredWebSecurityConfigurersIgnoreParents(
    160.  
      ConfigurableListableBeanFactory beanFactory){
    161.  
      return new AutowiredWebSecurityConfigurersIgnoreParents(beanFactory);
    162.  
      }
    163.  
       
    164.  
      /**
    165.  
      * A custom verision of the Spring provided AnnotationAwareOrderComparator that uses
    166.  
      * {@link AnnotationUtils#findAnnotation(Class, Class)} to look on super class
    167.  
      * instances for the {@link Order} annotation.
    168.  
      *
    169.  
      * @author Rob Winch
    170.  
      * @since 3.2
    171.  
      */
    172.  
      private static class AnnotationAwareOrderComparator extends OrderComparator {
    173.  
      private static final AnnotationAwareOrderComparator INSTANCE = new AnnotationAwareOrderComparator();
    174.  
       
    175.  
      @Override
    176.  
      protected int getOrder(Object obj) {
    177.  
      return lookupOrder(obj);
    178.  
      }
    179.  
       
    180.  
      private static int lookupOrder(Object obj) {
    181.  
      if (obj instanceof Ordered) {
    182.  
      return ((Ordered) obj).getOrder();
    183.  
      }
    184.  
      if (obj != null) {
    185.  
      Class<?> clazz = (obj instanceof Class ? (Class<?>) obj : obj.getClass());
    186.  
      Order order = AnnotationUtils.findAnnotation(clazz, Order.class);
    187.  
      if (order != null) {
    188.  
      return order.value();
    189.  
      }
    190.  
      }
    191.  
      return Ordered.LOWEST_PRECEDENCE;
    192.  
      }
    193.  
      }
    194.  
       
    195.  
      /*
    196.  
      * 要是为了获取注解 @EnableWebSecurity 的属性 debugEnabled
    197.  
      *
    198.  
      * @see org.springframework.context.annotation.ImportAware#setImportMetadata(org.
    199.  
      * springframework.core.type.AnnotationMetadata)
    200.  
      */
    201.  
      public void setImportMetadata(AnnotationMetadata importMetadata) {
    202.  
      Map<String, Object> enableWebSecurityAttrMap = importMetadata
    203.  
      .getAnnotationAttributes(EnableWebSecurity.class.getName());
    204.  
      AnnotationAttributes enableWebSecurityAttrs = AnnotationAttributes
    205.  
      .fromMap(enableWebSecurityAttrMap);
    206.  
      debugEnabled = enableWebSecurityAttrs.getBoolean("debug");
    207.  
      if (webSecurity != null) {
    208.  
      webSecurity.debug(debugEnabled);
    209.  
      }
    210.  
      }
    211.  
       
    212.  
      /*
    213.  
      * (non-Javadoc)
    214.  
      *
    215.  
      * @see
    216.  
      * org.springframework.beans.factory.BeanClassLoaderAware#setBeanClassLoader(java.
    217.  
      * lang.ClassLoader)
    218.  
      */
    219.  
      public void setBeanClassLoader(ClassLoader classLoader) {
    220.  
      this.beanClassLoader = classLoader;
    221.  
      }
    222.  
      }
       
       
       
       
  • 相关阅读:
    JAVA中的继承
    各种浏览器设置背景颜色线性渐变的方式
    JavaScript原型链
    JavaScript运算符
    QQ聊天:终结编程语言和编程职业
    virutal dom patch
    关于编辑器和语言的一些启示
    node-webkit 资源
    我的程序,你的生活
    过早优化是万恶之源
  • 原文地址:https://www.cnblogs.com/xing1/p/13798430.html
Copyright © 2011-2022 走看看