Spring Security基础:我的第一个安全认证系统实战记录
大家好,我是林然,一个后端开发团队的负责人。今天我想聊聊我之前在做项目时遇到的一个典型问题——如何快速搭建一个安全、可靠的用户认证系统。这其实是很多项目的起点,尤其是在涉及权限管理、多角色登录等场景下,我们往往需要一个既灵活又稳定的认证框架。
这篇文章不是单纯的技术教程,而是结合我在实际工作中遇到的问题和解决方案的一次分享。希望通过这次复盘,不仅能帮你快速上手Spring Security,也能让你少走一些弯路。
背景介绍:为什么我们要从头开始做认证?

事情发生在去年年底,当时我带的团队接手了一个全新的企业级后台管理系统。这个系统的用户群体比较复杂,包括普通员工、部门管理员、超级管理员等多个角色,权限划分也非常细致。最开始,我们打算直接用公司老项目里现成的认证模块来“套”进去,结果发现那块代码已经年久失修,逻辑混乱不说,扩展性极差,稍微一改就出bug。
于是我们决定:重写整个认证模块,采用Spring Security来做底层支撑。
说实话,刚开始我对Spring Security也是一知半解,只是听说过它功能强大,但真正深入用起来才发现它远比想象中要复杂得多,尤其是涉及到自定义登录流程、多角色鉴权、JWT集成这些高级用法的时候。
接下来我就按照我当时的思路,一步一步带你走进这段实战经历。
问题描述:认证逻辑混乱、权限控制不透明

原来的认证模块主要存在以下几个问题:
- 登录验证逻辑分散:用户名密码验证、验证码校验、IP限制等功能被拆散在多个类中,维护困难。
- 权限控制依赖硬编码:有些接口的权限判断直接写在Controller中,修改权限就得重新上线。
- 无法支持多角色动态切换:前端传参切换角色,后端没有统一的抽象处理机制。
- 无日志记录和失败次数统计:一旦出现非法登录尝试,完全没办法追踪。
- 无法方便接入OAuth2或JWT:这对后期扩展是个大问题,特别是移动端或者第三方系统对接。
这些问题导致整个认证体系非常脆弱,动不动就出错,也容易留下安全隐患。
解决方案:使用Spring Security重构认证模块

我们最终决定以Spring Security为核心,构建一个可扩展、易维护、安全性强的认证系统。下面是我在这次实践中的一些关键点总结:
一、选型与架构设计
我们的目标是做一个标准的前后端分离系统,所以认证部分需要用Token(比如JWT)代替Session,并且要兼容RBAC(基于角色的访问控制)模型。
技术栈:
- Spring Boot + Spring Security
- MyBatis + MySQL
- JWT(JSON Web Token)
- Redis(用于存储Token黑名单)
整体架构设计如下图所示:
[Login Request] → [AuthenticationFilter] → [CustomUserDetailsService]
↓
数据库查用户信息
↓
登录成功 → 生成JWT
↓
返回Token给前端
所有请求必须携带Token,并在拦截器中完成鉴权。
二、Spring Security基本配置
首先,我们需要启用Spring Security并进行基本的配置。这里有几个关键点需要注意。
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterBefore(new JwtAuthFilter(userDetailsService(), jwtUtils), UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.antMatchers("/login").permitAll()
.anyRequest().authenticated()
.and()
.build();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authConf) throws Exception {
return authConf.getAuthenticationManager();
}
}
这里有几个要点解释一下:
csrf().disable():因为我们是前后端分离,所以关闭CSRF防护;sessionCreationPolicy(SessionCreationPolicy.STATELESS):告诉Spring不要创建Session;- 使用自定义的JWT过滤器替代默认的UsernamePasswordAuthenticationFilter。
三、自定义登录流程:从0到1实现JWT认证
这部分是最核心的部分,也是最容易出错的地方。
第一步:实现AuthenticationProvider
我们自己实现了一个JwtAuthenticationProvider,用来处理用户登录请求:
@Component
public class JwtAuthenticationProvider implements AuthenticationProvider {
private final CustomUserDetailsService userDetailsService;
private final PasswordEncoder passwordEncoder;
public JwtAuthenticationProvider(CustomUserDetailsService userDetailsService, PasswordEncoder passwordEncoder) {
this.userDetailsService = userDetailsService;
this.passwordEncoder = passwordEncoder;
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String rawPassword = authentication.getCredentials().toString();
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (passwordEncoder.matches(rawPassword, userDetails.getPassword())) {
return new UsernamePasswordAuthenticationToken(
userDetails.getUsername(),
null,
userDetails.getAuthorities()
);
} else {
throw new BadCredentialsException("用户名或密码错误");
}
}
@Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
小贴士:记得把你的provider加进Spring Security的认证链中!
第二步:生成JWT Token
我们封装了一个JWT工具类,负责生成和解析Token:
public class JwtUtils {
private static final String SECRET_KEY = "my-secret-key";
public String generateToken(String username) {
return Jwts.builder()
.setSubject(username)
.setExpiration(new Date(System.currentTimeMillis() + 864_000_000)) // 10天过期
.signWith(SignatureAlgorithm.HS512, SECRET_KEY)
.compact();
}
public String extractUsername(String token) {
return Jwts.parser()
.setSigningKey(SECRET_KEY)
.parseClaimsJws(token)
.getBody()
.getSubject();
}
}
第三步:实现JWT过滤器
我们在每次请求进来前,都会检查Header里的Token是否存在,如果存在则解析它并构造出当前用户的Authentication对象:
public class JwtAuthFilter extends OncePerRequestFilter {
private final UserDetailsService userDetailsService;
private final JwtUtils jwtUtils;
public JwtAuthFilter(UserDetailsService userDetailsService, JwtUtils jwtUtils) {
this.userDetailsService = userDetailsService;
this.jwtUtils = jwtUtils;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String token = getTokenFromRequest(request);
if (token != null && jwtUtils.validateToken(token)) {
String username = jwtUtils.extractUsername(token);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities()
);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterChain.doFilter(request, response);
}
private String getTokenFromRequest(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
}

别忘了把它添加到Security的过滤链中!
四、权限控制:从“能访问”到“有权限”
我们一开始的做法是在Controller里用注解控制权限:
@PreAuthorize("hasAuthority('ROLE_ADMIN')")
@GetMapping("/admin")
public ResponseEntity<?> adminOnlyEndpoint() {
// 只有管理员能访问
}
但是这种写法虽然简单,却不够灵活,也不便于后期维护。后来我们就改成了更通用的方式——在请求到来时,通过数据库实时加载用户的权限列表,结合RBAC模型进行验证。
为此,我们设计了一张简单的权限表:
| id | name | code | description |
|---|---|---|---|
| 1 | 用户管理 | USER_MANAGE | 管理用户信息 |
| 2 | 订单查看 | ORDER_VIEW | 查看订单详情 |
然后每个用户的角色会关联多个权限,在登录时将这些权限塞到Spring的GrantedAuthority中,这样后续就可以用@PreAuthorize("hasAuthority('ORDER_VIEW')")这样的方式来进行细粒度鉴权。
实施效果:更清晰、更可控、更安全

自从我们引入了Spring Security这套认证机制之后,系统有了以下几个显著变化:
- 权限控制更加直观:只需要在数据库配置权限码,就能控制接口访问;
- 登录逻辑高度解耦:所有的认证过程都被封装在独立的filter和provider中;
- 可扩展性强:后续我们可以轻松接入OAuth2、LDAP、多因素认证等机制;
- 日志完整、可追踪:我们将登录事件、异常操作都记录到了日志中,方便排查问题;
- 支持Token注销和刷新:利用Redis缓存黑名单,实现Token吊销功能。
更重要的是,整套机制运行稳定,再也没有出现因为认证问题导致的服务不可用。
我的经验和建议
如果你现在正在考虑是否要用Spring Security,或者刚入门不知道从哪开始,下面几点经验希望能帮到你:
✅ 1. 不要一开始就追求“全功能”
很多人第一次接触Spring Security就想一步到位写出完美的认证系统,结果被各种概念绕晕。其实你可以先从最基本的表单登录开始练手,再逐步加上JWT、Token管理等高级功能。
✅ 2. 多打印日志,了解每一步发生了什么
Spring Security是一个“魔法”很多的框架,你不去调试的话根本不知道哪个步骤出了问题。建议你在AbstractAuthenticationProcessingFilter、OncePerRequestFilter这些地方加日志输出,观察流程走向。
✅ 3. 合理使用Filter链顺序
Spring Security内部有很多内置过滤器,你添加自己的filter一定要注意顺序。例如我们的JWT过滤器必须放在UsernamePasswordAuthenticationFilter前面,否则会导致重复执行。
✅ 4. 善用@PreAuthorize和表达式语言SpEL
Spring Security内置了很多强大的权限表达式,比如:
@PreAuthorize("#userId == authentication.principal.userId or hasAuthority('ADMIN')")
这类写法可以非常灵活地控制接口权限。
✅ 5. 提前规划好权限结构和角色模型
权限设计不能临时拍脑袋,一定要提前画好权限模型图,明确哪些角色对应哪些权限。否则后面会非常难维护。
✅ 6. 注意生产环境下的Token管理和黑名单机制
Token的安全性非常重要。除了设置合适的过期时间外,你还得考虑黑名单(Blacklist)、自动刷新、注销机制等。建议搭配Redis来做缓存。
写在最后:安全这件事,值得认真对待
回头看看那次项目经历,虽然一开始对Spring Security不熟悉,走了不少弯路,但现在回想起来,它确实是我们后端系统中不可或缺的一环。
特别是在现在这种微服务、API泛滥的时代,一个好的安全框架不仅能保护你的数据,还能提升整个系统的稳定性、可维护性和扩展性。
希望这篇来自一线实战的文章,能帮助你少踩几个坑,更快地上手Spring Security。如果你有任何问题,或者想了解更多细节,欢迎留言交流!
技术路上,我们共勉。

评论 0