java - 使用 Spring OAuth2 资源服务器和对称 key 时如何避免 KeyLengthException

标签 java spring-boot spring-security oauth-2.0

所以我正在开发一个资源服务器(一个 Spring Boot 应用程序),我想利用 Spring Security OAuth2 资源服务器库的优点。

我现在面临的问题是授权服务器(另一个 Spring Boot 应用程序)使用对称 key 对 JWT 进行签名,很久以前它被设置为一个非常短的字符串,而我无法更改。

我按照 Spring Security 文档尝试了这个:

@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests(authorize -> authorize
                        .anyRequest().permitAll())
                .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
    }
    
    @Bean
    public JwtDecoder jwtDecoder(@Value("${jwt.secret-key}") String secretKey) {
        return NimbusJwtDecoder
                .withSecretKey(new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), "HS512"))
                .macAlgorithm(MacAlgorithm.HS512)
                .build();
    }
}

但是我得到以下错误:

Caused by: com.nimbusds.jose.KeyLengthException: The secret length must be at least 256 bits
at com.nimbusds.jose.crypto.impl.MACProvider.<init>(MACProvider.java:118) ~[nimbus-jose-jwt-9.10.1.jar:9.10.1]
at com.nimbusds.jose.crypto.MACVerifier.<init>(MACVerifier.java:168) ~[nimbus-jose-jwt-9.10.1.jar:9.10.1]
at com.nimbusds.jose.crypto.MACVerifier.<init>(MACVerifier.java:81) ~[nimbus-jose-jwt-9.10.1.jar:9.10.1]
at com.nimbusds.jose.crypto.MACVerifier.<init>(MACVerifier.java:113) ~[nimbus-jose-jwt-9.10.1.jar:9.10.1]
at com.nimbusds.jose.crypto.factories.DefaultJWSVerifierFactory.createJWSVerifier(DefaultJWSVerifierFactory.java:100) ~[nimbus-jose-jwt-9.10.1.jar:9.10.1]
at com.nimbusds.jwt.proc.DefaultJWTProcessor.process(DefaultJWTProcessor.java:364) ~[nimbus-jose-jwt-9.10.1.jar:9.10.1]
at com.nimbusds.jwt.proc.DefaultJWTProcessor.process(DefaultJWTProcessor.java:303) ~[nimbus-jose-jwt-9.10.1.jar:9.10.1]
at org.springframework.security.oauth2.jwt.NimbusJwtDecoder.createJwt(NimbusJwtDecoder.java:154) ~[spring-security-oauth2-jose-5.5.3.jar:5.5.3]
... 61 common frames omitted

据我所知,生成此异常的 MACProvider 不可配置,并且所需的 key 长度不能放宽。有什么办法解决这个问题吗?

谢谢

编辑:

尝试了用 0 填充键的建议,如下所示:

@Bean
public JwtDecoder jwtDecoder(@Value("${jwt.secret-key}") String secretKey) {
    var key = secretKey.getBytes(StandardCharsets.UTF_8);
    var paddedKey = Arrays.copyOf(key, 128);
    return NimbusJwtDecoder
            .withSecretKey(new SecretKeySpec(paddedKey, "HS512"))
            .macAlgorithm(MacAlgorithm.HS512)
            .build();
}

但现在我得到以下异常:

com.nimbusds.jose.proc.BadJWSException: Signed JWT rejected: Invalid signature
    at com.nimbusds.jwt.proc.DefaultJWTProcessor.process(DefaultJWTProcessor.java:378) ~[nimbus-jose-jwt-9.10.1.jar:9.10.1]
    at com.nimbusds.jwt.proc.DefaultJWTProcessor.process(DefaultJWTProcessor.java:303) ~[nimbus-jose-jwt-9.10.1.jar:9.10.1]
    at org.springframework.security.oauth2.jwt.NimbusJwtDecoder.createJwt(NimbusJwtDecoder.java:154) ~[spring-security-oauth2-jose-5.5.3.jar:5.5.3]
    at org.springframework.security.oauth2.jwt.NimbusJwtDecoder.decode(NimbusJwtDecoder.java:137) ~[spring-security-oauth2-jose-5.5.3.jar:5.5.3]
    at org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider.getJwt(JwtAuthenticationProvider.java:97) ~[spring-security-oauth2-resource-server-5.5.3.jar:5.5.3]
    at org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider.authenticate(JwtAuthenticationProvider.java:88) ~[spring-security-oauth2-resource-server-5.5.3.jar:5.5.3]
    at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:182) ~[spring-security-core-5.5.3.jar:5.5.3]
    at org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationFilter.doFilterInternal(BearerTokenAuthenticationFilter.java:130) ~[spring-security-oauth2-resource-server-5.5.3.jar:5.5.3]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.12.jar:5.3.12]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.5.3.jar:5.5.3]
    at org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter.doFilterInternal(OAuth2AuthorizationRequestRedirectFilter.java:178) ~[spring-security-oauth2-client-5.5.3.jar:5.5.3]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.12.jar:5.3.12]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.5.3.jar:5.5.3]
    at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:103) ~[spring-security-web-5.5.3.jar:5.5.3]
    at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:89) ~[spring-security-web-5.5.3.jar:5.5.3]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.5.3.jar:5.5.3]
    at org.springframework.security.web.csrf.CsrfFilter.doFilterInternal(CsrfFilter.java:117) ~[spring-security-web-5.5.3.jar:5.5.3]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.12.jar:5.3.12]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.5.3.jar:5.5.3]
    at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90) ~[spring-security-web-5.5.3.jar:5.5.3]
    at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75) ~[spring-security-web-5.5.3.jar:5.5.3]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.12.jar:5.3.12]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.5.3.jar:5.5.3]
    at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:110) ~[spring-security-web-5.5.3.jar:5.5.3]
    at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:80) ~[spring-security-web-5.5.3.jar:5.5.3]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.5.3.jar:5.5.3]
    at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:55) ~[spring-security-web-5.5.3.jar:5.5.3]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.12.jar:5.3.12]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.5.3.jar:5.5.3]
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:211) ~[spring-security-web-5.5.3.jar:5.5.3]
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:183) ~[spring-security-web-5.5.3.jar:5.5.3]
    at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:358) ~[spring-web-5.3.12.jar:5.3.12]
    at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:271) ~[spring-web-5.3.12.jar:5.3.12]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.3.12.jar:5.3.12]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.12.jar:5.3.12]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
    at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-5.3.12.jar:5.3.12]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.12.jar:5.3.12]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
    at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:96) ~[spring-boot-actuator-2.5.6.jar:2.5.6]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.12.jar:5.3.12]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.3.12.jar:5.3.12]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.12.jar:5.3.12]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:540) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:382) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:895) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1722) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
    at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
    at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
    at java.base/java.lang.Thread.run(Thread.java:834) ~[na:na]

编辑2:

源代码像这样生成 JWT(使用 io.jsonwebtoken 0.9.1 库):

private String generateToken(Map<String, Object> claims, String username) {

    Header header = Jwts.header();
    header.setType("JWT");

    String jti = UUID.randomUUID().toString();
    Date now = new Date(System.currentTimeMillis());

    return Jwts.builder()
            .setClaims(claims)
            .setHeader((Map<String, Object>) header)
            .setSubject(username)
            .setIssuedAt(now)
            .setIssuer("issuer")
            .setId(jti)
            .signWith(SignatureAlgorithm.HS512, secret)
            .compact();
}

编辑3:

解决方法:

@Bean
public JwtDecoder jwtDecoder(@Value("${jwt.secret-key}") String secretKey) {
    var key = TextCodec.BASE64.decode(secretKey);
    var paddedKey = key.length < 128 ? Arrays.copyOf(key, 128) : key;
    return NimbusJwtDecoder
            .withSecretKey(new SecretKeySpec(paddedKey, "HS512"))
            .macAlgorithm(MacAlgorithm.HS512)
            .build();
}

最佳答案

看起来像,HMAC ,如果 secret 长度短于哈希算法的 block 大小,它将用零填充 secret 。

并根据this

Block size: the size of the data block the underlying hash algorithm operates upon. For SHA-256, this is 512 bits, for SHA-384 and SHA-512, this is 1024 bits.

Output length: the size of the hash value produced by the underlying hash algorithm. For SHA-256, this is 256 bits, for SHA-384 this is 384 bits, and for SHA-512, this is 512 bits.

SHA-512 的 block 大小为 128 字节。

我建议,如果源使用 HS512 算法,请尝试用零填充 secret 以查看它是否有效。如果你的类路径中有 Guava 库:

Bytes.ensureCapacity(secretKey.getBytes(StandardCharsets.UTF_8), 128, 0);

ensureCapacity方法源码:

  public static byte[] ensureCapacity(byte[] array, int minLength, int padding) {
    Preconditions.checkArgument(minLength >= 0, "Invalid minLength: %s", minLength);
    Preconditions.checkArgument(padding >= 0, "Invalid padding: %s", padding);
    return array.length < minLength ? Arrays.copyOf(array, minLength + padding) : array;
  }

编辑 2:

首先尝试将 secret 解码为 base 64

byte[] decodedBytes = Base64.decodeBase64(secret)

然后添加填充并在解码器中使用它(如果 decodedBytes 的大小小于 128):

Arrays.copyOf(decodedBytes, 128);

我做了以下测试,一切正常:

  private String generateToken(Map<String, Object> claims, String username) {

    Header header = Jwts.header();
    header.setType("JWT");

    String jti = UUID.randomUUID().toString();
    Date now = new Date(System.currentTimeMillis());

    return Jwts.builder()
        .setClaims(claims)
        .setHeader((Map<String, Object>) header)
        .setSubject(username)
        .setIssuedAt(now)
        .setIssuer("issuer")
        .setId(jti)
        .signWith(SignatureAlgorithm.HS512, "asdf")
        .compact();
  }

  public JwtDecoder jwtDecoder() {
  // base64 decoder from org.apache.tomcat.util.codec.binary.Base64;
    byte[] key = Base64.decodeBase64("asdf");
   // var key = "asdf".getBytes(StandardCharsets.UTF_8);
    var paddedKey = Arrays.copyOf(key, 128);
    return NimbusJwtDecoder
        .withSecretKey(new SecretKeySpec(paddedKey, "HS512"))
        .macAlgorithm(MacAlgorithm.HS512)
        .build();
  }

并像这样使用它:

Main s = new Main();
String token = s.generateToken(new HashMap<>(), "hatef");
JwtDecoder decoder = s.jwtDecoder();
System.out.println(decoder.decode(token));

编辑 3:

OP 在评论中报告:

it worked with io.jsonwebtoken.impl.TextCodec.BASE64.decode(secretKey) instead of org.apache.tomcat.util.codec.binary.Base64.decodeBase64(secretKey), with the latter i still had a com.nimbusds.jose.proc.BadJWSException

关于java - 使用 Spring OAuth2 资源服务器和对称 key 时如何避免 KeyLengthException,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69829603/

相关文章:

java - 哪个是生成 Web 服务客户端的最佳 Maven 插件?

java - org.hibernate.loader.MultipleBagFetchException : cannot simultaneously fetch multiple bags

spring-boot - 无法反序列化 ExecutionContext : the class with [. .. Class] 并且 [... Class] 的名称不受信任

在所有级别的身份验证中使用 X509 的 Spring Security

grails - 在我的 gsp 中,<sec :username/> returns empty value

rest - 防止重播 REST url 的重播攻击

java - Mockito.verify 方法包含 boolean 值和参数捕获器

java - BlazeDS StreamingAMF : How to detect when flex client closes the connection?

jpa - Spring Boot 和 FlywayTest 导致 JPA Camel 路由在数据库重置期间抛出异常

java - fragment .java 错误 : No suitable method in found and cannot resolve method 'inflate(int, boolean)'