From dbd1819ea48f7a57b88d5828a6fe1dc237d62978 Mon Sep 17 00:00:00 2001 From: Bouke Nijhuis Date: Fri, 23 Aug 2019 12:05:26 +0200 Subject: [PATCH] add media type jwk-set+json to accept header Fixes gh-7290 --- .../security/oauth2/jwt/NimbusJwtDecoder.java | 4 +++- .../oauth2/jwt/NimbusJwtDecoderTests.java | 23 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java index 4c4df529ef..acfb590d32 100644 --- a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java +++ b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java @@ -22,6 +22,7 @@ import java.net.URL; import java.security.interfaces.RSAPublicKey; import java.text.ParseException; import java.time.Instant; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -310,6 +311,7 @@ public final class NimbusJwtDecoder implements JwtDecoder { } private static class RestOperationsResourceRetriever implements ResourceRetriever { + private static final MediaType APPLICATION_JWK_SET_JSON = new MediaType("application", "jwk-set+json"); private final RestOperations restOperations; RestOperationsResourceRetriever(RestOperations restOperations) { @@ -320,7 +322,7 @@ public final class NimbusJwtDecoder implements JwtDecoder { @Override public Resource retrieveResource(URL url) throws IOException { HttpHeaders headers = new HttpHeaders(); - headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON_UTF8)); + headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON, APPLICATION_JWK_SET_JSON)); ResponseEntity response; try { diff --git a/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoderTests.java b/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoderTests.java index e194267569..4b235b5dea 100644 --- a/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoderTests.java +++ b/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoderTests.java @@ -30,6 +30,7 @@ import java.util.Arrays; import java.util.Base64; import java.util.Collections; import java.util.Date; +import java.util.List; import java.util.Map; import javax.crypto.SecretKey; @@ -54,8 +55,10 @@ import org.assertj.core.api.Assertions; import org.junit.BeforeClass; import org.junit.Test; +import org.mockito.ArgumentCaptor; import org.springframework.core.convert.converter.Converter; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.http.RequestEntity; import org.springframework.http.ResponseEntity; import org.springframework.security.oauth2.core.OAuth2Error; @@ -72,6 +75,7 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.springframework.security.oauth2.jwt.NimbusJwtDecoder.withJwkSetUri; import static org.springframework.security.oauth2.jwt.NimbusJwtDecoder.withPublicKey; @@ -97,6 +101,8 @@ public class NimbusJwtDecoderTests { private static final String RS256_SIGNED_JWT = "eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ0ZXN0LXN1YmplY3QiLCJleHAiOjE5NzQzMjYzMzl9.CT-H2OWEqmSs1NWmnta5ealLFvM8OlbQTjGhfRcKLNxrTrzsOkqBJl-AN3k16BQU7mS32o744TiiZ29NcDlxPsr1MqTlN86-dobPiuNIDLp3A1bOVdXMcVFuMYkrNv0yW0tGS9OjEqsCCuZDkZ1by6AhsHLbGwRY-6AQdcRouZygGpOQu1hNun5j8q5DpSTY4AXKARIFlF-O3OpVbPJ0ebr3Ki-i3U9p_55H0e4-wx2bqcApWlqgofl1I8NKWacbhZgn81iibup2W7E0CzCzh71u1Mcy3xk1sYePx-dwcxJnHmxJReBBWjJZEAeCrkbnn_OCuo2fA-EQyNJtlN5F2w"; private static final String VERIFY_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAq4yKxb6SNePdDmQi9xFCrP6QvHosErQzryknQTTTffs0t3cy3Er3lIceuhZ7yQNSCDfPFqG8GoyoKhuChRiA5D+J2ab7bqTa1QJKfnCyERoscftgN2fXPHjHoiKbpGV2tMVw8mXl//tePOAiKbMJaBUnlAvJgkk1rVm08dSwpLC1sr2M19euf9jwnRGkMRZuhp9iCPgECRke5T8Ixpv0uQjSmGHnWUKTFlbj8sM83suROR1Ue64JSGScANc5vk3huJ/J97qTC+K2oKj6L8d9O8dpc4obijEOJwpydNvTYDgbiivYeSB00KS9jlBkQ5B2QqLvLVEygDl3dp59nGx6YQIDAQAB"; + private static final MediaType APPLICATION_JWK_SET_JSON = new MediaType("application", "jwk-set+json"); + private static KeyFactory kf; NimbusJwtDecoder jwtDecoder = new NimbusJwtDecoder(withoutSigning()); @@ -400,6 +406,23 @@ public class NimbusJwtDecoderTests { .containsExactlyInAnyOrder(JWSAlgorithm.RS256, JWSAlgorithm.RS512); } + // gh-7290 + @Test + public void decodeWhenJwkSetRequestedThenAcceptHeaderJsonAndJwkSetJson() { + RestOperations restOperations = mock(RestOperations.class); + when(restOperations.exchange(any(RequestEntity.class), eq(String.class))) + .thenReturn(new ResponseEntity<>(JWK_SET, HttpStatus.OK)); + JWTProcessor processor = withJwkSetUri("https://issuer/.well-known/jwks.json") + .restOperations(restOperations) + .processor(); + NimbusJwtDecoder jwtDecoder = new NimbusJwtDecoder(processor); + jwtDecoder.decode(SIGNED_JWT); + ArgumentCaptor requestEntityCaptor = ArgumentCaptor.forClass(RequestEntity.class); + verify(restOperations).exchange(requestEntityCaptor.capture(), eq(String.class)); + List acceptHeader = requestEntityCaptor.getValue().getHeaders().getAccept(); + assertThat(acceptHeader).contains(MediaType.APPLICATION_JSON, APPLICATION_JWK_SET_JSON); + } + private RSAPublicKey key() throws InvalidKeySpecException { byte[] decoded = Base64.getDecoder().decode(VERIFY_KEY.getBytes()); EncodedKeySpec spec = new X509EncodedKeySpec(decoded);