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

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.lryc.cn/news/620698.html

相关文章:

  • Python训练营打卡Day32-神经网络的训练
  • 【Golang】Golang内存泄漏问题排查(二)
  • OpenCv(三)——图像平滑处理
  • 8. 函数简介
  • OpenCV中对图像进行平滑处理的4种方式
  • HarmonyOS AI辅助编程工具(CodeGenie)智慧调优
  • 力扣(LeetCode) ——225 用队列实现栈(C语言)
  • 信息vs知识:人类学习与AI规则提取
  • 异步编程的 8 种实现方式:疑难点与注意事项解析
  • 《疯狂Java讲义(第3版)》学习笔记ch4
  • 安全加固4(K8S最小化微服务安全)
  • C++ 中的元控制流与概念化类型擦除
  • Elasticsearch 中如何配置 RBAC 权限-实现安全的访问控制
  • 论郑和下西洋元素融入课件编辑器的意义与影响​
  • 智能门锁:安全与便捷的现代家居入口
  • UE小:编辑器模式下「窗口/鼠标不在焦点」时仍保持高帧率
  • UE5配置MRQ编解码器输出MP4视频
  • Mybatis学习笔记(三)
  • PostgreSQL 免安装
  • AXI GPIO 2——ZYNQ学习笔记
  • 相较于传统AR作战环境虚拟仿真系统,其优势体现在哪些方面?
  • Mysql基本使用语句(一)
  • 生成和发布博客的工作流
  • 力扣(串联所有单词的子串)
  • ChatECNU 边缘 AI 智能体对话
  • 在线进销存系统高效管理网站源码搭建可二开
  • 倾斜按钮(径向渐变详细介绍)
  • MCU中的LTDC(LCD-TFT Display Controller)
  • 项目日志框架与jar中日志框架冲突 解决
  • 20. 了解过尾递归优化吗