JWT 认证和token创建的实现(Spring Security)

前沿 

一、JWT 基础概念

1.1 什么是 JWT

JWT(JSON Web Token)是一种无状态的认证方案,服务端不需要存储Session,所有信息都编码在 Token 中。

dddc8e88-e34e-490b-83e1-58e90898db2a

 

ac8dc563-c41b-4259-ab4c-b50f0022a273

 

1 Maven 依赖

<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>4.5.2</version>
</dependency>

1.2 application.yml 配置

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mental_health_assistant?useSSL=false
    username: root
    password: 123456

server:
  port: 1236

# JWT 配置
jwt:
  secret: MySecretKeyForJWT2025!@#$%^&*()_+SecureKeyHere   # 密钥
  expiration: 86400000            # 24 小时(毫秒)
  refresh-expiration: 604800000   # 7 天(毫秒)
  header: Authorization           # token 头部名称
  token-prefix: "Bearer "         # token 前缀

二、JwtConfig —— 配置映射类

作用:把 application.yml 里的 jwt.* 配置,映射成一个 Java Bean,方便注入使用。

 

 

package org.example.aispingboot.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Data                       // Lombok 自动生成 getter/setter
@Component                  // 交给 Spring 管理
@ConfigurationProperties(prefix = "jwt")   // 绑定 yml 中 jwt 前缀的配置
public class JwtConfig {
    private String secret;              // 密钥
    private long expiration;            // 过期时间
    private long refreshExpiration;     // 刷新时间
    private String header;              // 请求头名
    private String tokenPrefix;         // token 前缀
}

⚠️ 注意:@Data + @ConfigurationProperties 时必须保证 yml 中的 refresh-expiration(kebab-case)能正确映射到 refreshExpiration(camelCase),Spring Boot 默认支持这种松散绑定,所以没问题。

 

三、JwtUtil —— 工具类(重点)

3.1 为什么用 ApplicationContextAware?

工具类通常是 静态方法(static),但 Spring 中的 Bean(比如 JwtConfig)需要通过容器注入。静态方法无法直接用 @Autowired。

解决方案:让工具类实现 ApplicationContextAware,在 Spring 启动时拿到 ApplicationContext,之后就可以通过它手动取出 Bean。

package org.example.aispingboot.util;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.example.aispingboot.config.JwtConfig;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

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

@Component
public class JwtUtil implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    // Spring 启动时自动回调,注入上下文
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        JwtUtil.applicationContext = applicationContext;
    }

    // 从 Spring 容器中获取 JwtConfig
    private static JwtConfig getJwtConfig() {
        return applicationContext.getBean(JwtConfig.class);
    }

    /**
     * 生成 Token
     */
    public static String generateToken(String userId, String username, Integer roleType) {
        try {
            JwtConfig jwtConfig = getJwtConfig();

            // 签名算法(HMAC256 + 密钥)
            Algorithm algorithm = Algorithm.HMAC256(jwtConfig.getSecret());

            // 过期时间点 = 当前时间 + 配置的过期毫秒数
            Date expiration = new Date(System.currentTimeMillis() + jwtConfig.getExpiration());

            String token = JWT.create()
                    .withClaim("userId", userId)
                    .withClaim("username", username)
                    .withClaim("roleType", roleType)
                    .withExpiresAt(expiration)       // 过期时间
                    .withIssuedAt(new Date())        // 签发时间
                    .withIssuer("mental-health-assistant") // 签发者
                    .sign(algorithm);

            return token;
        } catch (Exception e) {
            throw new RuntimeException("Token 生成失败", e);
        }
    }

    /**
     * 校验 Token 并返回解码结果
     */
    public static DecodedJWT verifyToken(String token) {
        JwtConfig jwtConfig = getJwtConfig();
        Algorithm algorithm = Algorithm.HMAC256(jwtConfig.getSecret());
        JWTVerifier verifier = JWT.require(algorithm)
                .withIssuer("mental-health-assistant")
                .build();
        return verifier.verify(token);   // 无效会抛异常
    }

    /**
     * 获取 Token 中的用户ID
     */
    public static String getUserId(String token) {
        return verifyToken(token).getClaim("userId").asString();
    }

    /**
     * 获取用户名
     */
    public static String getUsername(String token) {
        return verifyToken(token).getClaim("username").asString();
    }

    /**
     * 获取角色
     */
    public static Integer getRoleType(String token) {
        return verifyToken(token).getClaim("roleType").asInt();
    }

    /**
     * 判断是否过期
     */
    public static boolean isExpired(String token) {
        try {
            return verifyToken(token).getExpiresAt().before(new Date());
        } catch (Exception e) {
            return true;
        }
    }
}

bf6d730e-e849-4fea-b13e-2942116c46d8

 

四、登录接口(生成 Token)

 

@RestController
@RequestMapping("/api/auth")
public class AuthController {

    @Autowired
    private UserService userService;

    @PostMapping("/login")
    public Result<LoginResponseDTO> login(@RequestBody LoginDTO dto) {
        // 1. 校验用户名密码
        User user = userService.findByUsername(dto.getUsername());
        if (user == null || !passwordEncoder.matches(dto.getPassword(), user.getPassword())) {
            return Result.error("用户名或密码错误");
        }

        // 2. 生成 token
        String token = JwtUtil.generateToken(
                String.valueOf(user.getId()),
                user.getUsername(),
                user.getRoleType()
        );

        // 3. 返回 DTO
        LoginResponseDTO resp = new LoginResponseDTO();
        resp.setToken(token);
        resp.setTokenPrefix("Bearer "); // 或从 JwtConfig 读取
        return Result.success(resp);
    }
}

Spring Security(安全框架) 的核心配置类

Spring Boot 项目中用于配置 Spring Security(安全框架) 的核心配置类。

基于 Spring Security 6.x(通常对应 Spring Boot 3.x)的写法。它的主要作用是定义整个系统的安全策略,比如:哪些接口需要登录才能访问、哪些接口可以匿名访问、如何处理登录和登出等。

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

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            // 1. 关闭 CSRF 防护(如果是前后端分离项目,通常使用 JWT,不需要 CSRF)
            .csrf(csrf -> csrf.disable())
            
            // 2. 配置请求授权规则
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/travel/hello").permitAll() // 允许 /hello 接口匿名访问
                .requestMatchers("/api/user/login").permitAll()   // 允许登录接口匿名访问
                .anyRequest().authenticated()                     // 其他所有请求都需要登录认证
            )
            
            // 3. 配置 Session 管理(无状态,适合前后端分离)
            .sessionManagement(session -> session
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            )
            
            // 4. 开启 HTTP Basic 认证(可选,通常前后端分离用 JWT 替代)
            .httpBasic(Customizer.withDefaults());

        return http.build(); // 构建并返回过滤器链
    }
}
  • @Configuration:Spring 核心注解。告诉 Spring 这是一个配置类,里面可能会包含一个或多个 @Bean 方法。Spring 启动时会加载这个类。

  • @EnableWebSecurity:Spring Security 核心注解。

    • 它的作用是开启 Spring Security 的 Web 安全支持。

    • 加上它之后,Spring Boot 会自动配置一套默认的安全过滤器链(Filter Chain)。如果我们不自定义这个类,Spring Security 默认会拦截所有请求,并生成一个随机密码,要求所有请求都必须经过 HTTP Basic 认证。

  • @EnableMethodSecurity:方法级别安全注解。

    • 它的作用是开启方法级别的权限控制。

    • 加上它之后,你可以在 Service 层或 Controller 层的方法上使用 @PreAuthorize("hasRole('ADMIN')")、@PostAuthorize 等注解,来实现细粒度的权限控制(比如:只有管理员才能删除用户)。

② 核心 Bean:SecurityFilterChain

  • @Bean:告诉 Spring,这个方法返回的对象需要被注册到 Spring 容器中。

  • SecurityFilterChain:这是 Spring Security 6.x 中最核心的 Bean。它代表了一条安全过滤器链。

 

Spring Security优化与注意事项(避坑指南)

  1. 不要返回 null:截图中的 return null; 是占位符。实际项目中,必须返回 http.build(),否则启动会报错。

  2. CSRF 问题:如果是前后端分离项目(Vue/React + Spring Boot),通常需要关闭 CSRF(.csrf(csrf -> csrf.disable())),否则 POST 请求会报 403 错误。如果是传统的 Thymeleaf 服务端渲染项目,则应该保留 CSRF。

  3. 跨域问题(CORS):如果前端和后端不在同一个域名/端口,还需要在 SecurityFilterChain 中配置 CORS,否则浏览器会拦截请求。可以通过 .cors(Customizer.withDefaults()) 开启,并配合一个 CorsConfigurationSource Bean 来配置。

  4. 方法安全:@EnableMethodSecurity 开启后,可以在 Service 方法上加 @PreAuthorize("hasAuthority('user:delete')") 这样的注解,实现非常灵活的权限控制。这是 Spring Security 非常强大的功能。

 

六、整体调用流程

 

登录:
  Controller.login()
      │
      ▼
  JwtUtil.generateToken(userId, username, roleType)
      │
      ├─ getJwtConfig()           ← 从 Spring 容器取配置
      ├─ Algorithm.HMAC256(secret)
      ├─ JWT.create().withClaim(...).sign(algorithm)
      ▼
  返回 "xxx.yyy.zzz" 给前端

请求:
  Header: Authorization: Bearer xxx.yyy.zzz
      │
      ▼
  JwtInterceptor.preHandle()
      │
      ├─ 取 Header,去掉 "Bearer "
      ├─ JwtUtil.verifyToken(token)
      ├─ 解析出 userId/username/roleType
      ├─ UserContext.set(...)
      ▼
  Controller 方法里用 UserContext.getUserId() 即可拿到当前登录用户

这是单个token 的写法 

 

 

posted @ 2026-09-25 16:01  -鹿-  阅读(9)  评论(0)    收藏  举报