当前位置: 首页 > java >正文

Spring Boot 3中JWT密钥安全存储方案

在Spring Boot 3中,存储和赋值JWT加密密钥的安全实践如下。核心原则是避免硬编码密钥,优先使用外部化配置和安全存储方案


推荐方案:外部化配置 + 环境变量

1. 配置 application.yml/application.properties

yaml

# application.yml
jwt:secret-key: "${JWT_SECRET_KEY}"  # 从环境变量注入
2. 通过环境变量注入密钥
  • 本地开发:在IDE运行配置或.env文件中设置

    bash

    # .env 文件(确保.gitignore)
    export JWT_SECRET_KEY=myStrongSecretKeyWith32Chars
  • 生产环境:通过容器/服务器环境变量注入

    bash

    # Docker示例
    docker run -e JWT_SECRET_KEY=your_secure_key your-app
  • 云服务:使用云平台密钥管理(如AWS Secrets Manager/Azure Key Vault)

3. Java代码中获取密钥

java

@Component
public class JwtUtil {private final String secretKey;// 构造器注入public JwtUtil(@Value("${jwt.secret-key}") String secretKey) {this.secretKey = secretKey;}public String generateToken(UserDetails userDetails) {return Jwts.builder().subject(userDetails.getUsername()).issuedAt(new Date()).expiration(new Date(System.currentTimeMillis() + 86400000)) // 24h.signWith(getSigningKey()) // 使用安全密钥.compact();}private Key getSigningKey() {byte[] keyBytes = Decoders.BASE64.decode(secretKey); // 密钥需Base64编码return Keys.hmacShaKeyFor(keyBytes);}
}

进阶安全方案

方案1:密钥管理服务(生产推荐)

java

@Bean
public Key jwtKey(SecretManagerService secretService) {String base64Key = secretService.getSecret("jwt-secret"); // 从Vault/AWS SM获取return Keys.hmacShaKeyFor(Decoders.BASE64.decode(base64Key));
}
方案2:自动生成密钥(仅限开发)

java

@Bean
public Key jwtKey() {return Keys.secretKeyFor(SignatureAlgorithm.HS256); // 每次启动变化,不适合生产
}

安全最佳实践

  1. 密钥强度:HS256算法至少32字符,推荐64字符随机字符串

    bash

    # 生成强密钥(Linux/Mac)
    openssl rand -base64 32
  2. 密钥轮换:通过密钥管理服务实现定期轮换

  3. 访问控制

    • 禁止日志打印密钥

    • 应用配置最小权限原则

  4. 配置文件安全

    properties

    # 禁止提交敏感数据到仓库
    /src/main/resources/application*.yml -> .gitignore

不同环境配置示例

环境存储位置注入方式
本地开发.env 文件Spring Boot @Value
测试环境CI/CD 管道变量部署脚本注入
生产环境AWS Secrets Manager/Hashicorp VaultSDK动态获取

关键提示:永远不要将真实密钥提交到代码仓库!Spring Boot的配置外部化机制(优先级从高到低):

  1. 命令行参数 --jwt.secret-key=xxx

  2. 环境变量 JWT_SECRET_KEY

  3. 配置文件 application-{profile}.yml

通过遵循这些实践,可确保JWT密钥在Spring Boot 3应用中得到安全管理和使用。

一、非静态实现

package com.weiyu.utils;import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;import java.util.Date;
import java.util.Map;/*** JWT工具类 (非静态实现)*/
@Component
public class JwtUtil {private final String secretKey;// 通过构造器注入密钥public JwtUtil(@Value("${jwt.secret-key}") String secretKey) {this.secretKey = secretKey;}/*** 生成 token 令牌* @param claims 业务数据* @return token 令牌*/public String genToken(Map<String, Object> claims) {// 10小时有效期long expirationMs = 1000 * 60 * 60 * 10;return JWT.create().withClaim("claims", claims).withIssuedAt(new Date()).withExpiresAt(new Date(System.currentTimeMillis() + expirationMs)).sign(Algorithm.HMAC256(secretKey));}/*** 验证并解析 token 令牌* @param token token 令牌* @return token 中的业务数据* @throws JWTVerificationException 当token验证失败时抛出*/public Map<String, Object> parseToken(String token) throws JWTVerificationException {return JWT.require(Algorithm.HMAC256(secretKey)).build().verify(token).getClaim("claims").asMap();}
}

使用示例:

生成 token

@RestController
@RequestMapping("/account")
@Slf4j
public class AccountController {@Autowiredprivate JwtUtil jwtUtil;@PostMapping("/login")public Result<?> login(String account, String password) {// 前端传过来的就是password的MD5密文if (password.equalsIgnoreCase(finalPassword)) {// 登录成功// 构建令牌数据,包含id,用户名(账号)Map<String, Object> claims = new HashMap<>();claims.put("userId", loginAccount.getAccount());claims.put("userName", loginAccount.getAccount());// 生成token令牌String token = jwtUtil.genToken(claims);return Result.success(token);}}

验证 token

package com.weiyu.interceptors;import com.weiyu.utils.JwtUtil;
import com.weiyu.utils.ThreadLocalUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;import java.util.Map;/*** 登录拦截器* 用于拦截请求,验证JWT令牌*/
@Component
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {@Autowiredprivate JwtUtil jwtUtil;/*** 预处理请求* 在请求到达 Controller 方法之前执行,当请求进入 Spring MVC 的 DispatcherServlet 后,首先会经过拦截器的 preHandle 方法* @param request 请求对象* @param response 响应对象* @param handler 处理器对象* @return 是否放行请求,返回 true 表示继续处理请求(放行),false 表示中断请求(不放行,需自行处理响应)* @throws Exception 异常*/@Overridepublic boolean preHandle(@NonNull HttpServletRequest request,@NonNull HttpServletResponse response,@NonNull Object handler) throws Exception {// 从请求头中获取 token 令牌String token = request.getHeader("Authorization");// 验证 token 令牌try {// 解析 token 令牌Map<String, Object> claims = jwtUtil.parseToken(token);// 将解析出来的 token 令牌数据存储到 ThreadLocalThreadLocalUtil.set(claims);// 放行return true;} catch (Exception e) {// http 响应状态码为 401response.setStatus(401);// 不放行return false;}}/*** 后处理请求* 在请求处理完成之后执行,包括 Controller 方法执行完毕、视图渲染完成(如返回 JSON 或 HTML)之后才会触发* @param request 请求对象* @param response 响应对象* @param handler 处理器对象* @param exception 异常* @throws Exception 异常*/@Overridepublic void afterCompletion(@NonNull HttpServletRequest request,@NonNull HttpServletResponse response,@NonNull Object handler,Exception exception) throws Exception {// 清空 ThreadLocal 的数据ThreadLocalUtil.remove();}
}

二、静态实现(不推荐)

以下是使用静态方法实现JWT工具类的安全方案,通过静态初始化块和@PostConstruct确保密钥安全注入:

java

package com.weiyu.utils;import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;import javax.annotation.PostConstruct;
import java.util.Date;
import java.util.Map;/*** 静态方法实现的JWT工具类* 注意:静态实现需谨慎处理密钥注入*/
@Component
public class JwtUtil {private static String SECRET_KEY; // 静态密钥变量private static final long EXPIRATION_MS = 1000 * 60 * 60 * 10; // 10小时有效期// 通过setter注入密钥@Value("${jwt.secret-key}")public void setSecretKey(String secretKey) {// 禁止多次设置密钥if (SECRET_KEY == null) {SECRET_KEY = secretKey;}}// 初始化检查@PostConstructprivate void init() {if (SECRET_KEY == null || SECRET_KEY.isBlank()) {throw new IllegalStateException("JWT密钥未正确配置");}}/*** 生成 token 令牌* @param claims 业务数据* @return token 令牌*/public static String genToken(Map<String, Object> claims) {checkKeyInitialized();return JWT.create().withClaim("claims", claims).withIssuedAt(new Date()).withExpiresAt(new Date(System.currentTimeMillis() + EXPIRATION_MS)).sign(Algorithm.HMAC256(SECRET_KEY));}/*** 验证并解析 token 令牌* @param token token 令牌* @return token 中的业务数据* @throws JWTVerificationException 当token验证失败时抛出*/public static Map<String, Object> parseToken(String token) throws JWTVerificationException {checkKeyInitialized();return JWT.require(Algorithm.HMAC256(SECRET_KEY)).build().verify(token).getClaim("claims").asMap();}// 密钥初始化检查private static void checkKeyInitialized() {if (SECRET_KEY == null) {throw new IllegalStateException("JWT工具类未初始化完成,请确保已注入密钥");}}
}

关键实现说明:

  1. 安全注入机制

    java

    @Value("${jwt.secret-key}")
    public void setSecretKey(String secretKey) {if (SECRET_KEY == null) { // 确保密钥只设置一次SECRET_KEY = secretKey;}
    }
  2. 初始化验证

    java

    @PostConstruct
    private void init() {if (SECRET_KEY == null || SECRET_KEY.isBlank()) {throw new IllegalStateException("JWT密钥未正确配置");}
    }
  3. 使用前检查

    java

    private static void checkKeyInitialized() {if (SECRET_KEY == null) {throw new IllegalStateException("JWT工具类未初始化完成");}
    }

使用示例:

java

// 在Spring管理的Bean中使用
@Service
public class AuthService {public String generateUserToken(User user) {Map<String, Object> claims = Map.of("userId", user.getId(),"role", user.getRole());return JwtUtil.genToken(claims); // 静态调用}public User validateToken(String token) {try {Map<String, Object> claims = JwtUtil.parseToken(token);return User.fromClaims(claims);} catch (JWTVerificationException e) {throw new UnauthorizedException("无效令牌");}}
}

配置要求 (application.yml):

yaml

jwt:secret-key: "${JWT_SECRET}"  # 从环境变量获取

安全注意事项:

  1. 密钥管理

    • 生产环境必须通过环境变量注入密钥

    • 本地开发使用.env文件(加入.gitignore

  2. 密钥生成

    bash

    # 生成强密钥(32字节)
    openssl rand -base64 32
  3. 静态类限制

    • 无法直接使用Spring的依赖注入优势

    • 不能动态刷新密钥(需要重启应用)

    • 测试时需要额外初始化

  4. 替代方案建议

    java

    // 更推荐的实例方法模式(非静态)
    @Service
    public class AuthService {private final JwtUtil jwtUtil; // 注入实例public String generateToken() {return jwtUtil.genToken(claims);}
    }

静态实现的优缺点:

优点

  • 无需注入即可调用

  • 工具类方法调用简洁

  • 适合简单工具类场景

缺点

  • 密钥在类加载后保持不变

  • 无法利用Spring的配置刷新机制

  • 多环境管理更复杂

  • 测试时需手动模拟初始化

建议:在需要严格安全控制的场景(如生产环境),推荐使用非静态实现。静态实现更适合内部工具或低安全要求的场景。

http://www.xdnf.cn/news/17843.html

相关文章:

  • 图灵测试:人工智能的“行为主义判据”与哲学争议
  • 论,物联网日志系统架构如何设计?
  • 使用colmap自制3DGaussian_Splatting数据集
  • Java进阶学习之Stream流的基本概念以及使用技巧
  • 第四天~在CANFD或CAN2.0的ARXML文件中实现Multiplexor多路复用信号实战
  • 3D-R1、Scene-R1、SpaceR论文解读
  • Codeforces Round 1042 (Div. 3)
  • Ansys FreeFlow入门:对搅拌罐进行建模
  • vector 认识及使用
  • 【论文阅读-Part1】PIKE-RAG: sPecIalized KnowledgE and Rationale Augmented Generation
  • 如何通过WiFi将文件从安卓设备传输到电脑
  • Scrapy 基础框架搭建教程:从环境配置到爬虫实现(附实例)
  • Pytorch在FSDP模型中使用EMA
  • 考研408《计算机组成原理》复习笔记,第四章(3)——指令集、汇编语言
  • 14、C 语言联合体和枚举知识点总结
  • Linux系统Namespace隔离实战:dd/mkfs/mount/unshare命令组合应用
  • 报数游戏(我将每文更新tips)
  • 2022 年全国硕士研究生招生考试真题笔记
  • 杂记 01
  • elasticsearch基础概念与集群部署
  • Blender模拟结构光3D Scanner(一)外参数匹配
  • ARM芯片架构之CoreSight Channel Interface 介绍
  • 20250813测试开发岗(凉)面
  • Spring Security 前后端分离场景下的会话并发管理
  • 商品分类拖拽排序设计
  • 数据结构:队列(Queue)与循环队列(Circular Queue)
  • 【SpringBoot系列-01】Spring Boot 启动原理深度解析
  • 【OpenGL】LearnOpenGL学习笔记07 - 摄像机
  • 《设计模式之禅》笔记摘录 - 15.观察者模式
  • 分布式与微服务宝典