zoukankan      html  css  js  c++  java
  • Spring Security基本原理

    近期研究了Spring Security,现进行记录。

    首先先进行一个最简单的demo。
    默认情况下,在Spring Boot里,如果在classpath下面有Spring Security相关的jar包,那么Spring Boot会自动地替我们做一些安全的配置。本Demo正是基于Spring Boot构建,使用maven进行项目管理。maven核心POM依赖配置如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    <dependencies>

    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-oauth2</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <!--Spring Security Web相关-->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    </dependency>
    </dependencies>
    <dependencyManagement>
    <dependencies>
    <!--Spring 依赖管理平台-->
    <dependency>
    <groupId>io.spring.platform</groupId>
    <artifactId>platform-bom</artifactId>
    <version>Brussels-SR9</version>
    <type>pom</type>
    <scope>import</scope>
    </dependency>
    <!--Spring Cloud依赖管理-->
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-dependencies</artifactId>
    <version>Edgware.SR3</version>
    <type>pom</type>
    <scope>import</scope>
    </dependency>
    </dependencies>
    </dependencyManagement>

    采用的Spring Boot版本为1.5.12.RELEASE,引入Spring依赖管理平台,最大程度地减少一些依赖的冲突情况,高版本的spring-cloud-starter-oauth2已经包含了Spring Security的依赖,因此引入这一个依赖即可。
    然后写一个最简单的demo,只提供一个Rest服务:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

    @RestController
    public class {

    public static void main(String[] args) {
    SpringApplication.run(SpringApplicationDemoApplication.class, args);
    }

    @GetMapping("/hello")
    public String hello(){
    return "Hello,Spring Security!";
    }

    }

    然后启动应用,访问http://localhost:8080/hello,发现会弹一个如下的Http Basic认证框,让我们输入用户名和密码。
    2018427232339
    在不进行任何自定义配置的情况下,Spring Security默认生成的用户名是固定的user,密码的话我们回来看看之前启动Spring Boot时的控制台,可以看到有这么一句话:
    Using default security password: fb39f959-fe9c-4d51-abb8-7303cfba4d30

    这个就是Spring Security在启动时为我们随机生成的密码。
    2018427232619

    我们将密码拷贝下来,进行登录,便能够看到我们之前写的那个服务的响应了。

    2018427233524

    从这个例子里我们可以看到,在默认的情况下,不做任何配置的时候,Spring Security做了这么两件事:

    1. Spring Security将我们所有的服务都保护起来了,任何一个Rest服务,要想调用都要先进行身份认证。
    2. 身份认证的方式就是上图中的http basic的方式,这个是Spring Security默认的一个行为。

    但是,这个行为肯定是不能满足我们的需求的,因为没有任何一个应用会用这种Http Basic的方式去做用户的身份校验。
    那么,如何覆盖掉Spring Security默认的行为?比如说,提供一个包含用户名和密码的表单登录。我们来看一下,如何处理这样的一个场景。
    具体做法是,我们可以编写一个类,继承WebSecurityConfigurerAdapter,这个是Spring Security提供的一个适配器类,从这个名字我们就可以看出来,它是专门做Web安全应用配置的一个适配器,我们在类上写一个@Configuration注解将它声明为一个配置类,并override掉适配器里面的一个configure方法,从源码里我们可以看到有三个configure方法,分别接收三个不同的参数:AuthenticationManagerBuilder,WebSecurity,HttpSecurity。

    接收AuthenticationManagerBuilder的方法:

    1
    2
    3
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    this.disableLocalConfigureAuthenticationBldr = true;
    }

    接收WebSecurity的方法:

    大专栏  Spring Security基本原理">1
    2
    3
    public void configure(WebSecurity web) throws Exception {

    }

    我们重点关注接收参数为HttpSecurity的configure方法,可以看到,它的默认配置正是:

    1
    2
    3
    4
    5
    6
    7
    8
    protected void configure(HttpSecurity http) throws Exception {
    http
    .authorizeRequests()
    .anyRequest().authenticated()
    .and()
    .formLogin().and()
    .httpBasic();
    }

    这段配置翻译过来,正是我们先前的结论:默认情况下Spring Security应用的所有请求都需要经过认证,并且认证方式为Http Basic。
    我们现在覆盖掉参数是HttpSecurity的方法,我们想要达到的一个效果是让它用表单的形式登录,那么我们只要像下面这样写:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    //启用基于表单形式的登录
    http.formLogin()
    .and()
    //下面的配置都是关于授权的配置
    .authorizeRequests()
    //任何请求
    .anyRequest()
    //都需要认证
    .authenticated();
    }

    http.formLogin就是使用基于表单登录进行认证的方式。正如我们一直所强调的,安全其实就是两件事,一个是认证,一个是授权,认证我们已经配置了,但是还不能落下授权。authorizeRequests()开始的就是关于授权的配置。通过五行代码,我们其实就已经定义了一个最简单的安全环境:用表单登录进行身份认证,所有的请求都需要身份认证后才能访问。
    定义了这个类之后,我们把系统重新启动,同样访问http://localhost:8080/hello ,这时候我们会发现跳到了如下的一个表单登录页:
    2018427213346
    在输入了用户名user和从控制台复制过来的随机密码后,我们同样系统在此从登录页跳回了之前的访问的服务,我们也再次看到先前的那个服务的响应。

    2018427233524

    注意
    这里有一个跳转,实际上我们一开始访问这个服务的时候,它给我们跳转到了登录页上,在认证成功后,又再次重定向到我们请求的服务上,这个也是Spring Security默认的登录成功处理器的一个行为。
    假设说,我们就是不想使用表单登录,就想使用Http Basic方式进行登录,那么应该怎么写呢?其实也很简单,我们只需要像下面这样写:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    //启用基于Http Basic形式的登录
    http.httpBasic()
    .and()
    //下面的配置都是关于授权的配置
    .authorizeRequests()
    //任何请求
    .anyRequest()
    //都需要认证
    .authenticated();
    }

    启动应用后,发现又回到之前Http Basic登录认证的方式了。
    例子我们暂时先演示到这里,基于这个例子我们已经对Spring Security有了一个基本印象,基于这个印象我们接下来来看看Spring Security的基本原理。

    Spring Security基本原理

    2018429172853

    上图中,最右边这个就是我们的Rest API,也就是我们刚才写的Controller,左边的这组过滤器链正是Spring Security的核心。实际上Spring Security的本质就是一组Filter。因此所有访问服务的请求都会经过Spring Security的过滤器,同样服务器的响应也是会经过这些过滤器然后返回给终端。这些过滤器在系统启动的时候Spring Boot会自动把它配置到上下文上,这个我们暂时先不关注。我们现在主要关注一下这个过滤器链上有哪些过滤器,当然,也是主要的几种过滤器。
    首先,最核心的就是上图中绿色的那些过滤器,它的作用是用来认证用户的身份.每一个方块代表一种过滤器,每一种过滤器负责处理一种认证方式。在先前的例子里我们举了两种认证方式:一种是表单登录,一种是Http Basic方式登录,相应地,会有两个类来处理这两种认证请求,也就是上图中的UsernamePasswordAuthenticationFilter(处理表单登录)和BasicAuthenticationFilter(处理HttpBasic登录)。这些绿色的过滤器的主要作用是检查当前的请求里面是不是有这些过滤器所需要的信息.比如说,对UsernamePassword这个过滤器来说,首先它会检查你这个请求是不是一个登录请求,其次它会检查这个登录请求里带没带用户名密码。如果这个登录请求中携带了用户名和密码,这个过滤器就会尝试用这个用户名和密码去进行登录。如果这个请求中没有携带用户名和密码,那么这个过滤器就会把这个请求放行到下一个过滤器。下一个过滤器比如说是BasicAuthenticationFilter,那么这个过滤器就会检查你的请求头里面是不是有Basic开头的这种Authorization信息,如果有的话,它会尝试拿出来做Base64解码,然后取出用户名和密码去尝试进行登录。然后如果我们还有其他的认证方式(其实在Spring Security里它还提供了其他的认证方式),那么按照这个原理它会一个一个往下走,任何一个过滤器它成功地完成了用户的登录之后,它会在请求上做一个标记,告诉后面的过滤器当前的用户已经认证成功了。最终请求经过了绿色的过滤器以后会到一个叫做FilterSecutrityInterceptor的过滤器,这个过滤器是整个Spring Security的最后一环,在这个“守门人”的身后,就是我们自己写的服务了。因此在这个服务里面,它会去决定我们当前的请求能不能去访问后面的服务。那么它依据什么判断呢?就是依据我们代码里的配置。比如说我们先前的配置:所有的请求都要经过身份认证后才能访问,那么它就会去判断当前的请求是不是经过了前面某一个过滤器的身份认证。当然,前面的那个认证配置其实可以配置得很复杂的,比如说某些请求只有管理员才能访问,那么这些规则都会放在FilterSecutrityInterceptor过滤器里面,这个过滤器会根据这些规则去验证。验证的结果也只有两种,如果验证通过的话,那么这个请求就可以访问我们最终的服务了,如果验证不通过,那么根据不通过的原因,它会抛出不同的异常.比如说先前的配置中如果配了所有的请求都要经过身份认证但是实际请求并没有经过认证的话那么它会抛一个身份认证不通过的异常,再比如说先前的配置中如果配置了请求必须要管理员才能访问,那么即使你经过了身份认证但是你不是VIP用户的话那么这个过滤器就会抛一个没权限的异常。总而言之,它会根据不能访问的原因抛出不同的异常。在这个异常抛出来以后,在这个过滤器的前面,还有一个叫做ExceptionTranslationFilter的过滤器,这个过滤器的作用就是用来捕获FilterSecutrityInterceptor所抛出来的异常,然后这个过滤器会根据抛出来的异常做相应的处理,比如说你是因为没有登录所以不能访问,那么它会根据前面那些过滤器的配置引导用户去进行登录.比如说前面我们配置了UsernamePasswordAuthenticationFilter,那么它就会把用户引导到一个登录页面上,比如说我们前面配置了BasicAuthenticationFilter,那么它就会在浏览器上弹一个HttpBasic认证框。这就是Spring Security提供的所有功能和特性都是建立在这个基础之上的。实际上我们工作中一些常见的定制开发比如说增加手机验证码登录,增加第三方QQ微信登录就是在这些绿色的过滤器链上增加不同的过滤器来支持这些不同的认证方式。实际这些程序在运行的时候上面的过滤器肯定不止这三种,一般一个普通的应用一旦运行都会有十几种过滤器,但是目前要想理解Spring Security的基本原理只需要理解这三种过滤器就行了。这里要注意的是,在这个过滤器链上,绿色的部分我们是可以通过配置来决定其是否生效,比如说我们不想用BasicAuthenticationFilter就可以在配置中移除httpBasic的属性,这样BasicAuthenticationFilter就不会生效。但是除了绿色的以外,其他的颜色的过滤器都是不能被我们控制的,它们一定会在过滤器链上并且它们一定会在Spring Security事先指定的位置,比如说这个蓝色的过滤器一定会在橙色的过滤器之前,这个位置我们是不能控制的,我们也不能把它从过滤器上移除。

    Spring Security源码初探

    l了解完Spring Security的基本原理之后,我们在Spring Security的源码上打一些断点,然后结合上面的图把整个原理再过一下。

  • 相关阅读:
    总结C# 调用c++ 开源代码使用问题
    nodejs v14使用await async
    一个简单的js文件,在ts中使用的方法
    ts项目+webpack+juuery 插件的引入
    js 立即执行的多种写法
    在webgl2上使用fabric做标记
    comobox 绑定datatable ,无法获取选择值问题
    axios 请求拦截并在 token 过期后自动续订后重调当前请求
    javascript hook 一个函数(不定参数个数)
    java Date 大坑
  • 原文地址:https://www.cnblogs.com/lijianming180/p/12037903.html
Copyright © 2011-2022 走看看