Spring Security原理篇(三) HttpSecurity

1.初始化HttpSecurity对象

从前面的文章中,我们已经提到在WebSecurityConfigurerAdapter的初始化方法init()中,通过getHttp()方法获取到了HttpSecurity的对象,我们再来看一下init()这个方法的源代码

    /**
     * @param web
     * @throws Exception
     */
    public void init(final WebSecurity web) throws Exception {
        final HttpSecurity http = getHttp();
        web.addSecurityFilterChainBuilder(http).postBuildAction(new Runnable() {
            public void run() {
                FilterSecurityInterceptor securityInterceptor = http
                        .getSharedObject(FilterSecurityInterceptor.class);
                web.securityInterceptor(securityInterceptor);
            }
        });
    }

这个方法的具体作用已经在上一篇文章中说过,先构建HttpSecurity对象,然后通过WebSecurity对象的addSecurityFilterChainBuilder()方法添加到securityFilterChainBuilders的List中,最后用来组件过滤器链。

1.1 WebSecurityConfigurerAdaptergetHttp()方法

  • 首先我们还是引入一下这个方法的源代码
protected final HttpSecurity getHttp() throws Exception {
        //如果已经存在HttpSecurity 对象,则返回
        if (http != null) {
            return http;
        }
        //这里主要还是关于异常的一些处理,这里我们最后的文章统一再说,先给自己留个坑,先猜测一下吧
        DefaultAuthenticationEventPublisher eventPublisher = objectPostProcessor
                .postProcess(new DefaultAuthenticationEventPublisher());
    localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher);
           //构建AuthenticationManager对象,这个对象管理认证,后面我们再说
        AuthenticationManager authenticationManager = authenticationManager();
        authenticationBuilder.parentAuthenticationManager(authenticationManager);

            //创建共享对象
        Map, Object> sharedObjects = createSharedObjects();
       
        //构建HttpSecurity 需要用到authenticationBuilder,sharedObjects
        http = new HttpSecurity(objectPostProcessor, authenticationBuilder,
                sharedObjects);

        //允许默认配置的时候
        if (!disableDefaults) {
            // @formatter:off
            http
                .csrf().and()
                .addFilter(new WebAsyncManagerIntegrationFilter())
                .exceptionHandling().and()
                .headers().and()
                .sessionManagement().and()
                .securityContext().and()
                .requestCache().and()
                .anonymous().and()
                .servletApi().and()
                .apply(new DefaultLoginPageConfigurer<>()).and()
                .logout();
            // @formatter:on
            ClassLoader classLoader = this.context.getClassLoader();
            List defaultHttpConfigurers =
                    SpringFactoriesLoader.loadFactories(AbstractHttpConfigurer.class, classLoader);

            for(AbstractHttpConfigurer configurer : defaultHttpConfigurers) {
                http.apply(configurer);
            }
        }

        //??configure??
        configure(http);
        return http;
    }
  • 我们可以从上面的代码中可以知道,因为HttpSecurity构造函数需要AuthenticationManagerBuildersharedObjects 对象,上面的代码先创建AuthenticationManagerBuilder的对象,然后填充了共享对象的map,然后调用HttpSecueity的构造函数构造出来一个HttpSecurity的对象,然后configure(http),这个方法最后再讲,关于AuthenticationManagerBuilder我们到用户认证的时候再去专门讲,而sharedObjects我们也需要专门篇幅来将讲解,这些看似细节又复杂的东西,怕混乱到影响这篇文章重点需要讲解的HttpSecurity

1.2 HttpSecurity 类的大体理解

1.2.1 HttpSecurity的类图

Spring Security原理篇(三) HttpSecurity_第1张图片
HttpSecurity类图
  • 在说Filter的时候我们说到过WebSecuritybuild()方法的时候有很具体的说到AbastractSecurityBuilderAbstractConfiguredSecurityBuilder这两个类中build()方法执行的过程我们看一下HttpSecurity类的定义
public final class HttpSecurity extends
        AbstractConfiguredSecurityBuilder
        implements SecurityBuilder,
        HttpSecurityBuilder 

我们可以知道调用HttpSecuritybuild()方法的时候返回的就是DefaultSecurityFilterChain的对象,当然具体的还要看HttpSecurityperformBuild()方法

  • HttpSecurityBuilder接口

1.2.2HttpSecurity的属性

     //从变量名就可以看到是请求匹配过滤的配置信息    
    private final RequestMatcherConfigurer requestMatcherConfigurer;

    //过滤器列表?
    private List filters = new ArrayList<>();

    //匹配任何请求的匹配器
    private RequestMatcher requestMatcher = AnyRequestMatcher.INSTANCE;

    //过滤器比较.....啥啥啥不知道
    private FilterComparator comparator = new FilterComparator();
  • 带着疑问,我们还是要看先了解一下这些属性的值是怎么来的,先挑软柿子捏吧
  • 1.requestMatcher可以完整的看一下 AnyRequestMatcher的定义
public final class AnyRequestMatcher implements RequestMatcher {
    public static final RequestMatcher INSTANCE = new AnyRequestMatcher();

    public boolean matches(HttpServletRequest request) {
        //直接return true?那就是说有请求都匹配
        return true;
    }

    @Override
    @SuppressWarnings("deprecation")
    public boolean equals(Object obj) {
        return obj instanceof AnyRequestMatcher
                || obj instanceof org.springframework.security.web.util.matcher.AnyRequestMatcher;
    }

    @Override
    public int hashCode() {
        return 1;
    }

    @Override
    public String toString() {
        return "any request";
    }

    private AnyRequestMatcher() {
    }
}

这个类继承的接口只有matches方法,方法的注释上写的很明白,如果匹配规则就返回true,否则返回false,AnyRequestMatcher永远返回true,说明匹配任何请求。如果需要查看接口定义,可以自行查看类RequestMatcher的源代码,因为简单,节省空间。

*2.comparatorFilterComparator的对象。然而Filter比较器类的定义也是比较简单的,此处还是不引入源代码了,因为后面我们还要讲解,里面很重要的定义了一些我们不知不觉用着的东西。FilterComparator实现Comparator接口,作为比较器,我们只看一下他的一个方法就可以了

public int compare(Filter lhs, Filter rhs) {
    Integer left = getOrder(lhs.getClass());
    Integer right = getOrder(rhs.getClass());
    return left - right;
}

这个getOrder就是Filter上定义Order的数字,还有一种形式就是addFilter这个的。反正就是获取到在过滤器列表中的顺序,不过当然不是连续的顺序

  • 3.filters的list 只是通过addFilter()方法添加进来,放一下源代码,不解释了
    public HttpSecurity addFilter(Filter filter) {
        Class filterClass = filter.getClass();
        if (!comparator.isRegistered(filterClass)) {
            throw new IllegalArgumentException(
                    "The Filter class "
                            + filterClass.getName()
                            + " does not have a registered order and cannot be added without a specified order. Consider using addFilterBefore or addFilterAfter instead.");
        }
        this.filters.add(filter);
        return this;
    }

1.2.3 HttpSecurity的部分方法

  • 1.构造方法
public HttpSecurity(ObjectPostProcessor objectPostProcessor,
            AuthenticationManagerBuilder authenticationBuilder,
            Map, Object> sharedObjects) {
        super(objectPostProcessor);
        Assert.notNull(authenticationBuilder, "authenticationBuilder cannot be null");
        setSharedObject(AuthenticationManagerBuilder.class, authenticationBuilder);
        for (Map.Entry, Object> entry : sharedObjects
                .entrySet()) {
            setSharedObject((Class) entry.getKey(), entry.getValue());
        }
        ApplicationContext context = (ApplicationContext) sharedObjects
                .get(ApplicationContext.class);
        this.requestMatcherConfigurer = new RequestMatcherConfigurer(context);
    }
 
 

除了设置了共享对象之外,唯一值得一提的就是ApplicationContext 作为共享对象传递进来了,哈哈,毕竟spring security再牛逼,再spring面前还是得装装小媳妇的。至于requestMatcherConfigurer还是要在稍后的篇幅重点讲一下的,毕竟太重要了,不论对于我们使用还是要理解这个过程,都不可获取。

  • performBuild()方法,上面已经提到了,主要是为了构建过滤器链,还是看一下源代码吧
protected DefaultSecurityFilterChain performBuild() throws Exception {
        Collections.sort(filters, comparator);
        return new DefaultSecurityFilterChain(requestMatcher, filters);
}
  • 对过滤器进行排序,然后返回了创建的DefaultSecurityFilterChain对象

1.2.4 HttpSecurity实现HttpSecurityBuilder的方法

  • authenticationProvider()方法
    这个方法不准备列举源代码了,就是提供一个设置AuthenticationProvider的方法,至于为什么要说一下,就是为了支出HttpSecurity可以设置AuthenticationProvider至于说AuthenticationProvider有什么用,以后具体说,但是这里还是想先大概说一下,他是具体的用户验证的方式,比如用户名密码形式,邮箱密码形式,短信验证码形式的登录等等吧。
  • userDetailsService()方法
    设置userDetailsService()后面也会说到,主要有一个方法根据username去获取用户信息。然后根据获取到的用户比对密码是否正确的。后面再说吧,先简单提一下
  • addFilterAfter()方法
public HttpSecurity addFilterAfter(Filter filter, Class afterFilter) {
    comparator.registerAfter(filter.getClass(), afterFilter);
    return addFilter(filter);
}
  • 先注册到过滤器比较器里面,因为要排序

  • 然以后添加到过滤器列表中

  • addFilterBefore方法,参考addFilterAfter()方法吧,差不多

  • addFilter方法 自动排序的过滤器,但是从注释上我们必须认识到,添加的过滤器必须继承自一下的某一个过滤器

* 
    *
  • {@link ChannelProcessingFilter}
  • *
  • {@link ConcurrentSessionFilter}
  • *
  • {@link SecurityContextPersistenceFilter}
  • *
  • {@link LogoutFilter}
  • *
  • {@link X509AuthenticationFilter}
  • *
  • {@link AbstractPreAuthenticatedProcessingFilter}
  • *
  • CasAuthenticationFilter
  • *
  • {@link UsernamePasswordAuthenticationFilter}
  • *
  • {@link ConcurrentSessionFilter}
  • *
  • {@link OpenIDAuthenticationFilter}
  • *
  • {@link org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter}
  • *
  • {@link org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter}
  • *
  • {@link ConcurrentSessionFilter}
  • *
  • {@link DigestAuthenticationFilter}
  • *
  • {@link org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationFilter}
  • *
  • {@link BasicAuthenticationFilter}
  • *
  • {@link RequestCacheAwareFilter}
  • *
  • {@link SecurityContextHolderAwareRequestFilter}
  • *
  • {@link JaasApiIntegrationFilter}
  • *
  • {@link RememberMeAuthenticationFilter}
  • *
  • {@link AnonymousAuthenticationFilter}
  • *
  • {@link SessionManagementFilter}
  • *
  • {@link ExceptionTranslationFilter}
  • *
  • {@link FilterSecurityInterceptor}
  • *
  • {@link SwitchUserFilter}
  • *

1.2.4 HttpSecurity配置的部分方法

这部分还是参考一下源代码吧,太多太多了,其实在我们使用的时候也可以参考,因为这些方法的注释上都给出了具体的例子,下面简单的看一下吧,就比如formLogin()这个方法

    /**
     *
     * 指定支持基于表单的身份验证. If
     * 若果没有指定loginPage()这个配置,那么将使用默认的登录页面
     *
     * 

Example Configurations

* 默认的登录的URL为 /login *
     * @Configuration
     * @EnableWebSecurity
     * public class FormLoginSecurityConfig extends WebSecurityConfigurerAdapter {
     *
     *  @Override
     *  protected void configure(HttpSecurity http) throws Exception {
     *      http.authorizeRequests().antMatchers("/**").hasRole("USER").and().formLogin();
     *  }
     *
     *  @Override
     *  protected void configure(AuthenticationManagerBuilder auth) throws Exception {
     *      auth.inMemoryAuthentication().withUser("user").password("password").roles("USER");
     *  }
     * }
     * 
* * The configuration below demonstrates customizing the defaults. * *
     * @Configuration
     * @EnableWebSecurity
     * public class FormLoginSecurityConfig extends WebSecurityConfigurerAdapter {
     *
     *  @Override
     *  protected void configure(HttpSecurity http) throws Exception {
     *      http.authorizeRequests().antMatchers("/**").hasRole("USER").and().formLogin()
     *              .usernameParameter("username") // default is username
     *              .passwordParameter("password") // default is password
     *              .loginPage("/authentication/login") // default is /login with an HTTP get
     *              .failureUrl("/authentication/login?failed") // default is /login?error
     *              .loginProcessingUrl("/authentication/login/process"); // default is /login
     *                                                                      // with an HTTP
     *                                                                      // post
     *  }
     *
     *  @Override
     *  protected void configure(AuthenticationManagerBuilder auth) throws Exception {
     *      auth.inMemoryAuthentication().withUser("user").password("password").roles("USER");
     *  }
     * }
     * 
* * @see FormLoginConfigurer#loginPage(String) * * @return the {@link FormLoginConfigurer} for further customizations * @throws Exception */ public FormLoginConfigurer formLogin() throws Exception { return getOrApply(new FormLoginConfigurer<>()); }

2 从配置到Filter

我们还是需要看一下HttpSecurity的配置最后是怎样影响到我们的过滤器执行的
我们还是用两个简单的例子来看一下工作原理

2.1 formLogin()的原理

2.1.1 formLogin配置的例子

我们也不需要自己手写一个例子出来,然后说一大堆,我们直接可以拿到方法上面注释的例子来看一下就可以了,只是简单的替换掉了转义的字符和一点点英文的注释

 下面的配置演示了自定义默认值。
@Configuration
@EnableWebSecurity
 public class FormLoginSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {  
    http.authorizeRequests().antMatchers("/**").hasRole("USER").and().formLogin()
                .usernameParameter("username") //默认的用户名的参数 username
                .passwordParameter("password") // 默认的密码的参数 password
                .loginPage("/authentication/login") // 默认请求地址 /login with an HTTP get
                .failureUrl("/authentication/login?failed") //默认失败地址 /login?error
                .loginProcessingUrl("/authentication/login/process"); // default is /login                                                                      // with an HTTP
                                                                        // post
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().withUser("user").password("password").roles("USER");
    }
 }

上面的例子做了一下几件事情

  • 在内存中创建了一个用户,用户名user,密码password,拥有角色USER
  • 配置了任何请求的用户都必须拥有USER角色
  • 配置了登录请求的地址为/authentication/login,失败的地址为/authentication/login?failed,用户名提交的参数为username,密码提交的参数为password,虽然用户名和密码的参数默认也是这样,但是我们也知道这里可以自定义

2.1.2 formLogin背后的实现原理

  • 我们看一下在HttpSecurity中formLogin()的方法
//formlogin()方法
public FormLoginConfigurer formLogin() throws Exception {
    //T1这里的FormLoginConfigurer和getOrApply方法我们都不知道是啥玩意儿
    return getOrApply(new FormLoginConfigurer<>());
}

//getOrApply()方法
private > C getOrApply(
            C configurer) throws Exception {
        //T2 这里的意思是去查看是否有存在了
        C existingConfig = (C) getConfigurer(configurer.getClass());
        if (existingConfig != null) {
            return existingConfig;
        }
           //T3 不存在的情况下就去调用apply()这个方法
        return apply(configurer);
    }

//apply()方法
public > C apply(C configurer)
            throws Exception {
         //T4
        configurer.addObjectPostProcessor(objectPostProcessor);
        //T5    
        configurer.setBuilder((B) this);
        //T6    
        add(configurer);
        return configurer;
    }
  • T1 这里的FormLoginConfigurer后面下面说
  • T2 这里的C泛型指的就是FormLoginConfigurergetConfigurer()方法的源代码自行查看,这个方法大概的意图就是LinkedHashMap>, List>> configurers在这个map中如果存在这个FormLoginConfigurer的配置就返回,否则返回null
  • T4 这里添加一个后置处理器,先不说
  • T5 设置this到builder里面,后面会有用
  • T6 将FormLoginConfigurer添加到LinkedHashMap>, List>> configurers的map中

2.1.3 FormLoginConfigurer

  • 类图


    Spring Security原理篇(三) HttpSecurity_第2张图片
    FormLoginConfigurer类图
  • 我们看一下构造方法

public FormLoginConfigurer() {
        super(new UsernamePasswordAuthenticationFilter(), null);
        usernameParameter("username");
        passwordParameter("password");
    }

调用父类构造方法的时候创建了UsernamePasswordAuthenticationFilter,然后复制给了一个叫做authFilter的变量,所以我们知道,其实在创建FormLoginConfigurer的时候,他自己就已经有了一个叫做UsernamePasswordAuthenticationFilter

  • FormLoginConfigurerFilter转换的configure()方法
@Override
    public void configure(B http) throws Exception {
        PortMapper portMapper = http.getSharedObject(PortMapper.class);
        if (portMapper != null) {
            authenticationEntryPoint.setPortMapper(portMapper);
        }

        RequestCache requestCache = http.getSharedObject(RequestCache.class);
        if (requestCache != null) {
            this.defaultSuccessHandler.setRequestCache(requestCache);
        }

        authFilter.setAuthenticationManager(http
                .getSharedObject(AuthenticationManager.class));
        authFilter.setAuthenticationSuccessHandler(successHandler);
        authFilter.setAuthenticationFailureHandler(failureHandler);
        if (authenticationDetailsSource != null) {
            authFilter.setAuthenticationDetailsSource(authenticationDetailsSource);
        }
        SessionAuthenticationStrategy sessionAuthenticationStrategy = http
                .getSharedObject(SessionAuthenticationStrategy.class);
        if (sessionAuthenticationStrategy != null) {
            authFilter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy);
        }
        RememberMeServices rememberMeServices = http
                .getSharedObject(RememberMeServices.class);
        if (rememberMeServices != null) {
            authFilter.setRememberMeServices(rememberMeServices);
        }
        F filter = postProcess(authFilter);
        http.addFilter(filter);
    }

这段代码大部分的都是在设置一些Filter执行需要的属性。这个和具体的这个配置到底是完成什么样的功能有关系,然后我们最需要关心的只有下面这行代码

http.addFilter(filter);

也就是说最后往HttpSecurity的List filters列表中添加了一个Filter对象

  • 触发FormLoginConfigurer的configure()方法调用
    HttpSecurity调用build()方法的时候回调用dobuild()方法,然后configure()方法


    Spring Security原理篇(三) HttpSecurity_第3张图片

    这个configure方法回调用HttpSecurity的所有的configure方法,然后转换成过滤器添加到filters方法中

private void configure() throws Exception {
        Collection> configurers = getConfigurers();

        for (SecurityConfigurer configurer : configurers) {
            configurer.configure((B) this);
        }
    }

至于说这个configurers属性我们前面说过每一个配置后面调用apply()方法都会添加到这个列表中,这里不再赘述

2.2 直接添加过滤器

上面添加过滤器是通过对HttpSecurity方法的调用实现配置,最后添加过滤器,然而直接添加过滤器就更加简单,下面只通过一个简单的例子来说一下

2.2.1 addFilterAfter方法

直接看一下源代码就行,因为实在太简单了

public HttpSecurity addFilterAfter(Filter filter, Class afterFilter) {
    comparator.registerAfter(filter.getClass(), afterFilter);
    return addFilter(filter);
}
public HttpSecurity addFilter(Filter filter) {
        Class filterClass = filter.getClass();
        if (!comparator.isRegistered(filterClass)) {
            throw new IllegalArgumentException(
                    "The Filter class "
                            + filterClass.getName()
                            + " does not have a registered order and cannot be added without a specified order. Consider using addFilterBefore or addFilterAfter instead.");
        }
        this.filters.add(filter);
        return this;
    }

我们可以看到只是先确定过滤器的顺序,然后看一下过滤器是否注册了,然后就会添加到我们的filters这个变量。

3 总结

杂乱无章的几乎介绍了这个类的所有的代码,我们总结一下这个类吧
*HttpSecurity最终可以得到一个DefaultSecurityFilterChain通过的是build()方法

  • HttpSecurity维护了一个过滤器的列表,这个过滤器的列表最终放入了DefaultSecurityFilterChain这个过滤器链中
  • HttpSecurity最终提供了很多的配置,然而所有的配置也都是为了处理维护我们的过滤器列表

你可能感兴趣的:(Spring Security原理篇(三) HttpSecurity)