Sping Security (一): 入门原理

428 阅读5分钟

Spring Security 入门原理

在web应用开发中,一种是不需要用户登录即可访问全部资源的,比如公司官网。而在更多的情况下web应用是需要用户登录之后才可以访问某些资源的,比如购物网站需要登录才能访问用户的账户信息,网站后台也需要管理员登录之后才可以操作。在登录过程中,应用的安全无疑是十分重要的。
选择Spring Security来保护web应用是一个非常不错的选择,Spring Security是spring项目中的一个安全模块,可以非常方便地集成到spring项目中。特别是在springBoot中使用起来更是非常简单,而且也可以非常方便地与oauth2.0 组合使用。
在接下来的篇幅中,我们会分入门原理、认证流程解析、授权流程解析、聚会oauth2、与其他安全框架的比较这几个篇幅进行介绍。每篇都会提供相应的代码示例。 本篇介绍的是入门原理。

说明: 本系列的文章使用的是 springBoot 2.7.11 (springboot 2x 最后一个稳定版本),对应的 Spring Security 是 5.7.8

流程

image.png

入门项目

pom文件

<parent>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-parent</artifactId>
   <version>2.7.11</version>
   <relativePath/> <!-- lookup parent from repository -->
</parent>

<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>

代码示例

... ... ...

没有代码 没有代码 没有代码

把 security包导入之后,springboot就自动配置完成了,真的是开箱即用。

  • 默认密码

在控制台有一行注释需要特别注意

image.png

UserDetailsServiceAutoConfiguration 中打印了一条日志,意思就是生成了一个安全密码,并且友情提示在开发模式下才这么使用。生成环境下需要自行去配置。

  • 登录页面 我们尝试访问应用的任何一个url,最终都会跳到登录页 http://localhost:8080/login。 登录页提示我们输入账号,密码进行登录。

image.png

  • 登录账号 回到上面的 UserDetailsServiceAutoConfiguration 可以很清晰地看到

image.png 初始了一个Bean (实际就是一个保存在内存中的登录账号),点击其中的User

image.png 可以看到,默认登录账号是 user, 登录密码是 UUID生成的字符串。

到这里为止,我们弄清了 Security的默认登录账号密码是如何产生的,当然我们也可以通过在application.yml文件中自由配置登录账号密码

spring:
    security:
        user:
            name: admin
            password: 1234

原理介绍

Spring Securtiy 对 web (也就是 servlet)的支持是基于 Servlet过滤器的。根据不同的功能需要,定义了很多拦截器,然后这些拦截器按照一定顺序组装成一个拦截链。

FilterChain

下图显示了处理单个HTTP请求时的流程

image.png

客户端向应用程序发送一个请求,容器根据配置创建一个 FilterChain ,其中包含 FilterServlet 。应用根据请求URL来处理 HttpServletRequest。 在Spring MVC应用程序中,Servlet是 DispatchServlet的一个实例。一个 Servlet 最多可以处理一个 HttpServletRequest 和 HttpServletResponse

DelegatingFilterProxy

在一个web应用启动之后,会有以下几种容器

  • Web容器:管理Listener(监听器)、Filter(过滤器)、间接管理
  • Servlet(通过Servlet容器)
  • Servlet容器:管理Servlet
  • Spring容器:管理Service、Dao
  • SpringMVC容器:管理Controller
  • SpringBoot容器:管理所有Bean

我们在项目中配置的bean一般情况下是被spring 容器管理管理,但是拦截链是 servlet容器调用的。如何让spring容器的bean 来作为 servlet容器的拦截器使用呢?

spring securtiy 使用的 DelegatingFilterProxy 将 servlet的拦截链中某个拦截器委托给 spring 的bean 来执行拦截动作。

image.png

image.png

由于security 支持配置多个拦截链,所以可以拓展为

image.png

以上就是security 拦截链的核心原理。关于secutity是如何认证的,会在下一个篇幅进行介绍。

spring Boot 自动地:

  • 启用Spring Security的默认配置,该配置将创建一个名为 springSecurityFilterChainservlet Bean。这个Bean负责你的应用程序中的所有安全(保护应用程序的URL,验证提交的用户名和密码,重定向到登录表单,等等)。
  • 创建一个 UserDetailsService Bean,它的用户名是 user,密码是随机生成的,会被输出到控制台。
  • 在每个请求中,用一个名为 springSecurityFilterChain 的 Bean 向 Servlet容器注册 Filter

关于security的自动配置

两个重要自动配置类 ,都属于 spring.boot.autoconfigure 这个包中

SecurityAutoConfiguration : 负责 Security 相关配置进行装配

SecurityFilterAutoConfiguration :在上述自动装配完成之后,对SecurityFilter 进行自动装配

  • SecurityAutoConfiguration

    @AutoConfiguration
    @ConditionalOnClass(DefaultAuthenticationEventPublisher.class) 
    //该class 属于 spring-security包中,也就是引入了security相关包才会进行spring-security功能自动装配
    @EnableConfigurationProperties(SecurityProperties.class)
    //将 SecurityProperties 这个配置bean加到 IOC容器
    @Import({ SpringBootWebSecurityConfiguration.class, SecurityDataConfiguration.class })
    //导入其他配置类
    public class SecurityAutoConfiguration {
    ​
        @Bean
        @ConditionalOnMissingBean(AuthenticationEventPublisher.class)
        //如果没有自定义配置 AuthenticationEventPublisher bean,则配置默认 
        public DefaultAuthenticationEventPublisher authenticationEventPublisher(ApplicationEventPublisher publisher) {
            return new DefaultAuthenticationEventPublisher(publisher);
        }
    ​
    }
    

    通过上述自动装配的流程,可以发现可以自定义 AuthenticationEventPublisher ,看名字是认证事件通知发布器,具体作用后面再细看。

    默认的 SecurityConfiguration 配置类

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnWebApplication(type = Type.SERVLET)
    class SpringBootWebSecurityConfiguration {
    ​
    ​
        @Configuration(proxyBeanMethods = false)
        @ConditionalOnDefaultWebSecurity
        //进一步了解这个注解,可以发现,这个bean是否自动装配,如果没有 WebSecurityConfigurerAdapter 或者 SecurityFilterChain 则进行装配
        @ConditionalOnMissingBean({
                org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter.class,
                SecurityFilterChain.class })
        static class Beans {
    ​
        }
        
        static class SecurityFilterChainConfiguration {
    ​
            @Bean
            @Order(SecurityProperties.BASIC_AUTH_ORDER)
            //默认的 SecurityFilterChain 
            SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
                http.authorizeRequests().anyRequest().authenticated();
                //拦截全部请求
                http.formLogin();
                //表单登录
                http.httpBasic();
                //认证模式为 basic
                return http.build();
            }
    ​
        }
    ​
    ​
        @Configuration(proxyBeanMethods = false)
        @ConditionalOnClass(WebInvocationPrivilegeEvaluator.class)
        //该bean 属于 spring-security-web中
        @ConditionalOnBean(WebInvocationPrivilegeEvaluator.class)
        //错误页配置
        static class ErrorPageSecurityFilterConfiguration {
    ​
            @Bean
            FilterRegistrationBean<ErrorPageSecurityFilter> errorPageSecurityFilter(ApplicationContext context) {
                FilterRegistrationBean<ErrorPageSecurityFilter> registration = new FilterRegistrationBean<>(
                        new ErrorPageSecurityFilter(context));
                registration.setDispatcherTypes(DispatcherType.ERROR);
                return registration;
            }
    ​
        }
    ​
        @Configuration(proxyBeanMethods = false)
        @ConditionalOnMissingBean(name = BeanIds.SPRING_SECURITY_FILTER_CHAIN)
        @ConditionalOnClass(EnableWebSecurity.class)
        //spring-security config中的类
        @EnableWebSecurity
        static class WebSecurityEnablerConfiguration {
    ​
        }
    ​
    }
    ​
    

    从 SpringBootWebSecurityConfiguration 中可以发现,如果可以实现一个 WebSecurityConfigurerAdapter 或者 SecurityFilterChain 的 bean 即可改写原有的装配流程。

  • SecurityFilterAutoConfiguration

    @AutoConfiguration(after = SecurityAutoConfiguration.class)
    @ConditionalOnWebApplication(type = Type.SERVLET)
    @EnableConfigurationProperties(SecurityProperties.class)
    @ConditionalOnClass({ AbstractSecurityWebApplicationInitializer.class, SessionCreationPolicy.class })
    public class SecurityFilterAutoConfiguration {
    ​
        private static final String DEFAULT_FILTER_NAME = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME;
    ​
        @Bean
        @ConditionalOnBean(name = DEFAULT_FILTER_NAME)
        //servlet 容器依靠这个代理bean,去spring容器中寻找特定的bean,委托spring中的bean来执行具体的过滤器操作
        public DelegatingFilterProxyRegistrationBean securityFilterChainRegistration(
                SecurityProperties securityProperties) {
            DelegatingFilterProxyRegistrationBean registration = new DelegatingFilterProxyRegistrationBean(
                    DEFAULT_FILTER_NAME);
            registration.setOrder(securityProperties.getFilter().getOrder());
            registration.setDispatcherTypes(getDispatcherTypes(securityProperties));
            return registration;
        }
    ​
        private EnumSet<DispatcherType> getDispatcherTypes(SecurityProperties securityProperties) {
            if (securityProperties.getFilter().getDispatcherTypes() == null) {
                return null;
            }
            return securityProperties.getFilter()
                .getDispatcherTypes()
                .stream()
                .map((type) -> DispatcherType.valueOf(type.name()))
                .collect(Collectors.toCollection(() -> EnumSet.noneOf(DispatcherType.class)));
        }
    ​
    }
    

参考文章