在现代Web应用开发中,认证机制是保障系统安全的第一道防线。传统的Session认证方式在分布式系统和前后端分离架构中逐渐显露出局限性,而基于Token的无状态认证方案因其良好的扩展性和灵活性成为主流选择。Spring Security作为Java生态中最成熟的安全框架,通过与JWT(JSON Web Token)或自定义Token的结合,能够构建出既安全又高效的认证体系。
我曾在多个企业级项目中实施过Token认证方案,发现很多开发者在初次接触时容易陷入配置陷阱。比如过滤器链的顺序错误导致认证失效,或是Token验证逻辑不完整引发安全漏洞。本文将基于实战经验,带你从零构建一个完整的Spring Security Token认证方案,重点解决以下核心问题:
java复制@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
public TokenAuthenticationTokenFilter getTokenFilter(){
return new TokenAuthenticationTokenFilter();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(getTokenFilter(), UsernamePasswordAuthenticationFilter.class);
http.authorizeRequests()
.antMatchers("/Login/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf().disable();
}
}
这段配置代码包含了几个关键设计点:
认证管理器暴露:通过authenticationManagerBean()方法将AuthenticationManager暴露为Spring Bean,这是为了支持后续的登录认证流程。在表单登录或自定义认证提供器时都需要用到这个Bean。
过滤器链配置:
TokenAuthenticationTokenFilter添加到UsernamePasswordAuthenticationFilter之前,确保在表单认证前先进行Token验证安全策略设置:
sessionCreationPolicy(SessionCreationPolicy.STATELESS)声明系统为无状态服务,不创建和使用Sessioncsrf().disable()关闭CSRF防护,这在纯API接口且使用Token认证的场景下是安全的。但如果系统同时支持Web页面操作,则需要保留CSRF防护实际项目中我曾遇到一个坑:当同时存在Token认证和表单登录时,如果没有正确设置过滤器顺序,会导致认证逻辑混乱。建议始终将Token过滤器放在认证链的最前端。
.antMatchers("/Login/**").permitAll()这行配置指定了无需认证的白名单路径。在实际开发中,我们通常需要放行以下类型的接口:
/auth/login)/swagger-ui/**)/static/**)/actuator/health)建议采用更精细化的权限控制方案:
java复制.antMatchers(HttpMethod.GET, "/public/**").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/user/**").hasAnyRole("USER","ADMIN")
java复制public class TokenAuthenticationTokenFilter extends OncePerRequestFilter {
@Autowired
private RedisUtils redisUtils;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String token = request.getHeader("token");
if(!StringUtils.hasText(token)){
filterChain.doFilter(request,response);
return;
}
Object tokenData = redisUtils.get(token);
if (tokenData == null){
response.setStatus(200);
response.setCharacterEncoding("utf-8");
response.getWriter().write(JSON.toJSONString(
Result.failed(401,"token 非法","")));
return;
}
Map<String,String> claims = JSON.parseObject(
tokenData.toString(),
new TypeReference<Map<String,String>>(){});
Collection<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority(claims.get("role")));
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(
new UserDto(), null, authorities);
SecurityContextHolder.getContext().setAuthentication(authentication);
filterChain.doFilter(request,response);
}
}
Token获取与验证:
权限信息提取:
安全上下文设置:
java复制// 建议的Redis Key设计
String redisKey = "auth:token:" + token;
// 值结构建议包含更多安全信息
TokenInfo tokenInfo = new TokenInfo(userId, roles, ip, expireTime);
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 返回401状态但无内容 | 响应头未正确设置 | 确保设置Content-Type为application/json |
| Token验证通过但无权限 | 角色前缀配置错误 | Spring Security默认需要ROLE_前缀 |
| Redis连接超时 | Redis服务不可用 | 增加重试机制或降级处理 |
Redis缓存设计:
过滤器优化:
java复制// 在白名单路径直接跳过过滤
if (isIgnorePath(request.getRequestURI())) {
filterChain.doFilter(request, response);
return;
}
推荐采用JWT与随机Token结合的方式:
java复制// 在过滤器中添加审计日志
AuditLog log = new AuditLog();
log.setUserId(claims.get("userId"));
log.setAction("API_ACCESS");
log.setPath(request.getRequestURI());
log.setIp(request.getRemoteAddr());
auditLogService.asyncSave(log);
在分布式系统中需要考虑:
我在实际项目中发现,合理的Token失效机制非常重要。建议实现以下功能:
这套方案经过多个百万级用户产品的验证,在安全性和性能之间取得了良好平衡。关键是要根据业务特点调整Token的有效期和刷新策略,并配合完善的监控系统实时发现异常认证行为。