You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot中如何获取自动生成的JWT验证密钥?

问题

我正在开发一个简单的Spring Boot应用,已经实现了登录方法,能生成由密钥签名的JWT。这个密钥是在SecurityConfig类中每次启动应用时自动生成的。现在我需要从请求中获取令牌,验证用户角色(通过包含3种用户类型的枚举实现),判断用户是否有权限执行请求。

我见过有人把密钥写入application.properties并通过@Value注解获取,但由于我的密钥是自动生成的,没法存入该文件。我知道存配置文件不安全,但这只是学习用的项目,不需要高度安全。请问该如何注入/获取密钥来验证JWT?


现有代码片段

SecurityConfig类中的密钥生成与过滤器配置

@Bean
public SecretKey secretKey() {
    SecretKey secretKey = Keys.secretKeyFor(SignatureAlgorithm.HS256);
    byte[] encodedKey = secretKey.getEncoded();
    String encodedKeyBase64 = Base64.getEncoder().encodeToString(encodedKey);

    System.out.println("Secret Key (Base64): " + encodedKeyBase64);

    return secretKey;
}

@Bean
public JwtAuthFilter jwtAuth() {
    return new JwtAuthFilter(secretKey());
}

EdificioController中的JWT验证代码(待完善)

@RestController
@RequestMapping(path ="tpo_apis/edificios")
public class EdificioController {

    @Autowired
    private EdificioService edificioService;
    @Value("${jwt.secret}")
    private SecretKey secret;

    @PostMapping(path ="/")
    public ResponseEntity<?> createEdificio(@RequestBody EdificioModel edificio, @RequestHeader(name = "Authorization") String token) throws Exception {
        Claims claims = Jwts.parser()
                .verifyWith(secret) // 这里需要用到生成JWT的密钥
                .build()
                .parseSignedClaims(token.replace("Bearer ", ""))
                .getPayload();

        System.out.println(claims.getSubject());
        return new ResponseEntity<>(edificioService.createEdificio(edificio), HttpStatus.CREATED);
    }
}

登录接口的JWT生成代码

@PostMapping("/login")
public ResponseEntity<String> login(@RequestBody UsuarioModelDTO credentials) {
    // 验证凭证(可使用Spring Security或其他机制)
    UsuarioModel usuario = this.usuarioService.findUsuario(credentials.getUsuario(), credentials.getPassword());
    if (usuario != null) {
        // 创建JWT令牌
        String token = Jwts.builder()
                .subject(credentials.getUsuario()).issuedAt(new Date())
                .claim("rol", usuario.getTipoUsuario())
                .issuedAt(new Date())
                .expiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME_IN_MIN * 60 * 1000))
                .signWith(secretKey, SignatureAlgorithm.HS256).compact();
        return new ResponseEntity<>(token, HttpStatus.OK);
    }
    // 补充凭证验证失败的返回逻辑
    return new ResponseEntity<>("登录失败", HttpStatus.UNAUTHORIZED);
}

解决方案

你已经把SecretKey注册为Spring的Bean,直接通过依赖注入获取即可,不需要用@Value读取配置文件,以下是几种可行的方式:

方式1:控制器中通过@Autowired注入SecretKey

修改EdificioController的代码,替换掉无效的@Value注解:

@RestController
@RequestMapping(path ="tpo_apis/edificios")
public class EdificioController {

    @Autowired
    private EdificioService edificioService;
    @Autowired // 直接注入Spring容器中的SecretKey Bean
    private SecretKey secret;

    @PostMapping(path ="/")
    public ResponseEntity<?> createEdificio(@RequestBody EdificioModel edificio, @RequestHeader(name = "Authorization") String token) throws Exception {
        Claims claims = Jwts.parser()
                .verifyWith(secret)
                .build()
                .parseSignedClaims(token.replace("Bearer ", ""))
                .getPayload();

        // 验证用户角色:从claims中取出rol枚举值判断权限
        TipoUsuario rol = (TipoUsuario) claims.get("rol");
        if (!TipoUsuario.ADMIN.equals(rol)) { // 假设仅ADMIN拥有创建权限
            return new ResponseEntity<>("无权限执行此操作", HttpStatus.FORBIDDEN);
        }

        System.out.println(claims.getSubject());
        return new ResponseEntity<>(edificioService.createEdificio(edificio), HttpStatus.CREATED);
    }
}

方式2:构造函数注入(推荐,符合Spring最佳实践)

构造函数注入比字段注入更安全,也更便于单元测试:

@RestController
@RequestMapping(path ="tpo_apis/edificios")
public class EdificioController {

    private final EdificioService edificioService;
    private final SecretKey secret;

    // 构造函数注入依赖
    public EdificioController(EdificioService edificioService, SecretKey secret) {
        this.edificioService = edificioService;
        this.secret = secret;
    }

    @PostMapping(path ="/")
    public ResponseEntity<?> createEdificio(@RequestBody EdificioModel edificio, @RequestHeader(name = "Authorization") String token) throws Exception {
        // JWT解析与角色验证逻辑同方式1
        Claims claims = Jwts.parser()
                .verifyWith(secret)
                .build()
                .parseSignedClaims(token.replace("Bearer ", ""))
                .getPayload();

        TipoUsuario rol = (TipoUsuario) claims.get("rol");
        if (!TipoUsuario.ADMIN.equals(rol)) {
            return new ResponseEntity<>("无权限执行此操作", HttpStatus.FORBIDDEN);
        }

        System.out.println(claims.getSubject());
        return new ResponseEntity<>(edificioService.createEdificio(edificio), HttpStatus.CREATED);
    }
}

额外建议:统一JWT验证逻辑到过滤器

你已经实现了JwtAuthFilter,更规范的做法是把JWT解析、签名验证、角色判断都放在过滤器中,避免在控制器中重复写验证代码:

  1. 修改JwtAuthFilter,在过滤器中完成JWT验证并注入用户信息:
public class JwtAuthFilter extends OncePerRequestFilter {

    private final SecretKey secretKey;

    public JwtAuthFilter(SecretKey secretKey) {
        this.secretKey = secretKey;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String authHeader = request.getHeader("Authorization");
        if (authHeader == null || !authHeader.startsWith("Bearer ")) {
            filterChain.doFilter(request, response);
            return;
        }

        String token = authHeader.replace("Bearer ", "");
        try {
            Claims claims = Jwts.parser()
                    .verifyWith(secretKey)
                    .build()
                    .parseSignedClaims(token)
                    .getPayload();

            String username = claims.getSubject();
            TipoUsuario rol = (TipoUsuario) claims.get("rol");

            // 将用户权限信息存入SecurityContext,供后续权限校验使用
            Authentication authentication = new UsernamePasswordAuthenticationToken(
                    username, null, Collections.singletonList(new SimpleGrantedAuthority(rol.name()))
            );
            SecurityContextHolder.getContext().setAuthentication(authentication);

        } catch (JwtException e) {
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            response.getWriter().write("无效或过期的JWT令牌");
            return;
        }

        filterChain.doFilter(request, response);
    }
}
  1. 开启Spring方法级安全,用注解控制权限:
    在SecurityConfig类上添加@EnableMethodSecurity注解:
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {
    // 其他配置代码...
}
  1. 简化控制器代码,用@PreAuthorize注解直接控制权限:
@RestController
@RequestMapping(path ="tpo_apis/edificios")
public class EdificioController {

    private final EdificioService edificioService;

    public EdificioController(EdificioService edificioService) {
        this.edificioService = edificioService;
    }

    @PostMapping(path ="/")
    @PreAuthorize("hasAuthority('ADMIN')") // 仅ADMIN角色可访问此接口
    public ResponseEntity<?> createEdificio(@RequestBody EdificioModel edificio) {
        // 直接处理业务逻辑,权限验证由过滤器和注解完成
        return new ResponseEntity<>(edificioService.createEdificio(edificio), HttpStatus.CREATED);
    }
}

这种方式能让权限验证逻辑集中管理,代码更简洁,也符合Spring Security的设计规范。


内容的提问来源于stack exchange,提问作者ramalandajo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 20:38:13