零基础搞定Spring Security安全认证系统
大家好,我是你们的老朋友,一个985毕业、常年在掘金写入门教程的全栈工程师,同时也是一名后端讲师。在我的教学过程中,发现很多初学者对“后端安全”这个概念既敬畏又迷茫。我当初学的时候也是一头雾水,看着满屏的过滤器和拦截器,根本不知道从何下手。
今天写这篇教程,就是为了帮大家打破这种恐惧。我们将用最简单的语言、最直接的代码,手把手带你快速搭建一个安全认证系统。无论你是刚接触Java的新手,还是想系统复习安全框架的开发者,这篇文章都能让你有所收获。
环境准备
在开始写代码之前,我们需要准备好开发环境。工欲善其事,必先利其器。
- JDK版本:推荐使用 JDK 17 或更高版本。Spring Boot 3.x 已经全面拥抱 JDK 17,这也是目前的行业主流。
- 构建工具:Maven 3.6+。
- 开发工具:你可以使用传统的 IntelliJ IDEA。不过,作为一名喜欢尝鲜的全栈工程师,我强烈推荐你试试最近很火的 AI 代码编辑器 Windsurf。它内置了非常强大的 AI 辅助编程能力,在你写 Spring Security 这种配置繁琐的框架时,AI 能帮你自动补全很多样板代码,极大提升效率。
环境准备好后,我们去 Spring Initializr 初始化一个项目。勾选以下依赖:
- Spring Web
- Spring Security
- Lombok(用来简化实体类代码)
项目结构搭建好后,我们就正式进入核心概念的学习。
核心概念解析
在动手之前,我们必须搞懂几个核心概念。遇到不懂的技术名词,我建议大家多利用 AI 工具,比如用 文心一言 或者 豆包 来帮你生成通俗的解释,但一定要结合代码去验证,不能只停留在理论层面。
认证与授权的区别
这是新手最容易混淆的两个概念。我们用去高档小区拜访朋友来打个比方:
| 概念 | 英文 | 通俗解释 | 小区比喻 |
|---|---|---|---|
| 认证 | Authentication | 验证“你是谁”。证明你的身份是合法的。 | 保安查你的身份证,确认你是张三。 |
| 授权 | Authorization | 验证“你能干嘛”。确认你是否有权限做某事。 | 确认你是张三后,保安看你是否有进入某栋楼的门禁卡。 |
在 Spring Security 中,认证和授权是紧密相连的。只有先通过了认证(证明你是谁),系统才能给你分配相应的角色,进而进行授权(决定你能访问哪些接口)。
SecurityContext 与 Authentication
SecurityContext 是 Spring Security 的核心上下文,它就像一个保险箱,里面装着当前登录用户的所有安全信息。而 Authentication 就是保险箱里的核心凭证,它包含了用户的用户名、密码、以及拥有的权限列表。
当用户登录成功后,Spring Security 会把 Authentication 对象存入 SecurityContext 中。后续请求到来时,框架会直接从上下文里取出这个凭证进行校验。
FilterChain(过滤器链)
这是 Spring Security 的灵魂。你可以把它想象成机场的安检通道。
[客户端请求]
↓
[UsernamePasswordAuthenticationFilter] (处理表单登录)
↓
[BasicAuthenticationFilter] (处理HTTP Basic认证)
↓
[ExceptionTranslationFilter] (处理认证/授权异常)
↓
[FilterSecurityInterceptor] (核心授权拦截器,检查是否有权限)
↓
[目标 Controller]
每一个 Filter 负责处理一种特定的安全逻辑。请求必须像过安检一样,一层一层通过这些过滤器,最终才能到达我们的业务代码。
实战项目:搭建安全认证系统
理论讲完了,我们直接上代码。这里顺便提一句,虽然咱们今天学的是 Java 的 Spring Security,但如果你以后想学 Go 语言的 Gin 框架,里面的中间件认证思想也是完全互通的,底层逻辑都是拦截和校验,学会了 Spring Security 的精髓,转其他语言也会非常快。
第一步:引入依赖与基础配置
在 pom.xml 中确保已经引入了 Spring Security 依赖。然后我们创建一个配置类,这是 Spring Security 6.x 版本的推荐写法。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// 禁用 CSRF,为了方便我们使用 Postman 测试,实际生产环境建议开启
.csrf(csrf -> csrf.disable())
// 配置请求授权规则
.authorizeHttpRequests(auth -> auth
// 放行登录接口和公共接口
.requestMatchers("/api/login", "/api/public/**").permitAll()
// 其他所有请求都需要认证
.anyRequest().authenticated()
)
// 配置表单登录(Spring Security 会自带一个默认的登录页面)
.formLogin(form -> form
.loginProcessingUrl("/api/login")
.permitAll()
);
return http.build();
}
}
第二步:自定义用户信息服务
在实际项目中,用户信息通常存在数据库里。为了快速演示,我们先使用内存模式。我们需要实现 UserDetailsService 接口。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
public class UserDetailsConfig {
// 密码编码器,Spring Security 强制要求密码必须加密存储
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public UserDetailsService userDetailsService() {
// 创建一个内存中的用户
UserDetails user = User.builder()
.username("admin")
// 注意:这里必须使用 PasswordEncoder 加密后的密码
.password(passwordEncoder().encode("123456"))
.roles("ADMIN")
.build();
UserDetails normalUser = User.builder()
.username("user")
.password(passwordEncoder().encode("123456"))
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user, normalUser);
}
}
第三步:编写业务接口
我们写几个接口来测试我们的安全系统是否生效。
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api")
public class TestController {
// 公共接口,不需要登录即可访问
@GetMapping("/public/hello")
public String publicHello() {
return "Hello, 这是一个公共接口!";
}
// 需要认证的接口
@GetMapping("/private/hello")
public String privateHello() {
return "Hello, 你已经成功登录,这是私有接口!";
}
// 需要 ADMIN 角色才能访问的接口
@GetMapping("/admin/dashboard")
public String adminDashboard() {
return "Hello Admin, 欢迎来到管理后台!";
}
}
第四步:测试与验证
启动项目。当你直接访问 http://localhost:8080/api/private/hello 时,会被重定向到 Spring Security 默认的登录页面。
使用我们配置的 admin / 123456 登录后,再次访问该接口,就能成功看到返回的数据了。如果你尝试用 user 账号访问 /api/admin/dashboard,则会收到 403 Forbidden 错误,这就说明我们的授权逻辑生效了。
常见问题解答
我当初学的时候,在这里踩了无数的坑。为了让大家少走弯路,我总结了几个新手最常遇到的问题。
1. 为什么启动后控制台没有打印默认密码了?
在早期的 Spring Security 版本中,启动时控制台会打印一个默认的 user 密码。但在 Spring Security 5.7 之后,基于表单的默认安全配置被废弃了。如果你没有自定义 SecurityFilterChain,它依然会生成默认用户,但如果你像我们教程里一样自定义了配置,默认用户就不会生成了。所以,一定要自己配置 UserDetailsService。
2. 访问接口一直报 403 Forbidden 怎么办?
遇到 403,不要慌,按以下三步排查:
- 检查 CSRF:如果你是用 Postman 或 Ajax 发送 POST 请求,且没有携带 CSRF Token,就会被拦截。在测试阶段,可以在配置中加上
.csrf(csrf -> csrf.disable())。 - 检查权限配置:确认你的
authorizeHttpRequests规则是否写错了,比如把需要放行的路径写成了需要认证。 - 检查角色前缀:在代码中配置角色时写的是
roles("ADMIN"),Spring Security 底层会自动加上ROLE_前缀。如果你在方法上使用@PreAuthorize("hasRole('ADMIN')")是没问题的,但如果用hasAuthority('ADMIN')就会报错,因为底层实际上是ROLE_ADMIN。
3. 密码必须加密吗?明文行不行?
绝对不行。Spring Security 6.x 强制要求使用 PasswordEncoder。如果你直接传明文密码,启动时或者登录时会直接抛出异常。必须使用 BCryptPasswordEncoder 等编码器对密码进行哈希处理。
学习建议与避坑指南
恭喜你,到这里你已经成功搭建了一个基础的 Spring Security 认证系统。但安全领域博大精深,这仅仅是个开始。作为你们的讲师,我给大家几点后续学习的建议:
- 不要死记硬背配置类:Spring Security 的配置类方法很多,不要试图背下来。理解
HttpSecurity的链式调用本质,知道每个 Filter 的作用,用的时候查文档即可。 - 理解底层执行流程:遇到 Bug 时,学会打断点。把断点打在
FilterSecurityInterceptor或者ExceptionTranslationFilter里,看看请求是怎么流转的,这比你看十遍博客都管用。 - 下一步学习路线:前后端分离是现在的绝对主流。接下来,你需要学习如何将 Spring Security 与 JWT (JSON Web Token) 结合,实现无状态的 Token 认证。再往后,可以挑战一下 OAuth2.0 协议,实现第三方登录(如微信、GitHub 登录)。
安全无小事,认证授权是后端开发的第一道大门。希望这篇教程能帮你推开这扇门。如果文章对你有帮助,欢迎在掘金给我点个赞,有任何问题,我们评论区见!

评论 0