SpringBoot + JWT + Sa-Token:认证鉴权双框架对比,安全登录与权限控制最佳实践
引言:为什么我们需要认证鉴权框架?
一个刚上线的系统,用户注册登录功能开发完成后,紧接着就要实现权限控制,比如哪些接口需要登录才能访问,哪些接口只有管理员才能调用。刚开始可能只是简单地在几个关键接口上加个登录校验,但随着业务功能越来越多,权限控制逻辑越来越复杂,代码变得混乱不堪。
传统的Session认证方式虽然简单,但在分布式系统中却存在诸多问题:Session共享、服务器内存占用、扩展性差等。这时候,我们就需要更现代、更灵活的认证鉴权方案了。
今天,我们就来聊聊两种主流的认证鉴权方案:JWT和Sa-Token,从资深后端工程师的角度分析它们的优缺点和最佳实践。
JWT架构与特性
JWT(JSON Web Token)是一种开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间安全地传输信息作为JSON对象。
JWT的结构由三部分组成:
- Header(头部):包含算法和令牌类型
- Payload(载荷):包含声明信息
- Signature(签名):用于验证消息在传输过程中没有被篡改
JWT的核心特性包括:
- 无状态:服务端不需要存储Session信息
- 自包含:令牌包含了用户信息,减少了数据库查询
- 跨域友好:天然支持跨域认证
- 可扩展:可以在载荷中添加自定义信息
Sa-Token架构与特性
Sa-Token是国产的轻量级Java权限认证框架,它将认证、授权、加密、限流等功能集于一体,提供了比传统方案更优雅的API调用。
Sa-Token的核心特性包括:
- 简单易用:API设计简洁,上手快
- 功能全面:集成了登录认证、权限认证、Session会话、单点登录等多种功能
- 高扩展性:提供了丰富的扩展接口
- 多种存储模式:支持Redis、内存、自定义存储等多种方式
- 高性能:优化了底层实现,性能优异
JWT vs Sa-Token:场景对比
那么,面对这两种主流的认证框架,我们该如何选择呢?
JWT更适合的场景:
- 分布式系统,需要无状态认证
- 前后端分离项目
- 需要跨域认证的场景
- 对服务端存储要求低的场景
- 移动端、小程序等轻量级应用
Sa-Token更适合的场景:
- 需要丰富的权限控制功能
- 需要踢人下线、同端互斥登录等高级功能
- 单体应用或微服务架构
- 需要单点登录(SSO)功能
- 对功能完整性要求较高的项目
SpringBoot集成实现
JWT实现方式
首先添加依赖:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
</dependency>
创建JWT工具类:
@Component
public class JwtUtil {
private static final String SECRET = "your-secret-key";
private static final long EXPIRATION_TIME = 86400000; // 24小时
public String generateToken(String username) {
Date now = new Date();
Date expiryDate = new Date(now.getTime() + EXPIRATION_TIME);
return Jwts.builder()
.setSubject(username)
.setIssuedAt(now)
.setExpiration(expiryDate)
.signWith(SignatureAlgorithm.HS512, SECRET)
.compact();
}
public String getUsernameFromToken(String token) {
Claims claims = Jwts.parser()
.setSigningKey(SECRET)
.parseClaimsJws(token)
.getBody();
return claims.getSubject();
}
public boolean validateToken(String token) {
try {
Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token);
return true;
} catch (JwtException | IllegalArgumentException e) {
return false;
}
}
}
Sa-Token实现方式
添加依赖:
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>1.34.0</version>
</dependency>
配置application.yml:
sa-token:
# token名称 (同时也是cookie名称)
token-name: satoken
# token有效期,单位s 默认30天, -1代表永不过期
timeout: 2592000
# token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒
active-timeout: -1
# 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
is-concurrent: true
# 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
is-share: true
# token风格
token-style: uuid
实际应用案例
案例一:用户登录认证
使用JWT实现登录认证:
@RestController
@RequestMapping("/auth")
public class AuthController {
@Autowired
private JwtUtil jwtUtil;
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest request) {
// 验证用户名密码
if (validateCredentials(request.getUsername(), request.getPassword())) {
String token = jwtUtil.generateToken(request.getUsername());
return ResponseEntity.ok(new LoginResponse(token));
}
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
private boolean validateCredentials(String username, String password) {
// 实际验证逻辑
return true;
}
}
使用Sa-Token实现登录认证:
@RestController
@RequestMapping("/auth")
public class AuthController {
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest request) {
// 验证用户名密码
if (validateCredentials(request.getUsername(), request.getPassword())) {
// 登录,生成token
StpUtil.login(request.getUsername());
return ResponseEntity.ok(new LoginResponse(StpUtil.getTokenInfo()));
}
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
private boolean validateCredentials(String username, String password) {
// 实际验证逻辑
return true;
}
}
案例二:权限控制
JWT方式需要自定义拦截器:
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private JwtUtil jwtUtil;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String token = extractToken(request);
if (token != null && jwtUtil.validateToken(token)) {
String username = jwtUtil.getUsernameFromToken(token);
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(username, null, new ArrayList<>());
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterChain.doFilter(request, response);
}
private String extractToken(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
}
Sa-Token方式更简单:
@RestController
@RequestMapping("/api")
public class ApiController {
@SaCheckLogin // 检查是否登录
@GetMapping("/user-info")
public ResponseEntity<?> getUserInfo() {
String loginId = StpUtil.getLoginIdAsString();
return ResponseEntity.ok(userInfoService.getUserById(loginId));
}
@SaCheckRole("admin") // 检查角色
@DeleteMapping("/user/{id}")
public ResponseEntity<?> deleteUser(@PathVariable Long id) {
userService.deleteUser(id);
return ResponseEntity.ok().build();
}
}
最佳实践与注意事项
- JWT安全性:使用强密钥,设置合理的过期时间,避免在载荷中存储敏感信息
- 令牌刷新:实现刷新令牌机制,平衡安全性和用户体验
- 黑名单管理:JWT无法主动失效,需要实现黑名单机制
- Sa-Token配置:根据业务需求合理配置各项参数
- 性能优化:使用Redis等缓存提高验证性能
- 错误处理:统一处理认证鉴权异常
总结
JWT和Sa-Token各有优势,选择哪个框架需要根据具体的业务场景来决定。
JWT适合对无状态、跨域要求较高的场景,而Sa-Token则更适合需要丰富功能和简化开发的场景。在实际项目中,我们可以根据团队技术栈、业务复杂度等因素来选择最适合的方案。
记住,认证鉴权是系统安全的第一道防线,无论选择哪种方案,都要确保实现的安全性和可靠性。安全无小事,从选择合适的认证框架开始!
标题:SpringBoot + JWT + Sa-Token:认证鉴权双框架对比,安全登录与权限控制最佳实践
作者:jiangyi
地址:http://jiangyi.space/articles/2025/12/27/1766815591115.html