Spring Boot + JWT 身份认证

作者:Administrator 发布时间: 2026-01-14 阅读量:1 评论数:0

在前后端分离的开发模式下,传统的 Session 方案显得力不从心:跨域问题难搞、服务器集群要做 Session 共享、移动端支持不友好……

这时候,JWT (JSON Web Token) 就成了最佳替代方案。

今天这篇文章不讲废话,直接带你在 Spring Boot 项目中从零实现一套标准、可用的 JWT 认证系统。

1. 为什么选 JWT?

简单来说,Session 是把用户信息存在服务端(内存或数据库),发给客户端一个 JSESSIONID;而 JWT 是把用户信息加密后存在客户端(Token)。

  • 无状态 (Stateless):服务端不存 Token,服务器随便横向扩展,不需要复杂的 Redis Session 共享配置。

  • 跨终端:iOS、Android、小程序、Web 通用。

2. 准备工作

首先,在你的 pom.xml 中引入 JWT 的工具包 jjwt。这是目前 Java 界最常用的 JWT 库。

XML

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>
<dependency>
    <groupId>javax.xml.bind</groupId>
    <artifactId>jaxb-api</artifactId>
    <version>2.3.1</version>
</dependency>

3. 核心三步走

实现 JWT 认证只需要三个核心组件:工具类、拦截器、配置类

第一步:编写 JWT 工具类 (JwtUtils)

这个类负责生成 Token 和解析 Token。我们不仅要生成,还要把生成的 Token 里的用户信息(Claims)取出来。

Java

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.stereotype.Component;

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

@Component
public class JwtUtils {

    // 密钥,非常重要,实际生产中请放在配置文件中,不要硬编码
    private static final String SECRET_KEY = "MySuperSecretKeyForMyBlogApp";
    // 过期时间:24小时 (毫秒)
    private static final long EXPIRATION_TIME = 86400000;

    /**
     * 生成 Token
     */
    public String generateToken(String username) {
        Map<String, Object> claims = new HashMap<>();
        return Jwts.builder()
                .setClaims(claims)
                .setSubject(username) // 设置主题(这里存用户名)
                .setIssuedAt(new Date(System.currentTimeMillis())) // 签发时间
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)) // 过期时间
                .signWith(SignatureAlgorithm.HS256, SECRET_KEY) // 签名算法和密钥
                .compact();
    }

    /**
     * 解析 Token,获取 Claims
     */
    public Claims extractClaims(String token) {
        return Jwts.parser()
                .setSigningKey(SECRET_KEY)
                .parseClaimsJws(token)
                .getBody();
    }

    /**
     * 验证 Token 是否有效
     */
    public boolean validateToken(String token, String username) {
        final String extractedUsername = extractClaims(token).getSubject();
        return (extractedUsername.equals(username) && !isTokenExpired(token));
    }

    private boolean isTokenExpired(String token) {
        return extractClaims(token).getExpiration().before(new Date());
    }
}

第二步:编写拦截器 (JwtInterceptor)

拦截器是“门卫”。它的作用是拦截所有请求,检查 Header 里有没有 Token,以及 Token 对不对。

Java

import io.jsonwebtoken.Claims;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
public class JwtInterceptor implements HandlerInterceptor {

    @Autowired
    private JwtUtils jwtUtils;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1. 获取请求头中的 Authorization
        String authHeader = request.getHeader("Authorization");

        // 2. 判断格式是否正确 (通常是 "Bearer " 开头)
        if (authHeader != null && authHeader.startsWith("Bearer ")) {
            String token = authHeader.substring(7); // 去掉 "Bearer "

            try {
                // 3. 尝试解析 Token
                Claims claims = jwtUtils.extractClaims(token);
                // 4. 将解析出的用户信息放入 Request 域,方便 Controller 使用
                request.setAttribute("username", claims.getSubject());
                return true; // 放行
            } catch (Exception e) {
                // Token 无效或过期
                response.setStatus(401);
                return false;
            }
        }

        // 没有 Token,返回 401 未授权
        response.setStatus(401);
        return false;
    }
}

第三步:配置拦截器 (WebConfig)

写好了门卫,得告诉它守哪扇门。我们需要配置拦截路径。

Java

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private JwtInterceptor jwtInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(jwtInterceptor)
                .addPathPatterns("/api/**") // 拦截所有 /api 开头的请求
                .excludePathPatterns("/api/login", "/api/register"); // 放行登录和注册接口
    }
}

4. 实战演示

现在,我们来看在 Controller 中怎么用。

登录接口 (不需要 Token):

Java

@PostMapping("/login")
public Result login(@RequestBody UserLoginDto user) {
    // 1. 校验用户名密码 (这里省略数据库查询逻辑)
    if ("admin".equals(user.getUsername()) && "123456".equals(user.getPassword())) {
        // 2. 校验通过,生成 Token
        String token = jwtUtils.generateToken(user.getUsername());
        return Result.success(token);
    }
    return Result.fail("用户名或密码错误");
}

业务接口 (需要 Token):

Java

@GetMapping("/user/profile")
public Result getProfile(HttpServletRequest request) {
    // 从拦截器塞入的属性中获取用户名
    String username = (String) request.getAttribute("username");
    return Result.success("当前登录用户是:" + username);
}

5. 进阶避坑指南 (Senior Tips)

做完上面这些,一个基本的 JWT 认证就完成了。但在生产环境中,还有几个坑要注意:

  1. Token 无法撤销的问题:

    JWT 一旦签发,在过期之前都是有效的。如果用户想“注销登录”或者修改了密码,旧 Token 依然可用。

    • 解决方案:引入 Redis。用户注销时,把这个 Token 的 ID 存入 Redis 设置为黑名单,拦截器校验时多查一步 Redis。

  2. 不要在 Payload 存敏感信息:

    JWT 的 Payload 部分只是 Base64 编码,没有加密!任何人拿到 Token 都能解码看到 Payload 里的内容。千万不要把密码、手机号放在这里。

  3. 刷新 Token (Refresh Token):

    为了安全,Access Token 有效期通常很短(如 30 分钟)。为了不让用户频繁登录,通常会配合一个有效期较长的 Refresh Token 来换取新的 Access Token。


总结

通过 jjwt + HandlerInterceptor,我们在 Spring Boot 中轻松实现了无状态认证。这套方案轻量、高效,是目前微服务和前后端分离项目的首选。

接下来的文章,我们将结合 Redis,讲讲如何解决 JWT 最大的痛点——Token 的注销与续签


原文链接:https://makule.top/

评论