Spring Security基础:快速搭建安全认证系统
上周五晚上十点半,我还在公司改一个“临时加的”登录页。产品经理说:“就加个用户名密码登录,很简单吧?” 我差点一口老血喷在键盘上——这需求简单?那得看用什么框架了。
作为一个在外包公司混了四年的“码农民工”,我已经见过太多次那种“明天上线、今晚给方案”的骚操作。这次项目是个内部管理系统,后端是 Java + Spring Boot,前端 Vue。客户死活不让用 OAuth2,非得自己搞一套账号密码认证。行吧,反正也不是第一次从零搭权限体系了。
为什么又选了 Spring Security?
其实我内心是抗拒的。毕竟我最近沉迷 Rust,连写点小工具都开始用 axum + jsonwebtoken 自己拼 auth middleware 了。但现实很骨感:客户要求 Java 技术栈,团队里没人会 Go,运维也只认 Tomcat。
说到 Go,我其实偷偷试过用 Gin 写了个 demo,配合 gorm + JWT,代码清爽到飞起。但一想到要给整个团队培训、文档重写、CI/CD 流程改造……算了,还是别给自己找麻烦。毕竟咱是外包,不是创业公司,稳定交付比技术洁癖重要多了。
至于 JavaScript?前端用 JS 没问题,但后端用 Node.js 做企业级权限控制?我上次见这么干的项目,三个月后就因为 token 刷新逻辑崩了被甲方骂到重构。所以,还是老老实实用 Spring Security 吧——虽然配置像迷宫,但胜在成熟、文档多、出了问题 Stack Overflow 上八成有人踩过。
吐槽一句:Spring Security 的学习曲线,简直像爬珠穆朗玛峰——你刚以为登顶了,发现前面还有个北坳。
快速搭个能跑的认证系统
废话不多说,直接上干货。目标很明确:用户输入用户名密码 → 后端校验 → 返回 JWT → 后续请求带 token 访问受保护接口。
1. 依赖安排
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
别忘了加数据库(比如 MySQL)和 MyBatis-Plus,用户信息总得存吧。
2. 用户实体设计
public class User {
private Long id;
private String username; // 唯一
private String password; // BCrypt加密
private String role; // ROLE_ADMIN / ROLE_USER
}
重点:密码一定要用 BCryptPasswordEncoder 加密!别学某些同事直接存明文,去年双11期间有个项目就这么被审计揪出来,差点背锅。
3. 核心配置:SecurityConfig
这才是重头戏。我踩过最大的坑就是忽略 http.cors().and().csrf().disable(),结果前端 Axios 死活跨域失败。
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(
AuthenticationConfiguration config) throws Exception {
return config.getAuthenticationManager();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.cors().and()
.csrf().disable() // 前后端分离,关掉CSRF
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeHttpRequests(authz -> authz
.requestMatchers("/auth/login").permitAll()
.anyRequest().authenticated()
)
.addFilterBefore(jwtFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public JwtAuthenticationFilter jwtFilter() {
return new JwtAuthenticationFilter();
}
}
4. 登录接口 & JWT 生成
@RestController
@RequestMapping("/auth")
public class AuthController {
@Autowired
private AuthenticationManager authManager;
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest req) {
try {
Authentication auth = authManager.authenticate(
new UsernamePasswordAuthenticationToken(req.getUsername(), req.getPassword())
);
// 生成JWT
String token = JwtUtil.generateToken(auth.getName());
return ResponseEntity.ok(new JwtResponse(token));
} catch (BadCredentialsException e) {
return ResponseEntity.status(401).body("用户名或密码错误");
}
}
}
这里的关键是 AuthenticationManager 会自动调用我们实现的 UserDetailsService 去查库。记得重写 loadUserByUsername 方法!
5. JWT 拦截器
自定义一个 OncePerRequestFilter,从 Header 里拿 Authorization: Bearer xxx,解析后塞进 SecurityContext:
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain) {
String token = extractToken(req);
if (token != null && JwtUtil.validateToken(token)) {
String username = JwtUtil.getUsernameFromToken(token);
UserDetails user = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken auth =
new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(auth);
}
chain.doFilter(req, res);
}
}
性能与架构考虑
虽然这是个“快速搭建”的方案,但在生产环境还是得注意几点:
- JWT 不要存敏感信息:payload 是 base64 可解的!
- 设置合理过期时间:我们一般设 2 小时,配合前端做静默刷新。
- 黑名单机制:登出时把 token 加入 Redis 黑名单(虽然 JWT 本意是无状态,但业务需要就得妥协)。
- 密码爆破防护:加个 Redis 计数器,5 次失败锁 10 分钟——这招是从我刷 LeetCode “LRU Cache” 题目得到的灵感,算法不只是面试用啊兄弟们!
说到算法,其实权限系统底层就是图论:用户-角色-权限,典型的多对多关系。如果你要搞 RBAC + 数据权限,那复杂度直线上升。不过外包项目一般到 ROLE 级别就停了,再往下?产品经理自己都搞不清需求。
对比一下其他语言方案
为了满足文章关键词要求,顺便列个表对比下不同技术栈搞认证的体验:
| 技术栈 | 开发速度 | 学习成本 | 生产稳定性 | 适合场景 |
|---|---|---|---|---|
| Spring Security | 中 | 高 | ⭐⭐⭐⭐⭐ | 企业级 Java 项目 |
| Go + Gin | 快 | 低 | ⭐⭐⭐⭐ | 微服务、高并发 API |
| Node.js + Passport | 快 | 中 | ⭐⭐ | 小型项目、快速原型 |
| Rust + Axum | 慢 | 极高 | ⭐⭐⭐ | 性能敏感、安全关键系统 |
我个人觉得:如果你团队 Java 技术栈成熟,Spring Security 虽然啰嗦但最省心。Go 更优雅,但国内企业 Java 生态根深蒂固,想推新技术?除非你是架构师,否则别碰。
最后一点感悟
写这篇文章的时候,我其实正在刷 LeetCode 准备跳槽。每天下班回家两小时,Rust 和算法轮着来。但白天上班,还是得面对这些“老旧但稳定”的 Java 项目。外包四年,我学会了:技术理想主义要藏好,交付才是王道。
Spring Security 虽然配置反人类,但它扛得住甲方爸爸的审计,经得起运维大叔的拷打。有时候,不是技术不够新,而是业务场景不允许你秀肌肉。
对了,上周那个登录页,周一顺利上线了。测试妹子说“这次没出现无限重定向”,我回她:“那是因为我把 successHandler 和 failureHandler 都删了,直接返回 JSON。” —— 看,有时候解决问题的方法,就是别按官方文档来 😏
共勉。

评论 0