mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-05-31 01:02:14 +00:00
Remap Nimbus JSON Parsing Errors
When Nimbus fails to parse either a JWK response or a JWT response, the error message contains information that either should or cannot be included in a Bearer Token response. For example, if the response from a JWK endpoint is invalid JSON, then Nimbus will send the entire response from the authentication server in the resulting exception message. This commit captures these exceptions and removes the parsing detail, replacing it with more generic information about the nature of the error. Fixes: gh-5517
This commit is contained in:
parent
28afb4e3d7
commit
6e67c0dcea
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -15,7 +15,15 @@
|
||||
*/
|
||||
package org.springframework.security.oauth2.jwt;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.text.ParseException;
|
||||
import java.time.Instant;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.nimbusds.jose.JWSAlgorithm;
|
||||
import com.nimbusds.jose.RemoteKeySourceException;
|
||||
import com.nimbusds.jose.jwk.source.JWKSource;
|
||||
import com.nimbusds.jose.jwk.source.RemoteJWKSet;
|
||||
import com.nimbusds.jose.proc.JWSKeySelector;
|
||||
@ -29,15 +37,10 @@ import com.nimbusds.jwt.JWTParser;
|
||||
import com.nimbusds.jwt.SignedJWT;
|
||||
import com.nimbusds.jwt.proc.ConfigurableJWTProcessor;
|
||||
import com.nimbusds.jwt.proc.DefaultJWTProcessor;
|
||||
|
||||
import org.springframework.security.oauth2.jose.jws.JwsAlgorithms;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.time.Instant;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* An implementation of a {@link JwtDecoder} that "decodes" a
|
||||
* JSON Web Token (JWT) and additionally verifies it's digital signature if the JWT is a
|
||||
@ -48,6 +51,7 @@ import java.util.Map;
|
||||
* <b>NOTE:</b> This implementation uses the Nimbus JOSE + JWT SDK internally.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @author Josh Cummings
|
||||
* @since 5.0
|
||||
* @see JwtDecoder
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7519">JSON Web Token (JWT)</a>
|
||||
@ -56,6 +60,9 @@ import java.util.Map;
|
||||
* @see <a target="_blank" href="https://connect2id.com/products/nimbus-jose-jwt">Nimbus JOSE + JWT SDK</a>
|
||||
*/
|
||||
public final class NimbusJwtDecoderJwkSupport implements JwtDecoder {
|
||||
private static final String DECODING_ERROR_MESSAGE_TEMPLATE =
|
||||
"An error occurred while attempting to decode the Jwt: %s";
|
||||
|
||||
private final URL jwkSetUrl;
|
||||
private final JWSAlgorithm jwsAlgorithm;
|
||||
private final ConfigurableJWTProcessor<SecurityContext> jwtProcessor;
|
||||
@ -108,7 +115,7 @@ public final class NimbusJwtDecoderJwkSupport implements JwtDecoder {
|
||||
try {
|
||||
return JWTParser.parse(token);
|
||||
} catch (Exception ex) {
|
||||
throw new JwtException("An error occurred while attempting to decode the Jwt: " + ex.getMessage(), ex);
|
||||
throw new JwtException(String.format(DECODING_ERROR_MESSAGE_TEMPLATE, ex.getMessage()), ex);
|
||||
}
|
||||
}
|
||||
|
||||
@ -135,8 +142,18 @@ public final class NimbusJwtDecoderJwkSupport implements JwtDecoder {
|
||||
|
||||
jwt = new Jwt(token, issuedAt, expiresAt, headers, jwtClaimsSet.getClaims());
|
||||
|
||||
} catch (RemoteKeySourceException ex) {
|
||||
if (ex.getCause() instanceof ParseException) {
|
||||
throw new JwtException(String.format(DECODING_ERROR_MESSAGE_TEMPLATE, "Malformed Jwk set"));
|
||||
} else {
|
||||
throw new JwtException(String.format(DECODING_ERROR_MESSAGE_TEMPLATE, ex.getMessage()), ex);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
throw new JwtException("An error occurred while attempting to decode the Jwt: " + ex.getMessage(), ex);
|
||||
if (ex.getCause() instanceof ParseException) {
|
||||
throw new JwtException(String.format(DECODING_ERROR_MESSAGE_TEMPLATE, "Malformed payload"));
|
||||
} else {
|
||||
throw new JwtException(String.format(DECODING_ERROR_MESSAGE_TEMPLATE, ex.getMessage()), ex);
|
||||
}
|
||||
}
|
||||
|
||||
return jwt;
|
||||
|
@ -22,10 +22,14 @@ import com.nimbusds.jwt.JWTClaimsSet;
|
||||
import com.nimbusds.jwt.JWTParser;
|
||||
import com.nimbusds.jwt.SignedJWT;
|
||||
import com.nimbusds.jwt.proc.DefaultJWTProcessor;
|
||||
import okhttp3.mockwebserver.MockResponse;
|
||||
import okhttp3.mockwebserver.MockWebServer;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.powermock.core.classloader.annotations.PowerMockIgnore;
|
||||
import org.powermock.core.classloader.annotations.PrepareForTest;
|
||||
import org.powermock.modules.junit4.PowerMockRunner;
|
||||
|
||||
import org.springframework.security.oauth2.jose.jws.JwsAlgorithms;
|
||||
|
||||
import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode;
|
||||
@ -46,11 +50,17 @@ import static org.powermock.api.mockito.PowerMockito.whenNew;
|
||||
*/
|
||||
@RunWith(PowerMockRunner.class)
|
||||
@PrepareForTest({NimbusJwtDecoderJwkSupport.class, JWTParser.class})
|
||||
@PowerMockIgnore("okhttp3.*")
|
||||
public class NimbusJwtDecoderJwkSupportTests {
|
||||
private static final String JWK_SET_URL = "https://provider.com/oauth2/keys";
|
||||
private static final String JWS_ALGORITHM = JwsAlgorithms.RS256;
|
||||
|
||||
private String unsignedToken = "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJleHAiOi0yMDMzMjI0OTcsImp0aSI6IjEyMyIsInR5cCI6IkpXVCJ9.";
|
||||
private static final String JWK_SET = "{\"keys\":[{\"p\":\"49neceJFs8R6n7WamRGy45F5Tv0YM-R2ODK3eSBUSLOSH2tAqjEVKOkLE5fiNA3ygqq15NcKRadB2pTVf-Yb5ZIBuKzko8bzYIkIqYhSh_FAdEEr0vHF5fq_yWSvc6swsOJGqvBEtuqtJY027u-G2gAQasCQdhyejer68zsTn8M\",\"kty\":\"RSA\",\"q\":\"tWR-ysspjZ73B6p2vVRVyHwP3KQWL5KEQcdgcmMOE_P_cPs98vZJfLhxobXVmvzuEWBpRSiqiuyKlQnpstKt94Cy77iO8m8ISfF3C9VyLWXi9HUGAJb99irWABFl3sNDff5K2ODQ8CmuXLYM25OwN3ikbrhEJozlXg_NJFSGD4E\",\"d\":\"FkZHYZlw5KSoqQ1i2RA2kCUygSUOf1OqMt3uomtXuUmqKBm_bY7PCOhmwbvbn4xZYEeHuTR8Xix-0KpHe3NKyWrtRjkq1T_un49_1LLVUhJ0dL-9_x0xRquVjhl_XrsRXaGMEHs8G9pLTvXQ1uST585gxIfmCe0sxPZLvwoic-bXf64UZ9BGRV3lFexWJQqCZp2S21HfoU7wiz6kfLRNi-K4xiVNB1gswm_8o5lRuY7zB9bRARQ3TS2G4eW7p5sxT3CgsGiQD3_wPugU8iDplqAjgJ5ofNJXZezoj0t6JMB_qOpbrmAM1EnomIPebSLW7Ky9SugEd6KMdL5lW6AuAQ\",\"e\":\"AQAB\",\"use\":\"sig\",\"kid\":\"one\",\"qi\":\"wdkFu_tV2V1l_PWUUimG516Zvhqk2SWDw1F7uNDD-Lvrv_WNRIJVzuffZ8WYiPy8VvYQPJUrT2EXL8P0ocqwlaSTuXctrORcbjwgxDQDLsiZE0C23HYzgi0cofbScsJdhcBg7d07LAf7cdJWG0YVl1FkMCsxUlZ2wTwHfKWf-v4\",\"dp\":\"uwnPxqC-IxG4r33-SIT02kZC1IqC4aY7PWq0nePiDEQMQWpjjNH50rlq9EyLzbtdRdIouo-jyQXB01K15-XXJJ60dwrGLYNVqfsTd0eGqD1scYJGHUWG9IDgCsxyEnuG3s0AwbW2UolWVSsU2xMZGb9PurIUZECeD1XDZwMp2s0\",\"dq\":\"hra786AunB8TF35h8PpROzPoE9VJJMuLrc6Esm8eZXMwopf0yhxfN2FEAvUoTpLJu93-UH6DKenCgi16gnQ0_zt1qNNIVoRfg4rw_rjmsxCYHTVL3-RDeC8X_7TsEySxW0EgFTHh-nr6I6CQrAJjPM88T35KHtdFATZ7BCBB8AE\",\"n\":\"oXJ8OyOv_eRnce4akdanR4KYRfnC2zLV4uYNQpcFn6oHL0dj7D6kxQmsXoYgJV8ZVDn71KGmuLvolxsDncc2UrhyMBY6DVQVgMSVYaPCTgW76iYEKGgzTEw5IBRQL9w3SRJWd3VJTZZQjkXef48Ocz06PGF3lhbz4t5UEZtdF4rIe7u-977QwHuh7yRPBQ3sII-cVoOUMgaXB9SHcGF2iZCtPzL_IffDUcfhLQteGebhW8A6eUHgpD5A1PQ-JCw_G7UOzZAjjDjtNM2eqm8j-Ms_gqnm4MiCZ4E-9pDN77CAAPVN7kuX6ejs9KBXpk01z48i9fORYk9u7rAkh1HuQw\"}]}";
|
||||
private static final String MALFORMED_JWK_SET = "malformed";
|
||||
|
||||
private static final String SIGNED_JWT = "eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ0ZXN0LXN1YmplY3QiLCJzY3AiOlsibWVzc2FnZTpyZWFkIl0sImV4cCI6NDY4Mzg5Nzc3Nn0.LtMVtIiRIwSyc3aX35Zl0JVwLTcQZAB3dyBOMHNaHCKUljwMrf20a_gT79LfhjDzE_fUVUmFiAO32W1vFnYpZSVaMDUgeIOIOpxfoe9shj_uYenAwIS-_UxqGVIJiJoXNZh_MK80ShNpvsQwamxWEEOAMBtpWNiVYNDMdfgho9n3o5_Z7Gjy8RLBo1tbDREbO9kTFwGIxm_EYpezmRCRq4w1DdS6UDW321hkwMxPnCMSWOvp-hRpmgY2yjzLgPJ6Aucmg9TJ8jloAP1DjJoF1gRR7NTAk8LOGkSjTzVYDYMbCF51YdpojhItSk80YzXiEsv1mTz4oMM49jXBmfXFMA";
|
||||
private static final String MALFORMED_JWT = "eyJhbGciOiJSUzI1NiJ9.eyJuYmYiOnt9LCJleHAiOjQ2ODQyMjUwODd9.guoQvujdWvd3xw7FYQEn4D6-gzM_WqFvXdmvAUNSLbxG7fv2_LLCNujPdrBHJoYPbOwS1BGNxIKQWS1tylvqzmr1RohQ-RZ2iAM1HYQzboUlkoMkcd8ENM__ELqho8aNYBfqwkNdUOyBFoy7Syu_w2SoJADw2RTjnesKO6CVVa05bW118pDS4xWxqC4s7fnBjmZoTn4uQ-Kt9YSQZQk8YQxkJSiyanozzgyfgXULA6mPu1pTNU3FVFaK1i1av_xtH_zAPgb647ZeaNe4nahgqC5h8nhOlm8W2dndXbwAt29nd2ZWBsru_QwZz83XSKLhTPFz-mPBByZZDsyBbIHf9A";
|
||||
private static final String UNSIGNED_JWT = "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJleHAiOi0yMDMzMjI0OTcsImp0aSI6IjEyMyIsInR5cCI6IkpXVCJ9.";
|
||||
|
||||
@Test
|
||||
public void constructorWhenJwkSetUrlIsNullThenThrowIllegalArgumentException() {
|
||||
@ -102,8 +112,53 @@ public class NimbusJwtDecoderJwkSupportTests {
|
||||
public void decodeWhenPlainJwtThenExceptionDoesNotMentionClass() throws Exception {
|
||||
NimbusJwtDecoderJwkSupport jwtDecoder = new NimbusJwtDecoderJwkSupport(JWK_SET_URL, JWS_ALGORITHM);
|
||||
|
||||
assertThatCode(() -> jwtDecoder.decode(this.unsignedToken))
|
||||
assertThatCode(() -> jwtDecoder.decode(UNSIGNED_JWT))
|
||||
.isInstanceOf(JwtException.class)
|
||||
.hasMessageContaining("Unsupported algorithm of none");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decodeWhenJwtIsMalformedThenReturnsStockException() throws Exception {
|
||||
try ( MockWebServer server = new MockWebServer() ) {
|
||||
server.enqueue(new MockResponse().setBody(JWK_SET));
|
||||
String jwkSetUrl = server.url("/.well-known/jwks.json").toString();
|
||||
|
||||
NimbusJwtDecoderJwkSupport decoder = new NimbusJwtDecoderJwkSupport(jwkSetUrl);
|
||||
|
||||
assertThatCode(() -> decoder.decode(MALFORMED_JWT))
|
||||
.isInstanceOf(JwtException.class)
|
||||
.hasMessage("An error occurred while attempting to decode the Jwt: Malformed payload");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decodeWhenJwkResponseIsMalformedThenReturnsStockException() throws Exception {
|
||||
try ( MockWebServer server = new MockWebServer() ) {
|
||||
server.enqueue(new MockResponse().setBody(MALFORMED_JWK_SET));
|
||||
String jwkSetUrl = server.url("/.well-known/jwks.json").toString();
|
||||
|
||||
NimbusJwtDecoderJwkSupport decoder = new NimbusJwtDecoderJwkSupport(jwkSetUrl);
|
||||
|
||||
assertThatCode(() -> decoder.decode(SIGNED_JWT))
|
||||
.isInstanceOf(JwtException.class)
|
||||
.hasMessage("An error occurred while attempting to decode the Jwt: Malformed Jwk set");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decodeWhenJwkEndpointIsUnresponsiveThenReturnsStockException() throws Exception {
|
||||
try ( MockWebServer server = new MockWebServer() ) {
|
||||
server.enqueue(new MockResponse().setBody(MALFORMED_JWK_SET));
|
||||
String jwkSetUrl = server.url("/.well-known/jwks.json").toString();
|
||||
|
||||
NimbusJwtDecoderJwkSupport decoder = new NimbusJwtDecoderJwkSupport(jwkSetUrl);
|
||||
|
||||
server.shutdown();
|
||||
|
||||
assertThatCode(() -> decoder.decode(SIGNED_JWT))
|
||||
.isInstanceOf(JwtException.class)
|
||||
.hasMessage("An error occurred while attempting to decode the Jwt: " +
|
||||
"Couldn't retrieve remote JWK set: Connection refused (Connection refused)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user