Spring Security源码分析

参考链接(主要参考此系列文章,截图和补充总结等由debug程序、阅读源码得出)

https://juejin.im/post/5d8d66aee51d45783f5aa49e

 

一、三句话解释框架原理

1、整个框架的核心是一个过滤器,这个过滤器名字叫springSecurityFilterChain类型是FilterChainProxy

2、核心过滤器里面是过滤器链(列表),过滤器链的每个元素都是一组URL对应一组过滤器

3、WebSecurity用来创建FilterChainProxy过滤器,

HttpSecurity用来创建过滤器链的每个元素。

补充解释:WebSecurity和HttpSecurity都是建造者

WebSecurity构建目标是FilterChainProxy对象

HttpSecurity的构建目标仅仅是FilterChainProxy中的一个SecurityFilterChain

@EnableWebSecurity注解,导入了WebSecurityConfiguration类

WebSecurityConfiguration中创建了建造者对象WebSecurity,和核心过滤器FilterChainProxy

 

二、框架接口设计

1、关注两个东西:建造者和适配器,框架的用法就是通过适配器对建造者进行配置

2、框架用法是写一个自定义配置类,继承WebSecurityConfigurerAdapter,重写几个configure()方法

WebSecurityConfigurerAdapter就是Web安全配置器的适配器对象。

 

// 安全建造者 // 顾名思义是一个builder构造器,创建并返回一个类型为O的对象

// 安全建造者
// 顾名思义是一个builder构造器,创建并返回一个类型为O的对象
public interface SecurityBuilder<O> {
    O build() throws Exception;
}

// 抽象安全建造者
public abstract class AbstractSecurityBuilder<O> implements SecurityBuilder<O> {
    private AtomicBoolean building = new AtomicBoolean();
    private O object;

    public final O build() throws Exception {
        // 限定build()只会进行一次!
        if (this.building.compareAndSet(false, true)) {
            this.object = doBuild();
            return this.object;
        }
        throw new AlreadyBuiltException("This object has already been built");
    }

    // 子类需要重写doBuild()方法
    protected abstract O doBuild() throws Exception;
}

// 配置后的抽象安全建造者
public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBuilder<O>>
        extends AbstractSecurityBuilder<O> {

    // 实现了doBuild()方法,遍历configurers进行init()和configure()。
    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;
        }
    }
    // 它的子类HttpSecurity和WebSecurity都实现了它的performBuild()方法!!!
    protected abstract O performBuild() throws Exception;

    // 主要作用是将安全配置器SecurityConfigurer注入到属性configurers中,
    private void configure() throws Exception {
        Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();

        for (SecurityConfigurer<O, B> configurer : configurers) {
            configurer.configure((B) this);
        }
    }
}
// 安全配置器,配置建造者B,B可以建造O
// 初始化(init)SecurityBuilder,且配置(configure)SecurityBuilder
public interface SecurityConfigurer<O, B extends SecurityBuilder<O>> {
    void init(B builder) throws Exception;
    void configure(B builder) throws Exception;
}


// Web安全配置器,配置建造者T,T可以建造web过滤器
public interface WebSecurityConfigurer<T extends SecurityBuilder<Filter>> 
        extends SecurityConfigurer<Filter, T> {
}

// Web安全配置器的适配器
// 配置建造者WebSecurity,WebSecurity可以建造核心过滤器
public abstract class WebSecurityConfigurerAdapter 
        implements WebSecurityConfigurer<WebSecurity> {
}


// 用于构建FilterChainProxy的建造者
public final class WebSecurity 
    extends AbstractConfiguredSecurityBuilder<Filter, WebSecurity>
    implements
        SecurityBuilder<Filter>, ApplicationContextAware {
}

// 用于构建SecurityFilterChain的建造者
public final class HttpSecurity 
    extends AbstractConfiguredSecurityBuilder<DefaultSecurityFilterChain, HttpSecurity>
    implements 
        SecurityBuilder<DefaultSecurityFilterChain>,
        HttpSecurityBuilder<HttpSecurity> {

}

3、总结:

看到建造者去看他的方法:build(); doBuid(); init(); configure(); performBuilde();

看到配置器去看他的方法: init(); config();

 

三、从写MySecurityConfig时使用的@EnableWebSecurity注解开始看源码:

1、@EnableWebSecurity注解导入了三个类,重点关注WebSecurityConfiguration

2、WebSecurityConfiguration中需要关注两个方法:

setFilterChainProxySecurityConfigurer()方法:创建了WebSecurity建造者对象,用于后面建造FilterChainProxy过滤器。

springSecurityFilterChain()方法:调用WebSecurity.build(),建造出FilterChainProxy过滤器对象。

 

四、WebSecurity的创建过程(由WebSecurityConfiguration创建)

1、setFilterChainProxySecurityConfigurer()方法负责收集配置累对象列表webSecurityConfigurers,并创建WebSecurity:

/**
 * Sets the {@code <SecurityConfigurer<FilterChainProxy, WebSecurityBuilder>}
 * instances used to create the web configuration.
 *
 * @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);
   }

   webSecurityConfigurers.sort(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;
}

2、下图是通过AutowiredWebSecurityConfigurersIgnoreParents的getWebSecurityConfigurers()方法,获取所有实现WebSecurityConfigurer的配置类。

 

 

五、FilterChainProxy的创建过程(由WebSecurityConfiguration创建)

1、在springSecurityFilterChain()方法中调用webSecurity.build()创建了FilterChainProxy。

PS:根据下面代码,我们可以知道如果创建的MySecurityConfig类没有被sping扫描到,

框架会新new 出一个WebSecurityConfigureAdapter对象,这会导致我们配置的用户名和密码失效。

 

 

2、WebSecurity是一个建造者,所以我们去看这些方法build(); doBuild(); init(); configure(); performBuild();

 

build()方法定义在WebSecurity对象的父类AbstractSecurityBuilder中:

 

build()方法会调用WebSecurity对象的父类AbstractConfiguredSecurityBuilder#doBuild():

 

doBuild()先调用init();configure();等方法

我们上面已经得知了configurersAddedInInitializing里是所有的配置类对象

如下图,这里会依次执行配置类的configure();init()方法

 

doBuild()最后调用了WebSecurity对象的perfomBuild(),来创建了FilterChainProxy对象

performBuild()里遍历securityFilterChainBuilders建造者列表

把每个SecurityBuilder建造者对象构建成SecurityFilterChain实例

最后创建并返回FilterChainProxy

 

3、securityFilterChainBuilders建造者列表的初始化

 

这时候要注意到WebSecurityConfigurerAdapter,这个类的创建了HttpSecurity并放入了securityFilterChainBuilders

 

WebSecurityConfigurerAdapter是一个安全配置器,我们知道建造者在performBuild()之前都会把循环调用安全配置器的init();configure();方法,然后创建HttpSecurity并放入自己的securityFilterChainBuilders里。

PS: 前面已经提到了,在WebSecurity初始化时,会依次将WebSecurityConfigurerAdapter的子类放入WebSecurity。

 

六、ServletContext如何拿到FilterChainProxy的过滤器对象

1、Bean都是存在Spring的Bean工厂里的,而且在Web项目中Servlet、Filter、Listener都要放入ServletContext中。

 

2、看下面这张图,ServletContainerInitializer接口提供了一个onStartup()方法,用于在Servlet容器启动时动态注册一些对象到ServletContext中。

官方的解释是:为了支持可以不使用web.xml。提供了ServletContainerInitializer,它可以通过SPI机制,当启动web容器的时候,会自动到添加的相应jar包下找到META-INF/services下以ServletContainerInitializer的全路径名称命名的文件,它的内容为ServletContainerInitializer实现类的全路径,将它们实例化。

Spring框架通过META-INF配置了SpringServletContainerInitializer类

 

 

SpringServletContainerInitializer实现了ServletContainerInitializer接口。

请注意该类上的@HandlesTypes(WebApplicationInitializer.class)注解

根据Sevlet3.0规范,Servlet容器在调用onStartup()方法时,会以Set集合的方式注入WebApplicationInitializer的子类(包括接口,抽象类)。然后会依次调用WebApplicationInitializer的实现类的onStartup方法,从而起到启动web.xml相同的作用(添加servlet,listener实例到ServletContext中)。

 

Spring Security中的AbstractSecurityWebApplicationInitializer就是WebApplicationInitializer的抽象子类.

当执行到下面的onStartup()方法时,会调用insertSpringSecurityFilterChain()

将类型为FilterChainProxy名称为springSecurityFilterChain的过滤器对象用DelegatingFilterProxy包装,然后注入ServletContext

 

七、FilterChainProxy运行过程

请求到达的时候,FilterChainProxy的dofilter()方法内部,会遍历所有的SecurityFilterChain,对匹配到的url,则一一调用SecurityFilterChain中的filter做认证或授权。

public class FilterChainProxy extends GenericFilterBean {

    private final static String FILTER_APPLIED = FilterChainProxy.class.getName().concat(".APPLIED");
    private List<SecurityFilterChain> filterChains;
    private FilterChainValidator filterChainValidator = new NullFilterChainValidator();
    private HttpFirewall firewall = new StrictHttpFirewall();

    public FilterChainProxy() {
    }

    public FilterChainProxy(SecurityFilterChain chain) {
        this(Arrays.asList(chain));
    }

    public FilterChainProxy(List<SecurityFilterChain> filterChains) {
        this.filterChains = filterChains;
    }

    @Override
    public void afterPropertiesSet() {
        filterChainValidator.validate(this);
    }

    @Override
    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);
                doFilterInternal(request, response, chain);
            }
            finally {
                SecurityContextHolder.clearContext();
                request.removeAttribute(FILTER_APPLIED);
            }
        }
        else {
            doFilterInternal(request, response, chain);
        }
    }
private void doFilterInternal(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {

        FirewalledRequest fwRequest = firewall.getFirewalledRequest((HttpServletRequest) request);
        HttpServletResponse fwResponse = firewall.getFirewalledResponse((HttpServletResponse) response);

        // 根据当前请求,获得一组过滤器链
        List<Filter> filters = getFilters(fwRequest);

        if (filters == null || filters.size() == 0) {
            if (logger.isDebugEnabled()) {
                logger.debug(UrlUtils.buildRequestUrl(fwRequest)
                        + (filters == null ? " has no matching filters": " has an empty filter list"));
            }
            fwRequest.reset();
            chain.doFilter(fwRequest, fwResponse);
            return;
        }

        VirtualFilterChain vfc = new VirtualFilterChain(fwRequest, chain, filters);
        // 请求依次经过这组滤器链
        vfc.doFilter(fwRequest, fwResponse);
    }

    /**
     * 根据Request请求获得一个过滤器链
     */
    private List<Filter> getFilters(HttpServletRequest request) {
        for (SecurityFilterChain chain : filterChains) {
            if (chain.matches(request)) {
                return chain.getFilters();
            }
        }
        return null;
    }

    /**
     * 根据URL获得一个过滤器链
     */
    public List<Filter> getFilters(String url) {
        return getFilters(firewall.getFirewalledRequest((new FilterInvocation(url, null)
                .getRequest())));
    }

    /**
     * 返回一个过滤器链
     */
    public List<SecurityFilterChain> getFilterChains() {
        return Collections.unmodifiableList(filterChains);
    }

// 过滤器链内部类
    private static class VirtualFilterChain implements FilterChain {
        private final FilterChain originalChain;
        private final List<Filter> additionalFilters;
        private final FirewalledRequest firewalledRequest;
        private final int size;
        private int currentPosition = 0;

        private VirtualFilterChain(FirewalledRequest firewalledRequest,
                FilterChain chain, List<Filter> additionalFilters) {
            this.originalChain = chain;
            this.additionalFilters = additionalFilters;
            this.size = additionalFilters.size();
            this.firewalledRequest = firewalledRequest;
        }

        @Override
        public void doFilter(ServletRequest request, ServletResponse response)
                throws IOException, ServletException {
            if (currentPosition == size) {
                if (logger.isDebugEnabled()) {
                    logger.debug(UrlUtils.buildRequestUrl(firewalledRequest)
                            + " reached end of additional filter chain; proceeding with original chain");
                }

                // Deactivate path stripping as we exit the security filter chain
                this.firewalledRequest.reset();

                originalChain.doFilter(request, response);
            }
            else {
                currentPosition++;

                Filter nextFilter = additionalFilters.get(currentPosition - 1);

                if (logger.isDebugEnabled()) {
                    logger.debug(UrlUtils.buildRequestUrl(firewalledRequest)
                            + " at position " + currentPosition + " of " + size
                            + " in additional filter chain; firing Filter: '"
                            + nextFilter.getClass().getSimpleName() + "'");
                }

                nextFilter.doFilter(request, response, this);
            }
        }
    }

public interface FilterChainValidator {
        void validate(FilterChainProxy filterChainProxy);
    }

    private static class NullFilterChainValidator implements FilterChainValidator {
        @Override
        public void validate(FilterChainProxy filterChainProxy) {
        }
    }

}

补充:WebSecurity在初始化的时候会扫描WebSecurityConfigurerAdapter配置器适配器的子类(即生成HttpSecurity配置器)。

所有的配置器会被调用init();configure();初始化配置,其中生成的每个HttpSecurity配置器都代表了一个过滤器链。

 

PS:如果有多个WebSecurityConfigurerAdapter配置器适配器的子类,会产生多个SecurityFilterChain过滤器链实例。Spring Security Oauth2的拓展就是这么做的。

 

八、spring security 怎么创建的过滤器

1、我们已经知道了springSecurityFilterChain(类型为FilterChainProxy)是实际起作用的过滤器链,DelegatingFilterProxy起到代理作用。

2、我们创建的MySecurityConfig继承了WebSecurityConfigurerAdapter。WebSecurityConfigurerAdapter就是用来创建过滤器链,重写的configure(HttpSecurity http)的方法就是用来配置HttpSecurity的。

protected void configure(HttpSecurity http) throws Exception {         http             .requestMatchers() // 指定当前`SecurityFilterChain`实例匹配哪些请求                 .anyRequest().and()             .authorizeRequests() // 拦截请求,创建FilterSecurityInterceptor                 .anyRequest().authenticated() // 在创建过滤器的基础上的一些自定义配置                 .and() // 用and来表示配置过滤器结束,以便进行下一个过滤器的创建和配置             .formLogin().and() // 设置表单登录,创建UsernamePasswordAuthenticationFilter             .httpBasic(); // basic验证,创建BasicAuthenticationFilter } 链接:https://juejin.im/post/5d9161ace51d4577ff0d9eb7 来源:掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

 

3、http.authorizeRequests()、http.formLogin()、http.httpBasic()分别创建了ExpressionUrlAuthorizationConfigurer,FormLoginConfigurer,HttpBasicConfigurer。在三个类从父级一直往上找,会发现它们都是SecurityConfigurer建造器的子类。SecurityConfigurer中又有configure()方法。该方法被子类实现就用于创建各个过滤器,并将过滤器添加进HttpSecurity中维护的装有Filter的List中,比如HttpBasicConfigurer中的configure方法,源码如下:

 

HttpSecurity作为建造者会把根据api把这些配置器添加到实例中

 

 

 

这些配置器中大都是创建了相应的过滤器,并进行配置,最终在HttpSecurity建造SecurityFilterChain实例时放入过滤器链

 

九、认证过滤器 UsernamePasswordAuthenticationFilter

1、参数有username,password的,走UsernamePasswordAuthenticationFilter,提取参数构造UsernamePasswordAuthenticationToken进行认证,成功则填充SecurityContextHolder的Authentication

 

2、UsernamePasswordAuthenticationFilter实现了其父类AbstractAuthenticationProcessingFilter中的attemptAuthentication方法。这个方法会调用认证管理器AuthenticationManager去认证。

 

3、关闭formLogin()验证后则不通过UsernamePasswordAuthenticationFilter验证,可自定义拦截器进行验证拦截

 

十、默认验证流程

1、开启formLogin()验证后,请求会被UsernamePasswordAuthenticationFilter拦截,程序事先在InMemoryUserDetailsManager中保存了一个user在Map<String, MutableUserDetails> users 里面。拦截器会将两者进行比对。

2、默认用户信息是在类SecurityProperties中生成,程序启动时UserDetailsServiceAutoConfiguration调用inMemoryUserDetailsManager方法,内部调用getOrDeducePassword方法打印出默认的生成的密码。

 

十一、Spring组件@Scope

1、Bean的作用域

Singleton(单例式):在整个应用中,只创建bean的一个实例。

Prototype(原型式):每次注入或者通过Spring应用上下文获取的时候,都会创建一个新的bean实例。

Session(会话式):在Web应用中,为每个会话创建一个bean实例。(eg:电子商务应用中,一个bean代表一个用户的购物车,只要同一个session一个bean)。

Request(请求式):在Web应用中,为每个请求创建一个bean实例。

 

链接:https://www.cnblogs.com/MrSi/p/7932218.html

 

2、每个客户端登录都会产生一个session会话,它的生命周期 从登录系统到 session过期,期间session上存储的信息都是有效可用的,我习惯于叫它会话级的缓存,像用户登录的身份信息我们一般都会绑定到这个session上。这里我们要讲的@Scope("session"),就是spring提供出来的一个会话级bean方案,在这种模式下,用spring的DI功能来获取组件,可以做到在会话的生命周期中这个组件只有一个实例。

 

3、接下来再说请求(request),http协议的处理模型,从客户端发起request请求,到服务端的处理,最后response给客户端,我们称为一次完整的请求。在这样的一次请求过程中,我们的服务站可能要串行调用funcA->funcB->funcC·... 这样的一串函数,假如我们的系统需要做细致的权限校验(用户权限,数据权限),更可怕的是funcA,funcB,funcC是3个人分别实现的,而且都要做权限校验。那么极有可能会出现3个人各连接了一次数据库,读取了同一批权限数据。这里想象一下,假如一个数据读取要花2秒,那么3个方法就要花费6秒的处理时间。但实际上这些数据只用在这个请求过程中读取一次,缓存在request上下文环境中,我习惯称之为线程级缓存。关于线程级缓存java有ThreadLocal方案,像Hibernate的事务就是用这种方案维持一次执行过程中数据库连接的唯一。当然,今天要讲的@Scope("request")也可以做到这种线程级别的缓存。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章