之所以想写这一系列,是因为之前工作过程中使用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基于数据库的用户认证和认证原理