Spring Security原理篇(三) HttpSecurity

Spring Security原理篇(三) HttpSecurity

本文是从怪诞140819 搬运而来,作者写的很好,于是搬运之。@怪诞140819 如有冒犯,请私信我

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 WebSecurityConfigurerAdapter的getHttp()方法

首先我们还是引入一下这个方法的源代码

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<Class<? extends Object>, 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<AbstractHttpConfigurer> 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的类图

HttpSecurity类图

在说Filter的时候我们说到过WebSecuritybuild()方法的时候有很具体的说到AbastractSecurityBuilderAbstractConfiguredSecurityBuilder这两个类中build()方法执行的过程我们看一下HttpSecurity类的定义

public final class HttpSecurity extends
        AbstractConfiguredSecurityBuilder<DefaultSecurityFilterChain, HttpSecurity>
        implements SecurityBuilder<DefaultSecurityFilterChain>,
        HttpSecurityBuilder<HttpSecurity> 

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

1.2.2HttpSecurity的属性
     //从变量名就可以看到是请求匹配过滤的配置信息    

    private final RequestMatcherConfigurer requestMatcherConfigurer;

    //过滤器列表?
    private List<Filter> 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这个的。反正就是获取到在过滤器列表中的顺序,不过当然不是连续的顺序

  1. filters的list 只是通过addFilter()方法添加进来,放一下源代码,不解释了
public HttpSecurity addFilter(Filter filter) {
        Class<? extends Filter> 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<Object> objectPostProcessor,
            AuthenticationManagerBuilder authenticationBuilder,
            Map<Class<? extends Object>, Object> sharedObjects) {
        super(objectPostProcessor);
        Assert.notNull(authenticationBuilder, "authenticationBuilder cannot be null");
        setSharedObject(AuthenticationManagerBuilder.class, authenticationBuilder);
        for (Map.Entry<Class<? extends Object>, Object> entry : sharedObjects
                .entrySet()) {
            setSharedObject((Class<Object>) 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<? extends Filter> afterFilter) {
    comparator.registerAfter(filter.getClass(), afterFilter);
    return addFilter(filter);
}

先注册到过滤器比较器里面,因为要排序

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

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

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


* <ul>
     * <li>{@link ChannelProcessingFilter}</li>
     * <li>{@link ConcurrentSessionFilter}</li>
     * <li>{@link SecurityContextPersistenceFilter}</li>
     * <li>{@link LogoutFilter}</li>
     * <li>{@link X509AuthenticationFilter}</li>
     * <li>{@link AbstractPreAuthenticatedProcessingFilter}</li>
     * <li><a href="{@docRoot}/org/springframework/security/cas/web/CasAuthenticationFilter.html">CasAuthenticationFilter</a></li>
     * <li>{@link UsernamePasswordAuthenticationFilter}</li>
     * <li>{@link ConcurrentSessionFilter}</li>
     * <li>{@link OpenIDAuthenticationFilter}</li>
     * <li>{@link org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter}</li>
     * <li>{@link org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter}</li>
     * <li>{@link ConcurrentSessionFilter}</li>
     * <li>{@link DigestAuthenticationFilter}</li>
     * <li>{@link org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationFilter}</li>
     * <li>{@link BasicAuthenticationFilter}</li>
     * <li>{@link RequestCacheAwareFilter}</li>
     * <li>{@link SecurityContextHolderAwareRequestFilter}</li>
     * <li>{@link JaasApiIntegrationFilter}</li>
     * <li>{@link RememberMeAuthenticationFilter}</li>
     * <li>{@link AnonymousAuthenticationFilter}</li>
     * <li>{@link SessionManagementFilter}</li>
     * <li>{@link ExceptionTranslationFilter}</li>
     * <li>{@link FilterSecurityInterceptor}</li>
     * <li>{@link SwitchUserFilter}</li>
     * </ul>
1.2.4 HttpSecurity配置的部分方法

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


    /**
     *
     * 指定支持基于表单的身份验证. If
     * 若果没有指定loginPage()这个配置,那么将使用默认的登录页面
     *
     * <h2>Example Configurations</h2>
     * 默认的登录的URL为 /login
    
     * <pre>
     * &#064;Configuration
     * &#064;EnableWebSecurity
     * public class FormLoginSecurityConfig extends WebSecurityConfigurerAdapter {
     *
     *  &#064;Override
     *  protected void configure(HttpSecurity http) throws Exception {
     *      http.authorizeRequests().antMatchers(&quot;/**&quot;).hasRole(&quot;USER&quot;).and().formLogin();
     *  }
     *
     *  &#064;Override
     *  protected void configure(AuthenticationManagerBuilder auth) throws Exception {
     *      auth.inMemoryAuthentication().withUser(&quot;user&quot;).password(&quot;password&quot;).roles(&quot;USER&quot;);
     *  }
     * }
     * </pre>
     *
     * The configuration below demonstrates customizing the defaults.
     *
     * <pre>
     * &#064;Configuration
     * &#064;EnableWebSecurity
     * public class FormLoginSecurityConfig extends WebSecurityConfigurerAdapter {
     *
     *  &#064;Override
     *  protected void configure(HttpSecurity http) throws Exception {
     *      http.authorizeRequests().antMatchers(&quot;/**&quot;).hasRole(&quot;USER&quot;).and().formLogin()
     *              .usernameParameter(&quot;username&quot;) // default is username
     *              .passwordParameter(&quot;password&quot;) // default is password
     *              .loginPage(&quot;/authentication/login&quot;) // default is /login with an HTTP get
     *              .failureUrl(&quot;/authentication/login?failed&quot;) // default is /login?error
     *              .loginProcessingUrl(&quot;/authentication/login/process&quot;); // default is /login
     *                                                                      // with an HTTP
     *                                                                      // post
     *  }
     *
     *  &#064;Override
     *  protected void configure(AuthenticationManagerBuilder auth) throws Exception {
     *      auth.inMemoryAuthentication().withUser(&quot;user&quot;).password(&quot;password&quot;).roles(&quot;USER&quot;);
     *  }
     * }
     * </pre>
     *
     * @see FormLoginConfigurer#loginPage(String)
     *
     * @return the {@link FormLoginConfigurer} for further customizations
     * @throws Exception
     */
    public FormLoginConfigurer<HttpSecurity> 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背后的实现原理

我们看一下在HttpSecurityformLogin()的方法

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

//getOrApply()方法
private <C extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity>> 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 extends SecurityConfigurerAdapter<O, B>> C apply(C configurer)
            throws Exception {
         //T4
        configurer.addObjectPostProcessor(objectPostProcessor);
        //T5    
        configurer.setBuilder((B) this);
        //T6    
        add(configurer);
        return configurer;
    }
  • T1 这里的FormLoginConfigurer后面下面说
  • T2 这里的C泛型指的就是FormLoginConfigurer,getConfigurer()方法的源代码自行查看,这个方法大概的意图就是LinkedHashMap<Class<? extends SecurityConfigurer<O, B>>, List<SecurityConfigurer<O, B>>> configurers在这个map中如果存在这个FormLoginConfigurer的配置就返回,否则返回null
  • T4 这里添加一个后置处理器,先不说
  • T5 设置this到builder里面,后面会有用
  • T6 将FormLoginConfigurer添加到LinkedHashMap<Class<? extends SecurityConfigurer<O, B>>, List<SecurityConfigurer<O, B>>> configurers的map中
2.1.3 FormLoginConfigurer类

类图
FormLoginConfigurer类图

我们看一下构造方法

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

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

FormLoginConfigurer向Filter转换的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对象

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

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

private void configure() throws Exception {
Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();

    for (SecurityConfigurer<O, B> configurer : configurers) {
        configurer.configure((B) this);
    }
}

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

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

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

public HttpSecurity addFilterAfter(Filter filter, Class<? extends Filter> afterFilter) {
comparator.registerAfter(filter.getClass(), afterFilter);
return addFilter(filter);
}
public HttpSecurity addFilter(Filter filter) {
Class<? extends Filter> 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最终提供了很多的配置,然而所有的配置也都是为了处理维护我们的过滤器列表

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Security是一个基于Spring框架的安全性框架,主要用于认证、授权和攻击防护。它的底层原理主要是通过Filter来处理请求,实现对请求的拦截和处理。具体来说,Spring Security的底层原理包括以下几个方面: 1. 过滤器链:Spring Security通过一系列的过滤器来处理请求,这些过滤器按照一定的顺序组成了一个过滤器链。每个过滤器都有特定的功能,例如身份验证、授权、会话管理等。 2. 安全上下文:Spring Security通过SecurityContextHolder来管理安全上下文,其中包括了当前用户的身份信息、权限信息等。在处理请求时,Spring Security会从安全上下文中获取相应的信息来进行处理。 3. 用户认证:Spring Security提供了多种用户认证方式,包括基于表单、HTTP基本认证、LDAP认证等。在用户认证过程中,Spring Security会根据用户提供的信息进行身份验证,并将验证结果存储到安全上下文中。 4. 授权管理:Spring Security提供了多种授权方式,包括基于角色、基于权限、基于表达式等。在授权管理过程中,Spring Security会根据用户的身份信息和请求的URL等信息来判断用户是否有相应的权限。 5. 攻击防护:Spring Security提供了多种攻击防护机制,包括CSRF防护、XSS防护、SQL注入防护等。在处理请求时,Spring Security会对请求进行安全性检查,防止恶意攻击。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值