大家好,我是你们的老朋友。
之前陆续写过不少框架整合、微服务、数据库相关的实战文章,经常有读者问:能不能出一套完整的安全认证授权教程?从最简单的入门 Demo 开始,一直到分布式环境下怎么做登录、怎么做鉴权、怎么防串号,一条线讲清楚。
说实话,这个主题一直想写,但迟迟没动笔。不是因为没东西可写,恰恰相反,Spring Security 本身涉及的概念太多:过滤器链、认证管理器、授权语义、会话并发、密码加密、CSRF、跨域、OAuth2、JWT……如果按官方文档那种严谨但枯燥的写法来,新手大概率看完第三页就劝退了。
所以这系列文章,我会换一种讲法:每集围绕一个真实场景展开,先说明场景,再给完整可运行的代码,然后逐步分析配置背后的原理,最后补充常见报错和线上避坑思路。
这一篇是系列的第 0 集,也就是前言和技术地图。我会先交代清楚这个系列到底讲什么、适合谁、需要准备什么环境,以及整个系列的核心概念先做一个扫盲。同时把 Spring Security 6.x 时代的一些重要变化提前讲清楚,避免后面代码示例中你突然看到一个不认识的写法。
如果你正准备用 Spring Security 给自己的项目加上登录和权限控制,或者面试前想在短时间内把 Spring Security 这块系统地过一遍,那这篇文章可以作为你整个学习方向的第一站。
1. 为什么要写这个系列:Spring Security 学习痛点分析
先聊几句题外话。
Spring 家族里的框架很多,Spring MVC、Spring Boot、Spring Cloud 都算容易上手,照着文档复制粘贴就能跑通。但 Spring Security 是一个异类——官方文档写得及其抽象,过滤器链的调用过程对于没接触过 Servlet 过滤器的同学来说就如同天书。
我在网上看过不少关于 Spring Security 的学习资料,质量参差不齐。有的教程还在用已经废弃的WebSecurityConfigurerAdapter,有的教程把安全配置全部丢到一个类里然后就告诉你“会了”,还有的教程上下文之间互相矛盾。当读者照着敲完代码,发现自己的项目并没有出现想象中的登录页,就会非常挫败。
其实 Spring Security 本身的设计思路并不复杂,它的核心可以概括为三句话:
- 所有请求在进入 Controller 之前,必须经过一条过滤器链。
- 过滤器链中的每个过滤器负责一件事:认证、授权、CSRF 防护、跨域处理等。
- 你想定制安全逻辑,就是往这条链子上加组件或修改默认组件。
真正让我们觉得难的原因,不是概念复杂,而是官方文档把这些概念拆得太散了。作为初学者,你很难形成一张完整的图。
所以这个系列,我给自己定了一个目标:不复制文档,不逐行翻译源码,而是按项目开发的真实路径,把 Spring Security 拆成一集一集的实战课。
每一集都从一个实际问题出发,比如:
- 为什么我加了 security 依赖,项目启动后访问接口就变成 401 了?
- 如何把我的用户表接入 Spring Security,而不是用内存用户?
- 如何实现“用户登录后只能访问自己的数据”?
- 如何在前后端分离的项目中返回 JSON 而不是跳转登录页?
- 如何给接口动态配置权限,比如通过数据库配置角色能访问的接口?
这些问题一个一个解决,Spring Security 的基本全貌也就自然建立起来了。
2. 本系列适合哪些读者
我没有把读者限定为“零基础”,因为零基础也分很多种。
如果你完全没接触过 Java Web,连 Servlet、Filter、DispatcherServlet 都没听说过,那我不建议你直接从这个系列开始。建议先去把 Spring Boot 的基本使用过一遍,能写一个简单的 REST 接口,再回头看系列文章。
如果你已经能独立写一些 Spring Boot 接口,但从来没给项目加过登录认证逻辑,或者说你之前一直用拦截器、Filter 手写 Token 校验,现在想体系化地学习 Spring Security,那这个系列真的非常合适。
还有一类读者,就是准备跳槽面试、需要梳理 Spring Security 源码和设计思想的技术候选人。系列文章中不只有代码,也会有核心概念图、源码调用链分析和源码级排错思路,这部分内容能够帮助你在面试中把“会用”升级为“理解”。
总结一下,适合阅读的人群:
- 会用 Spring Boot 写接口,但没做过认证授权的开发者。
- 手写过 Filter、拦截器做登录校验,想对比学习 Spring Security 的开发者。
- 项目中已经用到 Spring Security,但业务一复杂就不知道怎么配置的开发者。
- 面试前需要系统复习认证授权核心知识的求职者。
如果你属于以上几类,那这个系列应该能陪你走完从入门到项目中能独立落地的全过程。
3. 环境准备与版本说明
版本是一个很重要的问题。
Spring Security 目前的主流版本是 5.7.x 和 6.x。其中 Spring Security 5.7 是旧系列的最后一个长期维护版本,而 Spring Security 6.0 在 2022 年 11 月随 Spring Boot 3.0 一起发布,带来了很多破坏性变更。
在这个系列中,我会以 Spring Security 6.x 作为主版本进行讲解,原因很明确:Spring Boot 3.x 已经发布多年,新项目基本都用 3.x,如果你还在用 Spring Boot 2.x,也可以阅读系列中的大部分内容,代码中需要留意lambda DSL和无WebSecurityConfigurerAdapter这两点新写法。
具体环境信息列举如下:
- JDK:建议使用 JDK 17 或更高版本(Spring Boot 3.x 强制要求 JDK 17)。
- 框架:Spring Boot 3.x,文中示例以 3.2.x 左右版本为例。
- 构建工具:Maven 3.8+ 或 Gradle 7.x 均可,示例中统一使用 Maven。
- IDE:IntelliJ IDEA 或 Eclipse,推荐 IDEA,演示效果更直观。
- 数据库:部分示例使用 MySQL 8.x,并配合 MyBatis-Plus 或 Spring Data JPA 演示动态用户与动态权限。
- 其他组件:Redis(可选,用于分布式会话)、Nacos(可选,用于注册中心与配置中心)、JWT(用于无状态认证)。
需要说明的是,由于不同时间段各依赖版本会不断升级,如果你在配置依赖时发现我的版本号和你的不一致,不要慌,优先参考你本地 Spring Boot Parent 管理的版本即可。多数情况下,Spring Boot 的依赖管理已经帮你锁定了兼容的 Security 版本。
示例项目结构我会采用多模块方式,核心模块如下:
spring-security-tutorial/ ├── pom.xml ├── security-core/ # 核心认证授权封装 ├── security-01-basic/ # 第1集:最简登录认证 ├── security-02-db/ # 第2集:数据库用户认证 ├── security-03-jwt/ # 第3集:前后端分离 + JWT └── security-04-authorize # 第4集:方法级权限与动态权限每个模块都可以独立运行,方便你单独打开阅读对应集数的代码。
4. 核心概念扫盲:认证、授权与过滤器链
正式代码开始之前,先把三个核心概念讲清楚。
4.1 认证(Authentication)
认证回答的问题只有一个:你是谁?
在 Spring Security 中,认证的产物是一个Authentication对象。当用户未认证时,Authentication对象中只有一个匿名用户标识或者为空;当用户通过用户名密码等方式登录成功后,Authentication对象中会包含用户的身份信息、凭证信息和权限列表。
很多人容易混淆认证和登录。登录只是认证方式中的一种。认证可以支持用户名密码、手机验证码、微信扫码、OAuth2 第三方登录等多种形式。Spring Security 的AuthenticationManager是整个认证流程的核心入口,它会根据请求中携带的凭证信息返回一个完整认证结果。
4.2 授权(Authorization)
授权回答的问题是:你能做什么?
认证通过后,系统需要判断当前用户有没有权限访问某个资源。Spring Security 中,权限信息通常存储在Authentication对象里的GrantedAuthority集合中,每一项就是一个权限标识。
授权的实现层次很丰富:
- 在 URL 请求入口做过滤控制,也就是基于
HttpSecurity的请求规则配置。 - 在方法调用层面做控制,通过
@PreAuthorize、@Secured等注解实现。 - 在页面元素层面做控制,通过
sec:authorize标签实现。
这个系列的重点会放在前两种,因为实际项目中大多数接口权限都可以通过requestMatchers和@PreAuthorize解决。
4.3 过滤器链(Filter Chain)
Spring Security 之所以强大,核心就在于它集成了一个完整的过滤器链。
在 Spring Boot 中,Spring Security 会自动注册一条过滤器链,所有请求都会在这个链路上经过一系列过滤器的处理。常见的过滤器包括:
UsernamePasswordAuthenticationFilter:处理表单登录请求,提取用户名密码。BasicAuthenticationFilter:处理 HTTP Basic 认证。BearerTokenAuthenticationFilter:处理携带 Token 的请求。AuthorizationFilter:在认证通过后判断是否有权限访问对应资源。CsrfFilter:处理 CSRF 防护。ExceptionTranslationFilter:捕获认证和授权异常,转换为标准响应。
你可以通过HttpSecurity来调整这条过滤器链中的组件,比如新增一个自定义过滤器、修改某个过滤器的执行位置、关闭某个默认过滤器等。
很多同学在项目里遇到“请求被拦截了”“登录后依然 401”“跨域配置不生效”等问题,本质都是对过滤器链的执行顺序不够理解。所以后面的系列中,我会专门拿出一集来详细拆解过滤器链的调用过程。
4.4 SecurityContext 与 SecurityContextHolder
这里再补充一个高频考点。
SecurityContext保存的是当前线程的认证信息。SecurityContextHolder是一个基于 ThreadLocal 的工具类,默认情况下会在当前线程中存储SecurityContext。
在普通的同步请求中,你可以通过下面的方式获取当前登录用户:
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();不过在异步模式下、WebFlux 环境中或者子线程中,SecurityContext的传递策略会发生变化,后面讲到异步认证传播时再详细展开。
5. 一个最简 Demo:抢先感受 Spring Security 的默认行为
在正式进入第 1 集之前,先看一个最简示例,帮助你建立对 Spring Security 的直观感受。
这一节只做热身,目标只有一个:看到 Spring Security 的默认登录页。
5.1 创建项目并添加依赖
先创建一个空的 Spring Boot 项目,添加两个依赖即可。
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.2.5</version> <relativePath/> </parent> <dependencies> <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> </dependencies>5.2 写一个测试接口
创建启动类和 Controller。
package com.example.demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @SpringBootApplication @RestController public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } @GetMapping("/hello") public String hello() { return "Hello Spring Security"; } }5.3 启动并访问接口
启动项目后,在浏览器访问:
http://localhost:8080/hello你会发现,你并没有直接看到接口返回的字符串,而是在浏览器内被重定向到了一个默认的登录页:
http://localhost:8080/login这就是 Spring Security 的默认行为:没有经过认证的请求,不允许直接访问受保护资源。
Spring Security 在默认情况下创建一个内存用户,用户名为user,密码是项目启动后在控制台打印出来的一段随机 UUID 字符串,类似这样:
Using generated security password: 1e4f9d5f-5f0c-4b3f-8e9e-0d2cbf84cd29你可以在登录页输入user和这段随机密码,登录成功后就会重定向回/hello接口,看到返回结果。
注意,这只是默认行为。实际项目中没有人会用随机密码登录系统。我们需要做的是修改认证方式,让 Spring Security 认识我们的用户。
正是从这个小 Demo 开始,后面的系列文章会一步一步把这个默认的登录流程改造成符合真实业务需要的认证授权流程。
6. 常见问题:见了就头疼的几个报错
Spring Security 是一个报错信息极其晦涩的框架。很多报错一眼看上去根本不知道在说什么,但其实本质原因就那么几个。
这里先提前总结几个出现频率最高的,后面的系列文章会逐个详细展开。
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
启动报错Failed to configure a DataSource | 引入了需要数据源的依赖但没配置数据库连接 | 检查是否需要数据库,不需要则排除对应自动配置 |
| 访问接口返回 401 | 未登录访问受保护资源,或认证方式不对 | 检查请求是否携带了正确凭证,或调整安全放行规则 |
| 访问接口返回 403 | 用户已认证,但权限不足 | 检查授权配置和当前用户权限列表 |
| 表单登录后一直 404 | 配置了登录认证处理器但路径设置错误 | 确认loginProcessingUrl与登录页表单的 action 一致 |
密码校验报错There is no PasswordEncoder mapped for the id "null" | 使用了{noop}之外的密码格式,或未指定加密方式 | 使用BCryptPasswordEncoder,并确保密码存储格式匹配,例如{bcrypt}前缀 |
WebSecurityConfigurerAdapter报错或已废弃 | 使用的是 Spring Security 5.7 以上或 6.x | 不要再继承该类,使用组件式SecurityFilterChainBean 配置 |
实际上,一大半问题都出现在“版本新老混合”和“过滤器链配置顺序不当”上。由于 Spring Security 6 的配置方式相比老版本已经发生了较大改变,很多网上的老教程代码已经没法直接使用。如果你使用的是 Spring Boot 3.x,建议认真学习本系列中的新式写法,不要再去看那些基于WebSecurityConfigurerAdapter的老代码。
7. 本系列规划:接下来每一集会讲什么
目前这个系列的大致规划如下,后续根据读者反馈可能会调整顺序:
第 1 集:自定义用户名密码认证。不再使用默认随机密码,把认证信息绑定到自定义用户上。
第 2 集:基于数据库实现用户认证。把用户表接入 Spring Security,讲清楚UserDetailsService和UserDetails的作用,解决“用户存在库里但无法登录”的问题。
第 3 集:密码加密与密码匹配机制。理解PasswordEncoder的设计,对比BCrypt、SCrypt、Argon2等不同加密算法,完成用户注册和密码校验。
第 4 集:请求级授权配置。深入讲解requestMatchers、permitAll、authenticated、hasRole、hasAuthority的区别与应用。
第 5 集:方法级安全。使用@PreAuthorize等注解,在 Service 层做更精细的权限控制,解决“同是登录用户,但权限不同”的问题。
第 6 集:前后端分离方案。让 Security 接口返回 JSON 而不是重定向,处理 Ajax 请求的 401 与 403 响应格式。
第 7 集:JWT 登录认证。结合 Spring Security 实现无状态 Token 登录,适合接口服务、APP 后端场景。
第 8 集:自定义过滤器。在过滤器链中插入自己的过滤器,实现验证码校验、签名验签等扩展需求。
第 9 集:会话管理与并发控制。实现“单用户最大登录数”“踢人下线”等常见需求。
第 10 集:分布式场景下的认证方案。讨论 SSO 与 Redis 会话共享的基本思路。
第 11 集:源码级别分析。回顾过滤器链调用过程,分析认证流程中各个组件的协作关系,面试前可以重点看这一集。
这个规划覆盖了从入门到进阶的完整路径。在阅读过程中,官方文档可以用来做语法查询,但这套系列可以帮你减少大量自己踩坑的时间。
8. 学习建议和避坑指南
这节想给准备跟着系列学习的朋友几条比较实在的建议。
第一,不要把 Spring Security 和 Spring Boot 割裂开。Spring Security 的很多自动化配置依赖 Spring Boot 的自动装配机制。同样一段 Security 配置,在 Spring Boot 2 和 Spring Boot 3 下表现可能不一样。你需要做的不是死记硬背配置,而是在理解自动配置的前提下去调整。
第二,不要只看不敲。Spring Security 的代码写得再多,都不如你亲自动手运行一个带数据库的登录 Demo。建议准备一个空的 Spring Boot 工程,在本地 MySQL 建一张sys_user表,跟着系列文章把代码敲一遍。只有到了这一步,才会真正理解UserDetailsService在干什么。
第三,遇到 401、403 报错不要慌。把当前请求的 URL、请求方式、是否携带 Token、后台日志里的异常类型四样信息列出来,基本就能解决八成的问题。后续我会在每一集里附上常见报错对照表,你可以直接按表排查。
第四,官方文档和源码是这个系列之外最有价值的参考。本系列文章用到的主要类,建议你都在 IDE 里点进去看一眼源码注释,比如SecurityFilterChain、UsernamePasswordAuthenticationFilter、UserDetailsService这几个核心类型。源码本身比任何二手资料都准确。
9. 总结与下集预告
在接下来的系列开篇中,我们会动手完成第一件大事:把 Spring Security 默认的随机用户改成我们自己配置的用户,并讲清楚用户名密码在校验底层到底是怎么工作的。
你会接触到几个在后面反复用到的重要类:UserDetails、UserDetailsService、PasswordEncoder、AuthenticationManager等。这一集理解了这些概念后,后面数据库版用户认证、JWT 登录等进阶内容理解起来就会非常顺。
如果你对某个细节有疑问,欢迎在评论区留言,遇到频率高的问题我会在后续第 0.5 集或者其他补充内容中专门解答。
下一集,我们正式进入代码实战,不见不散。