使用 Key 和 SecretKey 签发 JWT Token

1、概览

JSON Web Tokens(JWT)是用于保护无状态应用的事实上的标准。Spring Security 框架提供了集成 JWT 以保护 REST API 的方法。

本文将带你了解如何在 Spring Boot 应用中创建 SecretKey 实例来签发和验证 JWT。

2、项目设置

2.1、Maven 依赖

首先,在 pom.xml 中添加 spring-boot-starter-webspring-boot-starter-securityspring-boot-starter-data-jpah2 数据库依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>3.2.3</version> 
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId> 
    <version>3.2.3</version> 
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
    <version>3.2.3</version>  
</dependency>
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <version>2.2.224</version>
</dependency>

Spring Boot Starter Web 提供了用于构建 REST API 的 API。Spring Boot Starter Security 用于提供身份认证和授权。h2 是一个内存数据库,方便快速开发。

然后,还要在 pom.xml 中添加 jjwt-apijjwt-impljjwt-jackson 依赖:

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.12.5</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.12.5</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId>
    <version>0.12.5</version>
</dependency>

这些依赖提供了生成和签发 JWT 的 API,并将其整合到 Spring Security 中。

2.2、JWT 配置

首先,创建一个身份认证入口点:

@Component
class AuthEntryPointJwt implements AuthenticationEntryPoint {
    // ...
}

这里,创建了一个类来处理 Spring Security 应用中使用 JWT 身份认证的授权访问尝试。它充当门卫,确保只有拥有有效访问权限的用户才能访问受保护的资源。

然后,创建一个 AuthTokenFilter 类,它可以拦截传入的请求,验证 JWT Token,并在 Token 有效情况下对用户进行身份认证:

class AuthTokenFilter extends OncePerRequestFilter {
    // ...
}

最后,创建 JwtUtil 类,它提供了创建和验证 Token 的方法:

@Component
class JwtUtils {
    // ... 
}

该类包含使用 signWith() 方法的逻辑。

2.3、Security Configuration

最后,定义 SecurityConfiguration 类并整合 JWT:

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
class SecurityConfiguration {
    // ...

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.csrf(AbstractHttpConfigurer::disable)
          .cors(AbstractHttpConfigurer::disable)
          .authorizeHttpRequests(req -> req.requestMatchers(WHITE_LIST_URL)
            .permitAll()
            .anyRequest()
            .authenticated())
          .exceptionHandling(ex -> ex.authenticationEntryPoint(unauthorizedHandler))
          .sessionManagement(session -> session.sessionCreationPolicy(STATELESS))
          .authenticationProvider(authenticationProvider())
          .addFilterBefore(
              authenticationJwtTokenFilter(), 
              UsernamePasswordAuthenticationFilter.class
           );

        return http.build();
    }

    // ...
}

上面的代码中,整合了 JWT 入口和 Filter ,以激活 JWT 身份认证。

3、signWith() 方法

JJWT 提供了一个 signWith() 方法,使用特定加密算法和秘钥签发 JWT。这一签名过程对于确保 JWT 的完整性和真实性至关重要。

signWith() 方法接受 KeySecretKey 实例和签名算法作为参数。基于哈希的消息认证码(HMAC)算法 是最常用的签名算法之一。

该方法需要一个 Secret Key,通常是一个字节数组,用于签名过程。我们可以使用 KeySecretKey 实例将一个 Secret 字符串转换为 Secret Key。

我们可以传递一个普通字符串作为 Secret Key。然而,这种方式缺乏安全性保证和随机性。

使用 SecretKey 实例可确保 JWT 的完整性和真实性。

4、签发 JWT

我们可以使用 KeySecretKey 实例创建一个强大的 Secret Key 来签发 JWT。

4.1、使用 Key 实例

我们可以将一个 Secret String 转换为 Key 实例,在使用它对 JWT 进行签名之前进一步加密。

首先,确保 Secret String 是 Base64 编码的:

private String jwtSecret = "4261656C64756E67";

然后,创建 Key 对象:

private Key getSigningKey() {
    byte[] keyBytes = Decoders.BASE64.decode(this.jwtSecret);
    return Keys.hmacShaKeyFor(keyBytes);
}

在上面的代码中,我们将 jwtSecret 解码为字节数组,然后调用 hmacShaKeyFor(),它接受 keyBytes 作为 Keys 实例的参数。这会根据 HMAC 算法生成 Secret Key。

如果 Secret Key 没有进行 Base64 编码,我们可以在纯字符串上调用 getByte() 方法:

private Key getSigningKey() {
    byte[] keyBytes = this.jwtSecret.getBytes(StandardCharsets.UTF_8);
    return Keys.hmacShaKeyFor(keyBytes);
}

然而,并不建议这样做,因为密钥的格式可能不佳,而且字符串可能包含非 UTF-8 字符。因此,必须确保密钥字符串是 Base64 编码的,然后才能生成 Secret Key。

4.2、使用 SecretKey 实例

此外,还可以使用 HMAC-SHA 算法创建 SecretKey 实例,从而生成一个强大的 Secret Key。

创建一个能返回 Secret Key 的 SecretKey 实例:

SecretKey getSigningKey() {
    return Jwts.SIG.HS256.key().build();
}

在这里,我们直接使用 HMAC-SHA 算法,而不使用字节数组。这就生成了一个强大的签名密钥。接下来,可以通过传递 getSigningKey() 作为参数来更新 signWith() 方法。

或者,也可以通过 BASE64 编码字符串创建 SecretKey 实例:

SecretKey getSigningKey() {
    byte[] keyBytes = Decoders.BASE64.decode(jwtSecret);
    return Keys.hmacShaKeyFor(keyBytes);
}

使用 SecretKey 实例比使用 Key 实例更明智,因为用于验证 Token 的名为 verifyWith() 的新方法接受 SecretKey 作为参数。

4.3、应用 Key

现在,使用 Secret Key 来签发应用的 JWT:

String generateJwtToken(Authentication authentication) {
    UserDetailsImpl userPrincipal = (UserDetailsImpl) authentication.getPrincipal();

    return Jwts.builder()
      .subject((userPrincipal.getUsername()))
      .issuedAt(new Date())
      .expiration(new Date((new Date()).getTime() + jwtExpirationMs))
      .signWith(key)
      .compact();
}

signWith() 方法将 SecretKey 实例作为参数,用于向令牌添加一个唯一的签名。

5、总结

本文介绍了如何在 Spring Boot 应用中使用 Java KeySecretKey 实例创建 Secret Key 来签发 JWT Token,以及这样做的好处是什么。


Ref:https://www.baeldung.com/spring-security-sign-jwt-token