Spring Boot 集成 JWT 实现安全认证详解

57 阅读4分钟
  1. 引言

随着 Web 服务的发展,如何保障系统的安全成为关键问题。在分布式系统和 RESTful API 中,传统的会话(Session)认证方式往往存在扩展性问题,因此 JWT(JSON Web Token) 逐渐成为主流的认证解决方案。Spring Boot 作为一个轻量级的微服务框架,能够非常方便地与 JWT 集成,实现用户认证和授权。本文将深入讲解 JWT 的工作原理、Spring Boot 中的具体实现步骤,以及一些常见的安全问题和最佳实践。

  1. JWT 简介

2.1. 什么是 JWT?

JWT(JSON Web Token)是一种开放的标准(RFC 7519),用于在客户端与服务器之间传递经过签名的令牌信息。它具有以下特点:

  • 无状态:JWT 本质上是一段自包含的 JSON 数据,不需要服务器端存储会话状态。
  • 可验证性:JWT 使用签名或加密方式保证数据不可篡改。
  • 可扩展性:JWT 中可以包含用户角色、权限等自定义声明(Claims),以支持复杂的权限控制。

2.2. JWT 的结构

JWT 的令牌结构由三个部分组成,用 . 连接:

header.payload.signature

  • Header:声明令牌的类型和签名算法,如 {"alg": "HS256", "typ": "JWT"}。
  • Payload:存储声明信息(如用户 ID、角色等)。
  • Signature:用于保证令牌完整性。

一个示例 JWT 如下:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyMSIsInJvbGUiOiJBRE1JTiIsImlhdCI6MTY1MDYzMzY4NCwiZXhwIjoxNjUwNjM0Mjg0fQ.QMQrcyVOaG-ovIDN6MEss9O9afDAZJ_8Ooa8Ar0exCk
  1. Spring Boot 集成 JWT 实现用户认证

下面我们将使用 Spring Boot 和 JWT 来实现一个完整的用户登录认证流程,包括用户登录、生成 JWT 令牌、请求拦截以及权限控制。

3.1. 引入依赖

我们需要在 pom.xml 中添加必要的依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

jjwt 是一个用于生成和解析 JWT 的 Java 库。

3.2. 配置安全过滤器和安全配置

3.2.1. 自定义 JWT 过滤器

我们需要在每次请求时解析 JWT,并基于令牌中的信息来决定是否允许访问:

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class JwtAuthenticationFilter extends OncePerRequestFilter {

    private final String secretKey = "yourSecretKey";

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {

        String token = request.getHeader("Authorization");

        if (token != null && token.startsWith("Bearer ")) {
            try {
                Claims claims = Jwts.parser()
                        .setSigningKey(secretKey)
                        .parseClaimsJws(token.substring(7))
                        .getBody();
                // 将用户信息存入上下文
                SecurityContextHolder.getContext().setAuthentication(
                        new JwtAuthenticationToken(claims.getSubject(), claims.get("role"))
                );
            } catch (Exception e) {
                response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                return;
            }
        }

        filterChain.doFilter(request, response);
    }
}

3.2.2. 配置 Spring Security

我们需要将自定义的过滤器加入到 Spring Security 过滤链中:

import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;

@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.csrf().disable()
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            .authorizeRequests()
            .antMatchers("/login").permitAll()
            .anyRequest().authenticated()
            .and()
            .addFilter(new JwtAuthenticationFilter());
        return http.build();
    }
}

3.3. 实现用户登录与 JWT 生成

3.3.1. 登录控制器

当用户登录成功后,我们将生成一个 JWT,并将其返回给客户端:

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.web.bind.annotation.*;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@RestController
public class AuthController {

    private final String secretKey = "yourSecretKey";

    @PostMapping("/login")
    public Map<String, String> login(@RequestParam String username, @RequestParam String password) {
        // 模拟用户认证(实际应用中应查询数据库)
        if ("admin".equals(username) && "password".equals(password)) {
            String token = Jwts.builder()
                    .setSubject(username)
                    .claim("role", "ADMIN")
                    .setIssuedAt(new Date())
                    .setExpiration(new Date(System.currentTimeMillis() + 3600000)) // 1小时过期
                    .signWith(SignatureAlgorithm.HS256, secretKey)
                    .compact();

            Map<String, String> response = new HashMap<>();
            response.put("token", "Bearer " + token);
            return response;
        } else {
            throw new RuntimeException("Invalid credentials");
        }
    }
}
  1. 使用 JWT 进行请求验证

客户端在后续请求中需要在 Authorization 头中携带 JWT:

curl -H "Authorization: Bearer <token>" http://localhost:8080/protected-resource

Spring Security 会通过自定义的过滤器验证令牌,并根据令牌中的用户信息进行权限控制。

  1. 常见问题与最佳实践

5.1. JWT 的失效控制

由于 JWT 是无状态的,无法直接在服务端销毁。为了解决这一问题,我们可以:

  1. 设置合理的过期时间,比如 1 小时。
  2. 黑名单机制:将已注销或失效的 JWT 存入 Redis 黑名单。

5.2. 刷新令牌(Refresh Token)

为了避免频繁登录,我们可以为用户提供 刷新令牌,允许用户在访问令牌过期后获取新的 JWT。

5.3. 使用 HTTPS 加密传输

JWT 中可能包含敏感信息,因此建议在生产环境中使用 HTTPS 传输,避免令牌在网络传输中被截获。

5.4. 避免过度暴露用户信息

不要在 JWT 中存储敏感的用户数据,只存放必要的声明信息,如用户 ID 和角色。

  1. 总结

通过本文的讲解,我们了解了如何在 Spring Boot 中集成 JWT 进行用户认证,包括 JWT 的生成、验证、过滤器的配置以及登录逻辑的实现。JWT 的无状态特性使得它非常适合微服务架构和分布式系统,但在使用时也需要注意其安全性和有效性管理。在实际项目中,结合 Redis 黑名单、刷新令牌等机制,可以进一步提升系统的安全性和用户体验。