一、Spring Security 概述
1.1 什么是 Spring Security
Spring Security 是一个基于 Spring 框架的安全框架,它为 Java 应用程序提供了全面的安全解决方案,专注于认证(Authentication)和授权(Authorization)两大核心安全领域。作为 Spring 生态系统的重要组成部分,它与 Spring 框架无缝集成,能够轻松应用于各种 Spring 应用,如 Spring Boot、Spring MVC 等。
其设计理念是通过一系列拦截器和过滤器,在应用程序的各个层级(如 Web 请求、方法调用等)实施安全控制,帮助开发者快速构建安全可靠的应用,而无需从零开始实现复杂的安全
逻辑。
1.2 核心功能
1.认证(Authentication)
- 验证用户身份的合法性,即确认 “你是谁”。
- 支持多种认证方式,包括用户名 / 密码认证、OAuth2.0、记住我功能、单点登录(SSO)等。
- 提供灵活的用户信息来源配置,可从内存、数据库、自定义服务等获取用户数据。
2.授权(Authorization)
- 确定已认证用户是否有权限执行特定操作,即判断 “你能做什么”。
- 支持基于角色的访问控制(RBAC)、基于权限的访问控制,以及细粒度的方法级、URL 级权限控制。
- 可通过配置文件、注解(如 @PreAuthorize)、编程式方式定义权限规则。
3.其他安全特性
- 防护常见攻击:内置对跨站请求伪造(CSRF)、跨站脚本攻击(XSS)、会话固定攻击、点击劫持等常见 Web 攻击的防护机制。
- 会话管理:提供会话创建、失效、并发控制等功能,可配置会话超时时间、会话 ID 生成策略等。
- 密码加密:集成多种密码加密算法(如 BCrypt、PBKDF2 等),避免明文存储密码,增强数据安全性。
- 安全事件监听:允许通过事件机制捕获认证成功 / 失败、授权拒绝等安全事件,便于日志记录和后续处理。
1.3 工作原理
SpringSecurity 的核心是过滤器链(Filter Chain),它通过一系列过滤器对请求进行拦截和处理,实现安全控制。其基本流程如下:
- 当一个请求进入应用时,会依次经过 Spring Security 过滤器链中的各个过滤器。
- 过滤器链中的关键过滤器(如 UsernamePasswordAuthenticationFilter)会处理认证相关的逻辑,例如验证用户名和密码。
- 认证成功后,会创建一个包含用户信息的 Authentication 对象,并将其存储在 SecurityContextHolder 中,以便后续的授权操作使用。
- 在请求处理过程中,授权过滤器(如 FilterSecurityInterceptor)会根据配置的权限规则,检查当前用户是否有权限访问请求的资源。如果没有权限,则会拒绝请求并返回相应的错误信息。
- 请求处理完成后,SecurityContextPersistenceFilter 会清理 SecurityContextHolder 中的信息,确保线程安全。
1.4 主要组件
- SecurityContextHolder:用于存储当前线程的安全上下文信息,其中包含了当前认证用户的 Authentication 对象。
- Authentication:表示用户的认证信息,包含用户名、密码、权限列表等内容。
- UserDetails:封装用户详细信息的接口,通常包含用户名、密码、是否启用等信息,开发者可实现该接口自定义用户信息。
- UserDetailsService:用于加载用户信息的接口,通过用户名获取 UserDetails 对象,是连接用户数据来源和认证流程的关键组件。
- AuthenticationManager:认证管理器,负责协调认证过程,它会委托给多个 AuthenticationProvider 进行认证尝试,直到其中一个成功或全部失败。
- AuthenticationProvider:具体执行认证逻辑的组件,不同的认证方式对应不同的 AuthenticationProvider(如 DaoAuthenticationProvider 用于用户名 / 密码认证)。
- AccessDecisionManager:访问决策管理器,在授权过程中根据用户的权限和请求的资源,决定是否允许访问。
1.5 应用场景
- Web 应用安全:保护 Web 应用的 URL 资源,限制未认证用户访问,控制不同角色用户的操作权限。
- REST API 安全:为 RESTful API 提供认证和授权支持,常用 OAuth2.0 等方式实现第三方应用的安全访问。
- 企业级应用:在复杂的企业环境中,与 LDAP、Active Directory 等集成,实现统一的身份认证和权限管理。
- 单点登录(SSO):通过与 Spring Security SAML、OAuth2.0 等集成,实现多个应用之间的单点登录,提升用户体验和系统安全性。
二、Spring Security基础
2.1 基本使用
创建springboot项目添加对应的springsecurity依赖:

添加热部署、Lombok、Spring Web依赖

添加springsecurity依赖

编写controller并测试

启动项目并访问

此时我们发现不能正常访问 http://localhost:8080/hello 请求了,为什么访问不了呢?
因为springsecurity框架帮我们做了一个验证,如果不是登录的请求,全部需要先登录才能继续访问,那么我们怎么知道用户名和密码呢?
不要着急,我来告诉你,来看一看我们启动项目的时候的过程,如图:

在该图上我们发现有一个密码,但是我们依然不知道用户名是啥,也不要着急,我们找找看,标注的上一个有一个类,我们去探探究竟,源码走起来。
复制该类的类名UserDetailsServiceAutoConfiguration,双击shift去查看:

点击进去看看,我们看到了这个类中有一个方法:

该方法中有一个参数User,探探究竟:

看上图,我们就知道了,哦....原来如此,原来用户名默认是user,密码是UUID随机生成的。
好,那么就把 user 和 日志中输出的随机密码 拿过去登录试试看。

这个时候成功访问到想要的结果了。出师告捷。

2.2 内存配置
但是这样也不是一个办法,用户名固定,密码随机,是不是意味着我们每次登录用户名相同,而密码都不一样呢,愁死了,怎么办?

这个注解还认识吗?说明在springboot的配置文件中有一个spring.security属性,这就好办了,走着:
在项目中创建配置application.yml配置文件:

重新启动项目

继续看源码:

是否会输出日志内容的随机密码主要看的是user.isxxx方法是否为true,点进去看看这个方法,走着:

继续进一步看看调用的这个方法

发现默认为true,继续扒拉扒拉代码发现:

这个password正好是我们yml中的password属性。
只要我们给password赋值就不会执行日志的随机密码了。
这个时候你还有一个疑问,那么下一次其它的请求或者相同的请求还需要登录吗?

重启项目试试,发送 http://localhost:8080/ 请求登录后成功显示:

继续发送/hello请求

我们发现只需要登录一次,就可以继续发送其他请求了。
这样的场景我们之前的知识点中好象都实现过呀。但是这是为啥呢,什么原理呢,不要着急,跟随我的脚步,解开神秘的面纱。
我们打开浏览器右键检查模式,找到应用那个选项


熟悉吗,似曾相识吗?这里我要提问同学,这是什么?
说明:Spring Security基于session的认证,服务器中有一个与JSESSIONID对应的session对象。
但是这个Spring Security框架是基于什么样的原理呢(怎么生成的登录页面)?
来看:入口类:FilterChainProxy
一看就是一个过滤器代理类(过滤器的知识大家还记得吧)

进来后看看doFilter方法处理过滤的业务:

进来后我们发现这里有一堆过滤器:

打个断点debug看看,有啥过滤器:


共有16个过滤器,其中标注的大家看名字大概能猜出来啥意思吧,先去login登录过滤器看看:
再底层去看看DefaultLoginPageGeneratingFilter



当登录的时候发送/login请求

原来该过滤器拦截我们的请求,框架自己帮我们生成的登录页面,同样的道理退出请求 /logout也会帮我们生成推出页面和相关的验证。
因此,本质就是原来Spring Security的本质就是各种的过滤器链帮我们完成的各种认证和权限校验。
但是这样也不行呀!这样子的话这个项目所有人的用户名和密码都成了你在配置文件中的值。也是一个固定值,所以应该怎么解决呢?
三、Spring Security登录认证
3.1 基于数据库查询登录
最终我们使用的还是需要将登录的用户名和密码存储在数据库中,然后去做登录认证处理。
但是我们应该怎么做呢?
添加mybatis和驱动,lombok相关依赖
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency><!-- Spring JDBC 支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency><!-- MyBatis Spring Boot Starter -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency><!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
</dependency>
<!-- MySQL 驱动 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
配置文件

数据表

接下来我们该生成pojo,mapper,service,controller等,
今天我们学习一个新的东西叫mybatis的逆向工程(之前代码不熟练的还是手动写代码),
快速帮我们生成pojo,mapper,*.xml
逆向工程
IDEA下载 Free MyBatis Tool 插件

找到集成到IDEA中的DB:



结果如下:

查看一下实体类pojo下的,需要修改的自己改一下。
在UserMapper上添加@Mapper注解:

自定义service

让我们的 UserService 接口继承一个UserDetailsService


让框架走我们自己定义的实现,不要走默认框架的就可以了,同时返回值中有获取用户名和密码的相关方法,这样我们就可以获取到页面上我们输入的账号和密码了,我们就可以通过这个账号和密码去数据库中查询了。
Controller

在 UserServiceImpl 实现类中打断点看效果:

开始测试:


这下就清楚了吧。这样我们就能放心的写我们自己的 UserServiceImpl 实现类了。

我们发现逆向工程中没有生成根据用户名查询用户的相关方法,因此需要我们自己定义去。
去 UserMapper 中定义个根据用户名查询用户的抽象方法,并编写相关的映射文件内容。


UserServiceImpl实现类的代码:

UserDetail 是一个接口,实现类如下图:

推荐使用User,特别注意这里的User不是我们的User(所以最好我们在创建实体类的时候不要叫User,不然不好区分) 进去查看:





UserServiceImpl

运行项目查看效果:

这个其实是没有配置 Spring Security 的加密器造成的,可以解决。
加密器配置
该加密器的配置是Spring Security的内部配置的,那么我们怎么加载呢,很简单,其实我们之前就说过,通过配置类然后利用@Bean注解就可以实现了。
添加一个config包SecrityConfig类来配置


当执行完 UserServiceImpl 后返回的 UserDetail 会交给Security,该框架会采用密码加密器来进行比较。
再一次运行查看:

此时不报错了,这个问题是我也不知道我的密码是啥(当时随机添加的密码),数据库中的加密不是BCrypt加密方式。所以就出现了这个问题。
7891

被折叠的 条评论
为什么被折叠?



