Spring Security 基础:快速搭建安全认证系统(零基础入门教程)
作者简介:我是某大厂后端开发工程师,有3年 Java 后端经验,业余时间在 B 站做技术 UP 主。今天这篇教程,是我结合自己当初学 Spring Security 的踩坑经历,以及带实习生、面试应届生时看到的常见问题,专门写给完全零基础的新手朋友的一份入门指南。
为什么你要学 Spring Security?它和你的简历、面试有什么关系?
我当初学的时候,以为“用户登录”就是写个 if (password == "123456") 就完事了。结果第一次实习就被导师狠狠教育了一顿:“你这代码连盐都没加,密码明文存数据库,黑客一拖库你就完了!”
后来我才明白:安全不是功能,而是底线。
Spring Security 是 Java 生态中最主流的安全框架,几乎所有中大型 Java 后端项目都会用到它。无论你是想进大厂、还是想做企业级开发,掌握它几乎是必选项。
- 📌 简历上写一句“熟悉 Spring Security”,比写“会用 HashMap”更能打动面试官。
- 💬 面试题高频考点:比如“Spring Security 的认证流程?”、“如何自定义登录逻辑?”、“JWT 和 Session 的区别?”——这些我都将在本文中用最直白的方式讲清楚。
- 🧠 思维升级:你会从“能跑就行”转向“安全可靠”,这是从学生思维到工程师思维的关键一步。
📝 小提醒:虽然本文讲的是 Java 的 Spring Security,但如果你主攻 Python,也别划走!安全的核心思想是相通的。Django 有
django.contrib.auth,Flask 有Flask-Login,理解原理后迁移非常快。而且很多公司后端技术栈是混合的,懂一点 Java 安全体系对你的职业发展很有帮助。
第一步:环境准备(手把手教你搭起来)
我们用最简单的组合:Spring Boot + Spring Security + Maven + JDK 17
所需工具清单
| 工具 | 版本建议 | 说明 |
|---|---|---|
| JDK | 17 或 21 | 推荐使用 LTS 版本 |
| IDE | IntelliJ IDEA | 免费社区版即可 |
| 构建工具 | Maven | 本文使用 Maven,Gradle 类似 |
| 浏览器 | Chrome / Edge | 用于测试登录页面 |
创建项目(30秒搞定)
- 打开 https://start.spring.io
- 填写以下信息:
- Project: Maven
- Language: Java
- Spring Boot: 3.2.x(最新稳定版)
- Group:
com.example - Artifact:
security-demo
- 在 Dependencies 搜索框中添加:
Spring WebSpring Security
- 点击 Generate 下载 ZIP 包,解压后用 IDEA 打开。
✅ 项目结构应该是这样的:
security-demo/
├── src/
│ └── main/
│ ├── java/com/example/securitydemo/
│ │ └── SecurityDemoApplication.java
│ └── resources/
└── pom.xml
验证环境是否成功
启动 SecurityDemo Application,访问 http://localhost:8080。
你会发现:自动跳转到了一个登录页面!
这就是 Spring Security 的“默认保护”机制——哪怕你一行代码没写,它也会保护所有接口!
默认用户名是 user,密码在控制台日志里,类似:
Using generated security password: a1b2c3d4-e5f6-7890-g1h2-i3j4k5l6m7n8
⚠️ 注意:这个密码每次启动都会变!仅用于开发测试。
第二步:核心概念扫盲(用大白话解释专业词)
很多新手被“认证”、“授权”、“过滤器链”这些词吓退。其实它们很简单:
1. 认证(Authentication) vs 授权(Authorization)
- 认证 = 你是谁?
比如:输入用户名密码 → 系统确认你是张三。 - 授权 = 你能干什么?
比如:张三只能看自己的订单,不能删别人的。
✨ 一句话记住:先认证,再授权。
2. Spring Security 的工作原理(简化版)
想象你进公司大楼:
- 门口保安(Security Filter Chain)拦住你
- 问你要工牌(UsernamePasswordAuthenticationToken)
- 他去 HR 系统查你是不是员工(UserDetailsService)
- 如果是,给你发临时门禁卡(SecurityContext)
- 之后你去任何部门(访问 API),门禁系统自动识别你权限
整个过程对你是透明的——你只关心“能不能进”,不关心保安怎么查你。
3. 关键组件速览
| 组件 | 作用 | 类比 |
|---|---|---|
UserDetails |
存储用户信息(用户名、密码、角色等) | 你的电子工牌 |
UserDetailsService |
根据用户名查用户数据 | HR 数据库查询接口 |
PasswordEncoder |
密码加密/校验 | 密码保险柜 |
SecurityFilterChain |
定义哪些路径需要保护 | 大楼安防策略 |
第三步:实战!5分钟搭建自己的登录系统
现在我们来替换掉 Spring Security 的默认行为,实现自己的用户名密码登录。
步骤 1:创建用户实体类
// src/main/java/com/example/securitydemo/model/User.java
package com.example.securitydemo.model;
public class User {
private String username;
private String password; // 注意:这里存的是加密后的密码!
private String role; // 角色,如 "USER", "ADMIN"
// 构造函数、getter/setter 省略(IDEA 可自动生成)
public User(String username, String password, String role) {
this.username = username;
this.password = password;
this.role = role;
}
// getter...
}
步骤 2:实现 UserDetailsService(查用户)
// src/main/java/com/example/securitydemo/service/CustomUserDetailsService.java
package com.example.securitydemo.service;
import com.example.securitydemo.model.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
@Service
public class CustomUserDownLatchService implements UserDetailsService {
// 模拟数据库(实际项目用 MyBatis/JPA 查询数据库)
private static final Map<String, User> USER_DB = new HashMap<>();
static {
// 存两个用户,密码用 {noop} 表示不加密(仅演示!)
USER_DB.put("alice", new User("alice", "{noop}123456", "USER"));
USER_DB.put("admin", new User("admin", "{noop}admin123", "ADMIN"));
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = USER_DB.get(username);
if (user == null) {
throw new UsernameNotFoundException("用户不存在: " + username);
}
// 返回 Spring Security 要求的 UserDetails 对象
return org.springframework.security.core.userdetails.User
.withUsername(user.getUsername())
.password(user.getPassword())
.roles(user.getRole())
.build();
}
}
🔒 重要安全提示:
{noop}表示“不加密”,绝对不能用于生产环境!后面我们会改成 BCrypt 加密。
步骤 3:配置 SecurityFilterChain(定义规则)
// src/main/java/com/example/securitydemo/config/SecurityConfig.java
package com.example.securitydemo.config;
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.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/public/**").permitAll() // 公共接口放行
.anyRequest().authenticated() // 其他所有请求都要登录
)
.formLogin(form -> form
.loginPage("/login") // 自定义登录页(可选)
.permitAll() // 登录页本身不需要认证
)
.logout(logout -> logout
.permitAll()
);
return http.build();
}
// 仅用于演示!生产环境必须用 BCrypt
@Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
}
步骤 4:创建测试接口
// src/main/java/com/example/securitydemo/controller/TestController.java
@RestController
public class TestController {
@GetMapping("/hello")
public String hello() {
return "Hello, authenticated user!";
}
@GetMapping("/admin")
public String admin() {
return "Welcome, admin!";
}
@GetMapping("/public/info")
public String publicInfo() {
return "This is public info.";
}
}
测试一下!
- 启动应用
- 访问
http://localhost:8080/public/info→ 直接显示(因为/public/**放行了) - 访问
http://localhost:8080/hello→ 跳转到登录页- 用户名:
alice,密码:123456→ 登录成功,显示 "Hello..."
- 用户名:
- 用
alice访问/admin→ 403 Forbidden(因为 alice 不是 ADMIN)
✅ 恭喜!你已经实现了基础的认证 + 授权!
第四步:安全加固(从“能跑”到“靠谱”)
前面为了简化用了 {noop} 明文密码,现在我们把它改成 BCrypt 加密——这也是面试常问的点!
修改密码存储方式
- 删除
@Bean PasswordEncoder中的NoOpPasswordEncoder - 替换为 BCrypt:
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
- 修改模拟数据库中的密码(用工具生成加密串):
// 在 main 方法里临时跑一次,生成加密密码
public static void main(String[] args) {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
System.out.println(encoder.encode("123456")); // 输出类似 $2a$10$...
System.out.println(encoder.encode("admin123"));
}
- 把输出的加密串填回
USER_DB:
static {
USER_DB.put("alice", new User("alice", "$2a$10$...", "USER"));
USER_DB.put("admin", new User("admin", "$2a$10$...", "ADMIN"));
}
💡 为什么用 BCrypt?
- 它是慢哈希,防止暴力破解
- 每次加密结果不同(带 salt),防彩虹表
- Spring Security 原生支持,一行代码搞定
新手常见问题 & 避坑指南
❓ 问题1:为什么登录后访问接口还是跳登录页?
原因:前端没带 Cookie!Spring Security 默认用 Session 认证,浏览器会自动带 JSESSIONID Cookie,但 Postman 或 Axios 需要手动处理。
✅ 解决方案:
- 浏览器直接访问没问题
- 用 Postman:先调登录接口,勾选 “Automatically persist cookies”
- 前端用 Axios:设置
withCredentials: true
❓ 问题2:如何返回 JSON 而不是跳转页面?
默认是表单登录(跳 HTML 页面)。如果你做前后端分离,需要改成 JSON 响应:
.formLogin(form -> form
.successHandler((request, response, authentication) -> {
response.setContentType("application/json");
response.getWriter().write("{\"code\":200, \"msg\":\"登录成功\"}");
})
.failureHandler((request, response, exception) -> {
response.setContentType("application/json");
response.getWriter().write("{\"code\":401, \"msg\":\"用户名或密码错误\"}");
})
)
❓ 问题3:和 JWT 有什么区别?要不要学?
- Session:服务端存状态(内存/Redis),适合传统 Web 应用
- JWT:无状态,Token 自包含信息,适合 App、微服务
📌 建议:先掌握 Session 模式(本文内容),再学 JWT。面试官更看重你是否理解“状态管理”的本质,而不是只会用 JWT。
下一步学习建议(写进你的学习计划)
- 深入源码:读
UsernamePasswordAuthenticationFilter源码,理解认证全流程 - 集成数据库:用 Spring Data JPA 连接 MySQL,替换
HashMap模拟数据 - 权限粒度细化:学习
@PreAuthorize("hasRole('ADMIN')")注解 - CSRF 防护:了解跨站请求伪造攻击及防御(Spring Security 默认开启)
- OAuth2.0:学微信/Google 第三方登录(大厂高频需求)
🎯 简历加分项:在 GitHub 上开源一个“基于 Spring Security 的 RBAC 权限管理系统”,哪怕只有基础功能,也能让面试官眼前一亮。
最后说两句
我当初学 Spring Security 时,也被那些抽象类、接口、过滤器搞晕过。但只要你记住:它只是帮你自动做了“验证身份 + 检查权限”这两件事,一切就清晰了。
安全没有银弹,但有最佳实践。希望这篇教程能成为你后端安全之路的第一块基石。
互动时间:如果你觉得有帮助,欢迎关注我的 B 站账号【你的B站ID】,我会持续更新《从零搭建企业级后端系统》系列视频。下期预告:《Spring Security + JWT 实现无状态认证》!
祝你编码无 bug,面试全通过! 🚀

评论 0