SpringBoot + JWT + Sa-Token:认证鉴权双框架对比,安全登录与权限控制最佳实践

引言:为什么我们需要认证鉴权框架?

一个刚上线的系统,用户注册登录功能开发完成后,紧接着就要实现权限控制,比如哪些接口需要登录才能访问,哪些接口只有管理员才能调用。刚开始可能只是简单地在几个关键接口上加个登录校验,但随着业务功能越来越多,权限控制逻辑越来越复杂,代码变得混乱不堪。

传统的Session认证方式虽然简单,但在分布式系统中却存在诸多问题:Session共享、服务器内存占用、扩展性差等。这时候,我们就需要更现代、更灵活的认证鉴权方案了。

今天,我们就来聊聊两种主流的认证鉴权方案:JWT和Sa-Token,从资深后端工程师的角度分析它们的优缺点和最佳实践。

JWT架构与特性

JWT(JSON Web Token)是一种开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间安全地传输信息作为JSON对象。

JWT的结构由三部分组成:

  1. Header(头部):包含算法和令牌类型
  2. Payload(载荷):包含声明信息
  3. 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();
    }
}

最佳实践与注意事项

  1. JWT安全性:使用强密钥,设置合理的过期时间,避免在载荷中存储敏感信息
  2. 令牌刷新:实现刷新令牌机制,平衡安全性和用户体验
  3. 黑名单管理:JWT无法主动失效,需要实现黑名单机制
  4. Sa-Token配置:根据业务需求合理配置各项参数
  5. 性能优化:使用Redis等缓存提高验证性能
  6. 错误处理:统一处理认证鉴权异常

总结

JWT和Sa-Token各有优势,选择哪个框架需要根据具体的业务场景来决定。

JWT适合对无状态、跨域要求较高的场景,而Sa-Token则更适合需要丰富功能和简化开发的场景。在实际项目中,我们可以根据团队技术栈、业务复杂度等因素来选择最适合的方案。

记住,认证鉴权是系统安全的第一道防线,无论选择哪种方案,都要确保实现的安全性和可靠性。安全无小事,从选择合适的认证框架开始!


标题:SpringBoot + JWT + Sa-Token:认证鉴权双框架对比,安全登录与权限控制最佳实践
作者:jiangyi
地址:http://jiangyi.space/articles/2025/12/27/1766815591115.html

    0 评论
avatar