Bootstrap

Spring Security 6 系列之一 - 开篇入门

之所以想写这一系列,是因为之前工作过程中使用Spring Security,但当时基于spring-boot 2.3.x,其默认的Spring Security是5.3.x。之后新项目升级到了spring-boot 3.3.0,结果一看Spring Security也升级为6.3.0,关键是其风格和内部一些关键Filter大改,导致在配置同样功能时,多费了些手脚,因此花费了些时间研究新版本的底层原理,这里将一些学习经验分享给大家。

注意由于框架不同版本改造会有些使用的不同,因此本次系列中使用基本框架是 spring-boo-3.3.0(默认引入的Spring Security是6.3.0),JDK版本使用的是19,所有代码都在spring-security-study项目上:https://github.com/forever1986/spring-security-study.git

1 Spring Security

什么是Spring Security,以下是官方地址截图说明
在这里插入图片描述
这里是翻译工具翻译结果:

Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架。它是保护基于spring的应用程序的事实上的标准。
Spring Security是一个框架,专注于为Java应用程序提供身份验证和授权。与所有Spring项目一样,Spring Security的真正强大之处在于它可以轻松地扩展以满足自定义需求

我们可以从描述中把握几个关键词:身份认证访问控制(授权)标准自定义需求
说明Spring Security是一个具备身份认证和访问控制的框架,其流程认证和授权流程具备一定行业标准,同时还能通过其扩展点自定义符合自己业务的需求。下面我们就身份认证和授权2个功能以及其它类似Spring Security框架做一下详细说明

1.1 身份认证

我们在做业务系统的时候,对于某些界面、接口都需要做登录认证,其实就是身份认证。只有通过身份认证的请求才能访问程序,不然就会返回http401状态。Spring Security就默认内置身份认证功能,让用户无需太多代码,即可实现一个简单用户密码登录认证。

1.2 访问控制(授权)

访问控制或者授权,其目的是对资源进行细粒度的控制,比如某个页面或者接口,只有具备什么权限的用户才能够访问。我们如果做过权限系统,应该了解ACLs、RBAC、ABAC等权限模式,而Spring Security就内置了一种标准的权限模式,可通过配置或者注解方式实现类似ACLs权限模式。

1.3 其它框架

除了Spring Security之外,是否有类似的其它安全框架
Apache Shrio:Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。相对于Spring Security来说,是一款轻量级的框架
Sa-Token:Sa-Token 是一个轻量级 Java 权限认证框架,主要解决:登录认证、权限认证、Session会话、单点登录、OAuth2.0、微服务网关鉴权 等一系列权限相关问题,并且它是国内的开源项目。github上这么写:当你受够 Shiro、SpringSecurity 等框架的三拜九叩之后,你就会明白,相对于这些传统老牌框架,Sa-Token 的 API 设计是多么的简单、优雅!
CAS (Central Authentication Service):CAS是一个独立的企业级单点登录解决方案,为Web应用程序提供了一个集中的认证机制。它允许用户在多个应用程序中使用相同的凭证进行登录。

2 第一个入门示例

Spring Security其实是一个入门门槛较高的框架,但是自从和spring-boot集成之后,其使用就简便很多。话不多说,我们开始体验一把。

新建一个父模块:spring-security-study,该模块只作为统一pom引入作用,没有其它功能或代码
入门示例代码参考lesson01子模块

1)新建一个父模块spring-security-study,其pom文件引入如下:(这里只是声明版本,有些引入只是为了后面使用提前引入而已)

<?xml version="1.0" encoding="UTF-8"?>
<properties>
        <maven.compiler.source>19</maven.compiler.source>
        <maven.compiler.target>19</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <spring-boot.version>3.3.0</spring-boot.version>
        <commons-pool2.version>2.12.0</commons-pool2.version>
        <jaxb-api.version>2.3.1</jaxb-api.version>
        <jjwt.version>0.9.0</jjwt.version>
        <lombok.version>1.18.36</lombok.version>
        <mybatis-plus.version>3.5.5</mybatis-plus.version>
        <mysql.version>8.0.19</mysql.version>
        <druid.version>1.2.9</druid.version>
        <fastjson.version>1.2.83</fastjson.version>
    </properties>


    <dependencyManagement>
        <dependencies>
            <!-- 引入spring boot依赖-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!-- lombok依赖,用于get/set的简便-->
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>${lombok.version}</version>
            </dependency>
            <!-- mysql依赖,用于连接mysql数据库-->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>${mysql.version}</version>
            </dependency>
            <!-- mybatis-plus依赖,用于使用mybatis-plus-->
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
                <version>${mybatis-plus.version}</version>
                <type>jar</type>
            </dependency>
            <!-- jjwt依赖,用于使用JWT-->
            <dependency>
                <groupId>io.jsonwebtoken</groupId>
                <artifactId>jjwt</artifactId>
                <version>${jjwt.version}</version>
            </dependency>
            <!-- jaxb-api依赖,在java17之后,被移除了,但是在做JWT解析时需要使用-->
            <dependency>
                <groupId>javax.xml.bind</groupId>
                <artifactId>jaxb-api</artifactId>
                <version>${jaxb-api.version}</version>
            </dependency>
            <!-- pool2和druid依赖,用于mysql连接池-->
            <dependency>
                <groupId>org.apache.commons</groupId>
                <artifactId>commons-pool2</artifactId>
                <version>${commons-pool2.version}</version>
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid-spring-boot-starter</artifactId>
                <version>${druid.version}</version>
            </dependency>
            <!-- fastjson依赖,用于json转换-->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
                <version>${fastjson.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <!-- maven仓库 -->
    <repositories>
        <repository>
            <id>central</id>
            <url>https://maven.aliyun.com/repository/central</url>
            <releases>
                <enabled>true</enabled>
            </releases>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </repository>
    </repositories>

2)新建子模块lesson01,其pom文件引入如下:

<dependencies>
    <!--Spring Boot 的 web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!--Spring Boot 提供的 Security 启动器 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
</dependencies>

3)在子模块lesson01,新建一个DemoController,里面有一个demo接口,返回简单字符串

@RestController
public class DemoController {

    @GetMapping("/demo")
    public String demo() {
        return"demo";
    }
}

4)设置启动类SecurityLesson01Application,并启动项目
5)访问:http://127.0.0.1:8080/demo 你会得到以下的界面
在这里插入图片描述
是的,你被强制需要登录才能访问demo接口

6)这时候输入用户名:user,密码(在控制台有一串自动生成的密码,如下图)。之后你就能访问demo页面

在这里插入图片描述

3 默认配置的原理

我们看到只需要引入一个spring-boot-starter-security的依赖,整个项目就会被Spring Security给管理起来。那么spring-boot-starter-security到底做了什么。
熟悉spring-boot的朋友都知道,约定大于配置。在spring-boot中就是默认对Spring Security进行了配置。
1)我们可以从spring-boot的autoconfigure中找到SecurityAutoConfiguration的自动化配置
在这里插入图片描述
2)进入SecurityAutoConfiguration这个配置类,该类没什么内容,但是注解中有2个类SpringBootWebSecurityConfiguration.class和SecurityProperties.class比较重要
在这里插入图片描述

3)SpringBootWebSecurityConfiguration类中的defaultSecurityFilterChain就是默认配置。这里简单说明一下每一步骤的作用。我们先知道Spring Security是基于一个Filter过滤器的链路方式进行一系列操作,后续会着重讲解其不同过滤器的作用。
在这里插入图片描述

这里说明一下,Spring Security的认证方式有多种,里面包括formLogin方式、HTTP Basic等,甚至可以自定义。如果你不使用默认HTTP认证,而是配置HTTP Basic。那么认证就不是通过一个登录页面,而是会弹出一个HTTP Basic对话框(如下图),有兴趣的朋友可以去了解一下HTTP Basic authentication(就是在Postman工具有一项Authorization配置),这里就不细说。
在这里插入图片描述

4)SecurityProperties类就是定义登录的用户名和密码,里面头部的properties注解是读取yaml配置,如果没有yaml配置,则给出一个默认用户user以及随机生成一串密码。这就解释了为什么我们什么都没配置,Spring Security就开始工作,还给我们生成了用户名密码

在这里插入图片描述

4 通过yaml配置自定义账号密码

我们可以通过配置application.yml文件,为Spring Security配置默认用户名和密码,这样就不会使用默认user和随机密码

server:
  port: 8080
spring:
  application:
    name: lesson01
  security:
    user:
      name: moo
      password: 1234

结语:问题来了,即使通过这种配置自定义账号密码,在业务上面来说,也不会有人将用户名和密码放到配置文件中,一般都会放到数据库中。那么Spring Security也考虑到这种情况,下一章我们来讲解:Spring Security基于数据库的用户认证和认证原理

;