JWTEncoder:无法选择 JWK 签名密钥

问题描述 投票:0回答:2

我在一个应用程序中有一个身份验证服务器+资源服务器。我花了很多时间搜索和调试,但与此相关的 Spring Boot 3.+ 的更新页面或主题并不多。因此,我完成了这项工作,并希望添加一个将在我的客户端和服务器之间共享的自定义秘密。这就是问题开始的地方......

这是我的身份验证+资源服务器配置:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

@Value("${security.jwt.secret}")
private String jwtSecret;

@Bean
SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http,
                                                           CorsConfigurationSource corsConfigurationSource) throws Exception {
    OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
    http.getConfigurer(OAuth2AuthorizationServerConfigurer.class).oidc(Customizer.withDefaults());

    http.exceptionHandling((exceptions) -> exceptions.defaultAuthenticationEntryPointFor(
                    new LoginUrlAuthenticationEntryPoint("/login"), new MediaTypeRequestMatcher(MediaType.TEXT_HTML)))
            .oauth2ResourceServer((resourceServer) -> resourceServer.jwt(Customizer.withDefaults()));

    http.cors(customizer -> customizer.configurationSource(corsConfigurationSource));
    return http.build();
}

@Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
    http.authorizeHttpRequests(
                    authorize -> authorize.requestMatchers("/oauth2/authorize").permitAll().anyRequest().authenticated())
            .formLogin(formLogin -> formLogin.loginPage("/login").permitAll())
            .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()));
    http.csrf(csrf -> csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()));
    return http.build();
}

@Bean
PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}

@Bean
public JwtEncoder jwtEncoder() {
    byte[] keyBytes = Base64.getDecoder().decode(jwtSecret);
    SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, "HmacSHA256");
    OctetSequenceKey octetKey = new OctetSequenceKey.Builder(secretKeySpec)
            .keyID("customKey")
            .build();
    JWKSet jwkSet = new JWKSet(octetKey);
    JWKSource<SecurityContext> jwkSource = (jwkSelector, context) -> {
        List<JWK> keys = jwkSelector.select(jwkSet);
        if (keys.isEmpty()) {
            System.out.println("No keys found matching selection criteria!");
        } else {
            System.out.println("Keys selected: " + keys.stream().map(JWK::getKeyID).collect(Collectors.joining(", ")));
        }
        return keys;
    };

    return new NimbusJwtEncoder(jwkSource);
}

@Bean
JwtDecoder jwtDecoder() {
    byte[] keyBytes = Base64.getDecoder().decode(jwtSecret);
    SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, "HmacSHA256");
    return NimbusJwtDecoder.withSecretKey(secretKeySpec).build();
}
}

我的 app.properties 中有:

security.jwt.secret=r26BoWWyTQMp/8rkD3RnRKsbHkRsmQWjTvJTfmhrQxU=

我的一切都以非对称方式(私钥和公钥)工作,但我也想尝试这个......

现在,当使用客户端登录时,我总是收到:

org.springframework.security.oauth2.jwt.JwtEncodingException:尝试对 Jwt 进行编码时发生错误:无法选择 JWK 签名密钥

  1. 我在服务器中缺少什么?
java spring-boot spring-security oauth-2.0 spring-oauth2
2个回答
1
投票

我会尝试的几件事:

我会确保

JWKSelector
使用的
NimbusJwtEncoder
与您期望的标准完全匹配,否则选择器可能会查找您未定义的特定属性(例如 use 或 alg)。

我还会添加异常或登录错误以帮助调试过程 对于 JWKSource

最后你可以在测试前先尝试siplyfy一下配置,看看是否有异常,像这样:

/* rest of code */
@Bean
public JwtEncoder jwtEncoder() {
    String jwtSecret = "your-secret-key"; // non-Base64 encoded secret for testing
    SecretKeySpec secretKeySpec = new SecretKeySpec(jwtSecret.getBytes(), "HmacSHA256");
    return new NimbusJwtEncoder(secretKeySpec);
}
/* rest of code */

如果此配置工作正常,没有错误,问题可能出在自定义 JWK 选择逻辑中,在此之后发布您的发现,我们从那里开始


0
投票

我已经解决了这个问题:

@Slf4j
@Configuration
@RequiredArgsConstructor
@EnableWebSecurity
public class SecurityConfig {

@Value("${jwt.key}")
private String jwtKey;

private final TokenService tokenService;

@Bean
SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http,
                                                           CorsConfigurationSource corsConfigurationSource) throws Exception {
    OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
    http.getConfigurer(OAuth2AuthorizationServerConfigurer.class).oidc(Customizer.withDefaults());

    http.exceptionHandling((exceptions) -> exceptions.defaultAuthenticationEntryPointFor(
                    new LoginUrlAuthenticationEntryPoint("/login"), new MediaTypeRequestMatcher(MediaType.TEXT_HTML)))
            .oauth2ResourceServer((resourceServer) -> resourceServer.jwt(jwtSpec -> {
                jwtSpec.decoder(jwtDecoder());
            }));

    http.cors(customizer -> customizer.configurationSource(corsConfigurationSource));
    return http.build();

}

@Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
    http
            .authorizeHttpRequests(authz -> authz
                    .requestMatchers("/hello").authenticated()
                    .anyRequest().permitAll())
            .oauth2ResourceServer(oauth2 -> oauth2
                    .jwt(jwt -> jwt.decoder(jwtDecoder())))
            .formLogin(Customizer.withDefaults());

    return http.build();
}

@Bean
AuthorizationServerSettings authorizationServerSettings() {
    return AuthorizationServerSettings.builder().build();
}

@Bean
WebSecurityCustomizer webSecurityCustomizer() {
    return (web) -> web.ignoring().requestMatchers(new AntPathRequestMatcher("/h2-console/**"));
}

@Bean
PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}

@Bean
OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer() {
    return tokenService.jwtCustomizer();
}

@Bean
public JwtEncoder jwtEncoder() {
    return tokenService.jwtEncoder();
}

@Bean
public JwtDecoder jwtDecoder() {
    byte[] keyBytes = Base64.getDecoder().decode(jwtKey);
    SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "HmacSHA256");
    return NimbusJwtDecoder.withSecretKey(keySpec).build();
}
}

还有 TokenService 类:

@Service
public class TokenService {

@Value("${jwt.key}")
private String jwtKey;

public OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer() {
    return context -> {
        if (OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType())) {
            context.getJwsHeader().algorithm(MacAlgorithm.HS256);
            Date expirationDate = 
Date.from(Instant.now().plus(Duration.ofHours(5)));
            Date issueDate = Date.from(Instant.now());
            context.getClaims().claims(claims -> {
                claims.put("exp", expirationDate);
                claims.put("iat", issueDate);
                claims.put("custom", "custom");
            });
        }
    };
}

public JwtEncoder jwtEncoder() {
    return parameters -> {
        byte[] secretKeyBytes = Base64.getDecoder().decode(jwtKey);
        SecretKeySpec secretKeySpec = new SecretKeySpec(secretKeyBytes, "HmacSHA256");

        try {
            MACSigner signer = new MACSigner(secretKeySpec);

            JWTClaimsSet.Builder claimsSetBuilder = new JWTClaimsSet.Builder();
            parameters.getClaims().getClaims().forEach((key, value) ->
                    claimsSetBuilder.claim(key, value instanceof Instant ? Date.from((Instant) value) : value)
            );
            JWTClaimsSet claimsSet = claimsSetBuilder.build();

            JWSHeader header = new JWSHeader(JWSAlgorithm.HS256);

            SignedJWT signedJWT = new SignedJWT(header, claimsSet);
            signedJWT.sign(signer);

            return Jwt.withTokenValue(signedJWT.serialize())
                    .header("alg", header.getAlgorithm().getName())
                    .subject(claimsSet.getSubject())
                    .issuer(claimsSet.getIssuer())
                    .claims(claims -> claims.putAll(claimsSet.getClaims()))
                    .issuedAt(claimsSet.getIssueTime().toInstant())
                    .expiresAt(claimsSet.getExpirationTime().toInstant())
                    .build();
        } catch (Exception e) {
            throw new IllegalStateException("Error while signing the JWT", e);
        }
    };
}
}
© www.soinside.com 2019 - 2024. All rights reserved.