Spring Security 基础:一个普通项目的认证系统搭建实战
引言:为什么我们要用 Spring Security?

作为一名后端开发者,我参与过多个企业级 Java 项目,其中最基础、也是最容易出问题的模块之一就是——用户权限控制与认证系统。在一次重构项目中,我们团队需要为一个内部管理系统加上登录认证和 RBAC(基于角色的访问控制)功能。当时我负责选型和实现,最终决定使用 Spring Security。
刚开始接触它时,确实有点复杂,文档也偏理论化。但一旦上手,它的灵活性和安全性让我印象深刻。这篇分享就基于我那次真实的项目经验,从背景出发,到踩坑、调优、上线,带你一步步搭建起一个安全可靠的认证系统。
背景介绍:一次典型的中小企业管理平台升级

我们开发的平台是一个面向中小企业的 OA 系统,包括审批流程、员工打卡记录、任务分派等功能。随着功能越来越多,用户群体逐渐扩大,产品方提出了明确的需求:
“我们需要用户登录之后才能操作敏感数据,不同角色的用户看到的内容不一样。”
于是,我接下了这个“看似简单”的任务:给系统加上登录认证机制,并支持不同角色权限控制。
听起来不难,但实际做起来才发现:
- 如何设计用户的登录流程?
- 如何保护接口不被未授权访问?
- 用户的角色如何动态配置?
- 登录凭证如何安全存储?
- 性能会不会受影响?并发高了怎么办?
这些问题都指向了一个事实:我们需要一个成熟、稳定、可扩展的安全框架来支撑这套机制。
选择 Spring Security 的理由


我们团队之前主要用的是 Spring Boot 构建后端服务,而 Spring Security 正是与其无缝集成的标准安全框架。它提供了以下关键能力:
- 用户认证(Authentication)
- 授权(Authorization)
- CSRF 防护
- Session 管理
- OAuth2 支持
虽然它配置起来略显繁琐,但其灵活性和扩展性非常强,适合我们这种中等规模的业务系统。
技术方案设计:从零开始构建认证体系
1. 系统架构简图(简化版)
[前端] -> [API 接口] -> [Spring Boot + Spring Security]
↓
[数据库]
我们的后端服务对外提供 REST API,通过 JWT Token 实现状态无感知的登录会话管理。
2. 数据库设计
为了支持认证和权限管理,我们在数据库中增加了三个核心表:
CREATE TABLE sys_user (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) UNIQUE NOT NULL,
password VARCHAR(100) NOT NULL,
enabled BOOLEAN DEFAULT TRUE
);
CREATE TABLE sys_role (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(30) NOT NULL
);
CREATE TABLE user_role (
user_id BIGINT,
role_id BIGINT,
FOREIGN KEY (user_id) REFERENCES sys_user(id),
FOREIGN KEY (role_id) REFERENCES sys_role(id)
);
每个用户可以有多个角色,每个角色对应不同的访问权限。
3. 接口设计考虑
我们定义了几个关键接口:
/login:接受用户名密码,返回 JWT Token/users/me:当前用户信息查询接口,需要认证/api/**:受保护的 API 路径,需要根据角色限制访问
关键代码实践:Spring Security 核心配置
1. 安全配置类 SecurityConfig.java
这是 Spring Security 的核心配置类,负责定义认证逻辑和访问规则。
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterBefore(new JwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.antMatchers("/login").permitAll()
.anyRequest().authenticated();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
}
这里有几个关键点:
JwtAuthenticationFilter是我们自定义的 JWT 解析过滤器;- 使用
BCryptPasswordEncoder加密密码; - 使用无状态 session,避免服务器存储 session 数据。
2. 自定义 UserDetailsService 实现
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
SysUser user = userRepository.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("用户不存在");
}
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
for (SysRole role : user.getRoles()) {
authorities.add(new SimpleGrantedAuthority(role.getName()));
}
return new org.springframework.security.core.userdetails.User(
user.getUsername(), user.getPassword(), authorities
);
}
}
这段代码负责从数据库加载用户信息及其角色权限。
3. JWT 工具类和 Filter
关于 JWT 的生成和校验部分比较通用,这里就不贴完整代码,只说明关键思路:
- 登录成功后,生成一个带有用户名、角色、过期时间的 JWT;
- 每次请求经过
JwtAuthenticationFilter时,从中提取 token 并解析用户信息; - 将解析后的用户信息封装成
Authentication对象放入上下文。
开发过程中的典型踩坑点与解决方法
1. 认证失败总是返回 403 或 401,定位困难
一开始我们并没有设置统一的异常处理逻辑,导致某些错误(如 token 失效、签名错误)直接抛出异常,前端无法友好提示。
解决方案:
添加全局异常处理器:
@RestControllerAdvice
public class AuthExceptionHandler {
@ExceptionHandler(value = {AuthenticationException.class})
public ResponseEntity<?> handleAuthException(AuthenticationException ex) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(ex.getMessage());
}
// 其他异常处理...
}
这样前端就能拿到统一格式的错误响应。
2. 角色权限控制写错路径,导致权限失效
在配置 .antMatchers("/api/admin/**").hasRole("ADMIN") 时,忘记加 ROLE_ 前缀(Spring Security 默认给所有角色加 ROLE_ 前缀),导致权限判断失败。
建议:
尽量使用 hasAuthority() 方法替代 hasRole(),因为后者会自动加前缀,容易混淆。
上线后的性能与运维经验分享
1. JWT 过期策略优化
起初我们设置的是 7 天过期,结果发现用户频繁掉线重新登录,体验不好。后来改为 1 小时过期 + 刷新 Token 机制,用户只需每隔一段时间刷新一次,大大提升了体验。
2. Redis 缓存黑名单 Token
为了防止 Token 被盗用或强行注销某个用户登录,我们引入了 Redis 缓存 Token 黑名单机制。每次用户登出,将 Token 加入 Redis,有效期与 Token 相同,验证时拦截。
3. 监控与日志记录
我们在安全层加入了日志埋点,比如记录哪些用户访问了哪些资源,是否鉴权失败等。这不仅有助于排查问题,还能用于审计。
实施效果总结:安全性和效率的双重提升
在项目上线后,我们观察到以下几个显著变化:
- 权限访问控制更加清晰,前后端协同更方便;
- 登录认证模块稳定性高,几乎没有因安全问题引发的故障;
- 在高并发情况下,系统依然保持良好的响应速度;
- 后续接入第三方服务、OAuth2 认证也更容易扩展。
整个系统的“安全水位”明显提高,不再担心用户数据泄露或越权访问的问题。
给读者的几点建议
如果你刚接触 Spring Security,这里是我的一些经验:
- 不要一上来就试图理解全部配置项,先从最简单的登录流程入手;
- 优先使用官方推荐的方式,再根据需求进行扩展,减少不必要的复杂度;
- JWT 和 Session 各有适用场景,小项目可以试试 Session,大项目建议用 JWT;
- 权限粒度要提前规划好,尤其是前后端分离项目,建议和前端约定好权限标识;
- 多测试,少假设,很多问题不是靠想出来的,而是靠 debug 出来的。
结语:安全这件事,越早越好
回过头来看,我们当初选择 Spring Security 是正确的。虽然初期学习曲线有些陡峭,但一旦熟悉,它为我们节省了大量的重复开发工作,也让系统具备更强的安全性和可维护性。
希望这篇文章能为你搭建第一个 Spring Security 项目提供一些参考和启发。哪怕只是让你避开了一个我曾经踩过的坑,那我也觉得值得。
代码千万行,安全第一行。共勉。

评论 0