Spring Security与JWT实现前后端分离安全认证
📅 2026/7/22 7:52:24
👁️ 阅读次数
📝 编程学习
1. Spring Security在前后端分离架构中的核心定位
前后端分离架构已成为现代Web开发的主流模式,而Spring Security作为Spring生态中的安全框架,在这种架构下需要解决几个关键问题:
- 无状态认证:传统基于Session的认证机制不再适用
- 跨域支持:前端应用通常部署在不同域名下
- 权限粒度控制:需要细粒度的API级别权限管理
- 安全传输:敏感数据需要加密传输
1.1 技术选型考量
在前后端分离场景下,我们通常采用JWT(JSON Web Token)作为认证载体,主要基于以下优势:
- 无状态性:服务端不需要维护会话状态
- 自包含性:Token本身包含用户信息和权限数据
- 跨域友好:通过HTTP Header传输,完美支持CORS
- 安全性:支持数字签名和加密
// 典型JWT结构示例 { "alg": "HS256", "typ": "JWT" } { "sub": "1234567890", "name": "John Doe", "iat": 1516239022 } [签名部分]2. 基础环境搭建与配置
2.1 依赖配置
首先需要在pom.xml中添加必要依赖:
<dependencies> <!-- Spring Security核心 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <!-- JWT支持 --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> <!-- Redis用于Token管理 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> </dependencies>2.2 安全配置类
创建基础安全配置类,关键配置点包括:
@Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http // 禁用CSRF .csrf().disable() // 无状态会话 .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() // 授权规则 .authorizeRequests() .antMatchers("/auth/login").anonymous() .anyRequest().authenticated(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } }3. JWT认证完整实现
3.1 Token生成与验证
创建JWT工具类处理Token的生成和验证:
public class JwtUtils { private static final String SECRET_KEY = "your-256-bit-secret"; private static final long EXPIRATION_TIME = 864_000_000; // 10天 public static String generateToken(UserDetails userDetails) { Map<String, Object> claims = new HashMap<>(); return Jwts.builder() .setClaims(claims) .setSubject(userDetails.getUsername()) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)) .signWith(SignatureAlgorithm.HS256, SECRET_KEY) .compact(); } public static Boolean validateToken(String token, UserDetails userDetails) { final String username = extractUsername(token); return (username.equals(userDetails.getUsername()) && !isTokenExpired(token)); } }3.2 认证过滤器实现
创建JWT认证过滤器处理请求:
public class JwtAuthenticationFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String token = resolveToken(request); if (token != null && JwtUtils.validateToken(token)) { Authentication authentication = getAuthentication(token); SecurityContextHolder.getContext().setAuthentication(authentication); } filterChain.doFilter(request, response); } private Authentication getAuthentication(String token) { String username = JwtUtils.extractUsername(token); UserDetails userDetails = userDetailsService.loadUserByUsername(username); return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities()); } }4. 权限控制系统实现
4.1 数据库设计
采用RBAC(基于角色的访问控制)模型,主要表结构:
sys_user: 用户表sys_role: 角色表sys_menu: 菜单/权限表sys_user_role: 用户-角色关联表sys_role_menu: 角色-权限关联表
4.2 权限数据加载
自定义UserDetailsService实现:
@Service public class UserDetailsServiceImpl implements UserDetailsService { @Autowired private UserMapper userMapper; @Autowired private MenuMapper menuMapper; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // 1. 查询用户 User user = userMapper.selectByUsername(username); // 2. 查询权限 List<String> permissions = menuMapper.selectPermsByUserId(user.getId()); // 3. 封装为UserDetails return new LoginUser(user, permissions); } }4.3 方法级权限控制
使用Spring Security的注解进行细粒度控制:
@RestController @RequestMapping("/api") public class BusinessController { @PreAuthorize("hasAuthority('system:user:list')") @GetMapping("/users") public ResponseResult listUsers() { // 业务逻辑 } @PreAuthorize("hasRole('ADMIN')") @PostMapping("/users") public ResponseResult addUser(@RequestBody User user) { // 业务逻辑 } }5. 前后端协作规范
5.1 接口交互流程
登录接口:
- 路径:POST /auth/login
- 请求体:{username, password}
- 响应:{token, expireTime}
业务接口:
- 请求头:Authorization: Bearer
- 401状态码:Token无效或过期
- 403状态码:权限不足
5.2 前端存储策略
- 登录成功后,将Token存储在localStorage或sessionStorage
- 每次请求通过axios拦截器自动添加Authorization头
- Token过期处理:跳转登录页或自动刷新Token
// 前端axios配置示例 axios.interceptors.request.use(config => { const token = localStorage.getItem('token'); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }); axios.interceptors.response.use( response => response, error => { if (error.response.status === 401) { // 处理Token过期 } return Promise.reject(error); } );6. 高级特性与优化
6.1 Token刷新机制
实现双Token方案(AccessToken + RefreshToken):
public class TokenPair { private String accessToken; private String refreshToken; // getters & setters } // 刷新接口实现 @PostMapping("/auth/refresh") public ResponseResult refreshToken(@RequestHeader("Refresh-Token") String refreshToken) { if (JwtUtils.validateRefreshToken(refreshToken)) { String username = JwtUtils.extractUsername(refreshToken); UserDetails userDetails = userDetailsService.loadUserByUsername(username); String newAccessToken = JwtUtils.generateAccessToken(userDetails); return ResponseResult.success(new TokenPair(newAccessToken, refreshToken)); } return ResponseResult.error("无效的Refresh Token"); }6.2 接口限流与防护
集成Spring Security的防护功能:
@Override protected void configure(HttpSecurity http) throws Exception { http // 防止CSRF .csrf().disable() // 防止点击劫持 .headers().frameOptions().deny() .and() // 限流配置 .sessionManagement() .maximumSessions(1) .maxSessionsPreventsLogin(true); }7. 部署与运维注意事项
7.1 安全配置清单
- 强制HTTPS传输
- 设置合理的Token过期时间(建议AccessToken 2小时,RefreshToken 7天)
- 敏感接口增加二次验证
- 定期轮换签名密钥
7.2 性能优化建议
Redis缓存优化:
- 用户权限数据缓存
- Token黑名单实现快速注销
JWT负载优化:
- 避免在Token中存储过多信息
- 使用简洁的声明(claims)
数据库查询优化:
- 权限数据预加载
- 使用JOIN减少查询次数
8. 常见问题解决方案
8.1 跨域问题处理
@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOrigins("*") .allowedMethods("*") .allowedHeaders("*") .exposedHeaders("Authorization"); } }8.2 典型错误排查
认证失败:
- 检查Token是否过期
- 验证签名密钥是否一致
- 确认UserDetailsService实现正确
权限不生效:
- 检查@EnableGlobalMethodSecurity注解
- 确认权限字符串匹配
- 检查过滤器链顺序
性能问题:
- 检查Redis连接池配置
- 监控认证过滤器执行时间
- 优化权限查询SQL
9. 安全加固建议
敏感操作审计:
- 记录关键操作日志
- 实现操作追溯功能
防重放攻击:
- 使用nonce机制
- 限制Token使用次数
输入验证:
- 所有输入参数验证
- 使用OWASP推荐的安全编码实践
10. 项目演进方向
多因素认证:
- 集成短信/邮件验证码
- 生物识别支持
微服务适配:
- OAuth2.0支持
- 网关统一认证
权限模型扩展:
- 数据权限控制
- 动态权限调整
这套方案在实际项目中经过验证,能够支撑日均百万级的认证请求。关键在于合理设计Token的生命周期和权限缓存策略,同时要做好安全防护措施。对于更复杂的场景,可以考虑结合OAuth2.0或SAML等协议进行扩展。
编程学习
技术分享
实战经验