Skip to content

Commit 99ecff1

Browse files
committed
Jwt Decoder Local Key Configuration
Adds support for configuring Resource Server DSL with a local public key. Fixes: spring-projectsgh-5131
1 parent d28e32b commit 99ecff1

File tree

5 files changed

+220
-3
lines changed

5 files changed

+220
-3
lines changed

config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,14 @@
2020
import java.io.FileReader;
2121
import java.io.IOException;
2222
import java.lang.reflect.Field;
23+
import java.security.KeyFactory;
24+
import java.security.interfaces.RSAPublicKey;
25+
import java.security.spec.X509EncodedKeySpec;
2326
import java.time.Clock;
2427
import java.time.Duration;
2528
import java.time.Instant;
2629
import java.time.ZoneId;
30+
import java.util.Base64;
2731
import java.util.Collection;
2832
import java.util.Collections;
2933
import java.util.Map;
@@ -110,6 +114,7 @@
110114
import static org.mockito.Mockito.verify;
111115
import static org.mockito.Mockito.when;
112116
import static org.springframework.security.oauth2.jwt.JwtProcessors.withJwkSetUri;
117+
import static org.springframework.security.oauth2.jwt.JwtProcessors.withPublicKey;
113118
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
114119
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic;
115120
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
@@ -947,6 +952,44 @@ public void requestWhenJwtAuthenticationConverterCustomizedAuthoritiesThenThoseA
947952
.andExpect(status().isOk());
948953
}
949954

955+
// -- single key
956+
957+
@Test
958+
public void requestWhenUsingPublicKeyAndValidTokenThenAuthenticates()
959+
throws Exception {
960+
961+
this.spring.register(SingleKeyConfig.class, BasicController.class).autowire();
962+
String token = this.token("ValidNoScopes");
963+
964+
this.mvc.perform(get("/")
965+
.with(bearerToken(token)))
966+
.andExpect(status().isOk());
967+
}
968+
969+
@Test
970+
public void requestWhenUsingPublicKeyAndSignatureFailsThenReturnsInvalidToken()
971+
throws Exception {
972+
973+
this.spring.register(SingleKeyConfig.class).autowire();
974+
String token = this.token("WrongSignature");
975+
976+
this.mvc.perform(get("/")
977+
.with(bearerToken(token)))
978+
.andExpect(invalidTokenHeader("signature"));
979+
}
980+
981+
@Test
982+
public void requestWhenUsingPublicKeyAlgorithmDoesNotMatchThenReturnsInvalidToken()
983+
throws Exception {
984+
985+
this.spring.register(SingleKeyConfig.class).autowire();
986+
String token = this.token("WrongAlgorithm");
987+
988+
this.mvc.perform(get("/")
989+
.with(bearerToken(token)))
990+
.andExpect(invalidTokenHeader("algorithm"));
991+
}
992+
950993
// -- In combination with other authentication providers
951994

952995
@Test
@@ -1519,8 +1562,38 @@ protected void configure(HttpSecurity http) throws Exception {
15191562
.oauth2ResourceServer()
15201563
.jwt()
15211564
.decoder(jwtDecoder);
1565+
}
1566+
}
1567+
1568+
@EnableWebSecurity
1569+
static class SingleKeyConfig extends WebSecurityConfigurerAdapter {
1570+
byte[] spec = Base64.getDecoder().decode(
1571+
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoXJ8OyOv/eRnce4akdan" +
1572+
"R4KYRfnC2zLV4uYNQpcFn6oHL0dj7D6kxQmsXoYgJV8ZVDn71KGmuLvolxsDncc2" +
1573+
"UrhyMBY6DVQVgMSVYaPCTgW76iYEKGgzTEw5IBRQL9w3SRJWd3VJTZZQjkXef48O" +
1574+
"cz06PGF3lhbz4t5UEZtdF4rIe7u+977QwHuh7yRPBQ3sII+cVoOUMgaXB9SHcGF2" +
1575+
"iZCtPzL/IffDUcfhLQteGebhW8A6eUHgpD5A1PQ+JCw/G7UOzZAjjDjtNM2eqm8j" +
1576+
"+Ms/gqnm4MiCZ4E+9pDN77CAAPVN7kuX6ejs9KBXpk01z48i9fORYk9u7rAkh1Hu" +
1577+
"QwIDAQAB");
1578+
1579+
@Override
1580+
protected void configure(HttpSecurity http) throws Exception {
1581+
// @formatter:off
1582+
http
1583+
.authorizeRequests()
1584+
.anyRequest().authenticated()
1585+
.and()
1586+
.oauth2ResourceServer()
1587+
.jwt();
15221588
// @formatter:on
15231589
}
1590+
1591+
@Bean
1592+
JwtDecoder decoder() throws Exception {
1593+
RSAPublicKey publicKey = (RSAPublicKey)
1594+
KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(this.spec));
1595+
return new NimbusJwtDecoder(withPublicKey(publicKey).build());
1596+
}
15241597
}
15251598

15261599
@Configuration
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
eyJhbGciOiJSUzUxMiJ9.eyJleHAiOjQ2ODczNDQ2NzN9.hvVUW_xwUXd7nGm27E5tLTZ21x64YjP0o-TMW6t_bOkfG1Vp1AMEX8fXvSqeG0vK8TWiB2_keOGtH-eFmAGBEYXq1o1zj1BgMHeaZAVio9n-77DkTzQ7CiOF5M1M7B_Ng4K8ra4DpieZZXVjHTWsuOiU1hWoI1tIna8VucAxZln-oh7PkrYmgwFTlsL2Z9aZZYN_X7ECyRQDf3lRrLwr4Go_XpJ5i9F-GT5LvUYa42uggGjvq_frfb0t5wcmPgjtqiE6l2mnrYFjjKTq1nQRYrJ5wFWOHUTRxNsGS8PwrNxzh6JW1ZZTS0n_JIOvSh__w0WAB241QLoKBx4AETMLQA
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
eyJhbGciOiJSUzI1NiJ9.eyJleHAiOjQ2ODczNDQ0MTd9.jfqDyHvpRXWF6KaRQS3cGT0HUSix09xwTPvUCtg9UJ2QR1Rx4MclGCli3yIHNm0vsRed4s-gZduVGfbj7enyKnpXCZE7dNxZENfm7P54OfJmlyJY3DvhzlyH_rtuOD4c_Q88J9uELd_pghikLlMtu8090UzTtwRfdo_JsDfMRAcDeYq7TTaL60w3AVarStwZAAy_dpi6bTEanm5hwkz4-deA4Bz4KentpvlcwB01IXw9DVYrW1lpzLgycwk_VbCK_LA1hjFnnjc3OnQaxvqydrBAlFD3ziklVAxGnKnrYzppixdwwztuga4XS36OhicIGXEkMf3oT3nzgcR309DP_A

oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtProcessors.java

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,13 @@
1818
import java.io.IOException;
1919
import java.net.MalformedURLException;
2020
import java.net.URL;
21+
import java.security.interfaces.RSAPublicKey;
2122
import java.util.Collections;
2223

2324
import com.nimbusds.jose.JWSAlgorithm;
25+
import com.nimbusds.jose.jwk.JWKSet;
26+
import com.nimbusds.jose.jwk.RSAKey;
27+
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
2428
import com.nimbusds.jose.jwk.source.JWKSource;
2529
import com.nimbusds.jose.jwk.source.RemoteJWKSet;
2630
import com.nimbusds.jose.proc.JWSKeySelector;
@@ -37,6 +41,7 @@
3741
import org.springframework.http.MediaType;
3842
import org.springframework.http.RequestEntity;
3943
import org.springframework.http.ResponseEntity;
44+
import org.springframework.security.oauth2.jose.jws.JwsAlgorithms;
4045
import org.springframework.util.Assert;
4146
import org.springframework.web.client.RestOperations;
4247
import org.springframework.web.client.RestTemplate;
@@ -61,6 +66,16 @@ public static JwkSetUriJwtProcessorBuilder withJwkSetUri(String jwkSetUri) {
6166
return new JwkSetUriJwtProcessorBuilder(jwkSetUri);
6267
}
6368

69+
/**
70+
* Use the given public key to validate JWTs
71+
*
72+
* @param key the public key to use
73+
* @return a {@link PublicKeyJwtProcessorBuilder} for further configurations
74+
*/
75+
public static PublicKeyJwtProcessorBuilder withPublicKey(RSAPublicKey key) {
76+
return new PublicKeyJwtProcessorBuilder(key);
77+
}
78+
6479
/**
6580
* A builder for creating Nimbus {@link JWTProcessor} instances based on a
6681
* <a target="_blank" href="https://tools.ietf.org/html/rfc7517#section-5">JWK Set</a> uri.
@@ -159,4 +174,65 @@ public Resource retrieveResource(URL url) throws IOException {
159174
}
160175
}
161176
}
177+
178+
/**
179+
* A builder for creating Nimbus {@link JWTProcessor} instances based on a
180+
* public key.
181+
*/
182+
public static final class PublicKeyJwtProcessorBuilder {
183+
private JWSAlgorithm jwsAlgorithm;
184+
private RSAKey key;
185+
186+
private PublicKeyJwtProcessorBuilder(RSAPublicKey key) {
187+
Assert.notNull(key, "key cannot be null");
188+
this.jwsAlgorithm = JWSAlgorithm.parse(JwsAlgorithms.RS256);
189+
this.key = rsaKey(key);
190+
}
191+
192+
private static RSAKey rsaKey(RSAPublicKey publicKey) {
193+
return new RSAKey.Builder(publicKey)
194+
.build();
195+
}
196+
197+
/**
198+
* Use the given signing
199+
* <a href="https://tools.ietf.org/html/rfc7515#section-4.1.1" target="_blank">algorithm</a>.
200+
*
201+
* The value should be one of
202+
* <a href="https://tools.ietf.org/html/rfc7518#section-3.3" target="_blank">RS256, RS384, or RS512</a>.
203+
*
204+
* @param jwsAlgorithm the algorithm to use
205+
* @return a {@link JwtProcessors} for further configurations
206+
*/
207+
public PublicKeyJwtProcessorBuilder jwsAlgorithm(String jwsAlgorithm) {
208+
Assert.hasText(jwsAlgorithm, "jwsAlgorithm cannot be empty");
209+
this.jwsAlgorithm = JWSAlgorithm.parse(jwsAlgorithm);
210+
return this;
211+
}
212+
213+
/**
214+
* Build the configured {@link JWTProcessor}.
215+
*mzRC
216+
* @return the configured {@link JWTProcessor}
217+
*/
218+
public JWTProcessor<SecurityContext> build() {
219+
if (!JWSAlgorithm.Family.RSA.contains(this.jwsAlgorithm)) {
220+
throw new IllegalStateException("The provided key is of type RSA; " +
221+
"however the signature algorithm is of some other type: " +
222+
this.jwsAlgorithm + ". Please indicate one of RS256, RS384, or RS512.");
223+
}
224+
225+
JWKSet jwkSet = new JWKSet(this.key);
226+
JWKSource<SecurityContext> jwkSource = new ImmutableJWKSet<>(jwkSet);
227+
JWSKeySelector<SecurityContext> jwsKeySelector =
228+
new JWSVerificationKeySelector<>(this.jwsAlgorithm, jwkSource);
229+
DefaultJWTProcessor<SecurityContext> jwtProcessor = new DefaultJWTProcessor<>();
230+
jwtProcessor.setJWSKeySelector(jwsKeySelector);
231+
232+
// Spring Security validates the claim set independent from Nimbus
233+
jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> { });
234+
235+
return jwtProcessor;
236+
}
237+
}
162238
}

oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/JwtProcessorsTest.java

Lines changed: 69 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,30 @@
1616

1717
package org.springframework.security.oauth2.jwt;
1818

19+
import java.security.KeyFactory;
20+
import java.security.NoSuchAlgorithmException;
21+
import java.security.interfaces.RSAPublicKey;
22+
import java.security.spec.EncodedKeySpec;
23+
import java.security.spec.InvalidKeySpecException;
24+
import java.security.spec.X509EncodedKeySpec;
25+
import java.util.Base64;
26+
27+
import com.nimbusds.jose.proc.BadJOSEException;
1928
import com.nimbusds.jose.proc.SecurityContext;
2029
import com.nimbusds.jwt.JWTClaimsSet;
2130
import com.nimbusds.jwt.proc.JWTProcessor;
31+
import org.junit.BeforeClass;
2232
import org.junit.Test;
2333

2434
import org.springframework.http.HttpStatus;
2535
import org.springframework.http.RequestEntity;
2636
import org.springframework.http.ResponseEntity;
37+
import org.springframework.security.oauth2.jose.jws.JwsAlgorithms;
2738
import org.springframework.web.client.RestOperations;
2839

2940
import static org.assertj.core.api.Assertions.assertThat;
3041
import static org.assertj.core.api.Assertions.assertThatCode;
42+
import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
3143
import static org.mockito.ArgumentMatchers.any;
3244
import static org.mockito.ArgumentMatchers.eq;
3345
import static org.mockito.Mockito.mock;
@@ -39,9 +51,18 @@
3951
* Tests for {@link JwtProcessors}
4052
*/
4153
public class JwtProcessorsTest {
42-
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\"}]}";
43-
private static final String SIGNED_JWT = "eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ0ZXN0LXN1YmplY3QiLCJzY3AiOlsibWVzc2FnZTpyZWFkIl0sImV4cCI6NDY4Mzg5Nzc3Nn0.LtMVtIiRIwSyc3aX35Zl0JVwLTcQZAB3dyBOMHNaHCKUljwMrf20a_gT79LfhjDzE_fUVUmFiAO32W1vFnYpZSVaMDUgeIOIOpxfoe9shj_uYenAwIS-_UxqGVIJiJoXNZh_MK80ShNpvsQwamxWEEOAMBtpWNiVYNDMdfgho9n3o5_Z7Gjy8RLBo1tbDREbO9kTFwGIxm_EYpezmRCRq4w1DdS6UDW321hkwMxPnCMSWOvp-hRpmgY2yjzLgPJ6Aucmg9TJ8jloAP1DjJoF1gRR7NTAk8LOGkSjTzVYDYMbCF51YdpojhItSk80YzXiEsv1mTz4oMM49jXBmfXFMA";
54+
private static final String JWK_SET = "{\"keys\":[{\"p\":\"5VhDhCcOp9D3krJi2W2uF-LPovIKqtU59Jdt_gF6iwPw_0Bgo8UlbFRv3HdnVYUExLdkvHOoA9bmbkV0w_6TUwLP65PKPu6P8JfeWsIuGi3wY91_CsfahxGmVZxlZookBUIMMnylvM9fGR6-daNgkl31CKqDpkJt9XF35yjVpbM\",\"kty\":\"RSA\",\"q\":\"v3yrgOhimojmLVsLBdynmw_pfAlZPw2eXVzoJ514xP94UZJQRY_NOUjYV0O9Vqict_Qv42sUa-uurY8n_0Btslt--iJsMyTHYMIKjbyeFAqAGFuXYbQPnorEOkuZhT1NIBZhlLLuKSD8DVCtsEv2EVgBTwyzFJ6QbXLqVpNUvZs\",\"d\":\"ZyjCopsw7TszSV4qMIyunb0PaGfHbQ_0LJcAxhNwQsf3MYR6j0J9k1GVxq2SjpRylgKJg8CKjySaU4frewH7MEaNLNdfR2_XMFSKW3KFggdNRtW1TFwjcHfpBLTvB3MEaTx56Sohn0eXqd_Wa2EAfRiLjllwOeqwXqgdSXdvKfkkmV2DBZ2h100wLJB87Y5kvlGvbNDs0KgTFaPWRZkCQz3CGhGPDyTJVwzgJvIxbHgzl9DnW6FlgrP_DZmyfGbJ833FZSiBczTQGDWT7euR3h491fKPCHTXjdULtU1578NldRAo8SOXH4ThXXA_kwKafIGlKx5LZPNwMWgNuVvE6Q\",\"e\":\"AQAB\",\"use\":\"sig\",\"kid\":\"one\",\"qi\":\"eaGTNLhJf1K82YqB6VKrYWz1hxsKnjRBg-V-kuWJXvW7HQsLFKx56kXy_ximz_IQDZOO3F-rW_7Saz3RvWuFt_Yq7sRcLCMtpiDRbZ-nGDgHxQHedtLoalLLPmJkMMsZwZzXf9l6LO6a8r30lrC_C-kPY5K7lz97ZToKeper7c8\",\"dp\":\"QQJ4-O_dTqKEWvfn3zwg2jJ3qvezIGOarwNxsUuYAenXGXOVMTcD-aYhozvRdcNj66MUkfqyyIvU-7MCe0AhYKluaJeW_6m98XQLGmzqho85EgXKKjMmdZ0CKkhP0fYcacUkEfeVP2UEzukREeWCzVqGx7MV6D3yT12foE3J6dM\",\"dq\":\"PsH2V5ZSEsHBZqYLE83ApMJvTHan6FFnUMQNVkZ2-WGdJmbkphe-NAMa3GbYHBnA201NkKRcmg4xPrLHchHEogr4r7QucAiiy6Rs3w0tZfYXC2ShVaU05Uoni8-RLijsKRMMwjZudc5YrWh-tGQA7qhALY9E9gIN5cEe6mb5A_c\",\"n\":\"q4yKxb6SNePdDmQi9xFCrP6QvHosErQzryknQTTTffs0t3cy3Er3lIceuhZ7yQNSCDfPFqG8GoyoKhuChRiA5D-J2ab7bqTa1QJKfnCyERoscftgN2fXPHjHoiKbpGV2tMVw8mXl__tePOAiKbMJaBUnlAvJgkk1rVm08dSwpLC1sr2M19euf9jwnRGkMRZuhp9iCPgECRke5T8Ixpv0uQjSmGHnWUKTFlbj8sM83suROR1Ue64JSGScANc5vk3huJ_J97qTC-K2oKj6L8d9O8dpc4obijEOJwpydNvTYDgbiivYeSB00KS9jlBkQ5B2QqLvLVEygDl3dp59nGx6YQ\"}]}";
4455
private static final String JWK_SET_URI = "http://issuer/.well-known/jwks.json";
56+
private static final String RS512_SIGNED_JWT = "eyJhbGciOiJSUzUxMiJ9.eyJzdWIiOiJ0ZXN0LXN1YmplY3QiLCJleHAiOjE5NzQzMjYxMTl9.LKAx-60EBfD7jC1jb1eKcjO4uLvf3ssISV-8tN-qp7gAjSvKvj4YA9-V2mIb6jcS1X_xGmNy6EIimZXpWaBR3nJmeu-jpe85u4WaW2Ztr8ecAi-dTO7ZozwdtljKuBKKvj4u1nF70zyCNl15AozSG0W1ASrjUuWrJtfyDG6WoZ8VfNMuhtU-xUYUFvscmeZKUYQcJ1KS-oV5tHeF8aNiwQoiPC_9KXCOZtNEJFdq6-uzFdHxvOP2yex5Gbmg5hXonauIFXG2ZPPGdXzm-5xkhBpgM8U7A_6wb3So8wBvLYYm2245QUump63AJRAy8tQpwt4n9MvQxQgS3z9R-NK92A";
57+
private static final String RS256_SIGNED_JWT = "eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ0ZXN0LXN1YmplY3QiLCJleHAiOjE5NzQzMjYzMzl9.CT-H2OWEqmSs1NWmnta5ealLFvM8OlbQTjGhfRcKLNxrTrzsOkqBJl-AN3k16BQU7mS32o744TiiZ29NcDlxPsr1MqTlN86-dobPiuNIDLp3A1bOVdXMcVFuMYkrNv0yW0tGS9OjEqsCCuZDkZ1by6AhsHLbGwRY-6AQdcRouZygGpOQu1hNun5j8q5DpSTY4AXKARIFlF-O3OpVbPJ0ebr3Ki-i3U9p_55H0e4-wx2bqcApWlqgofl1I8NKWacbhZgn81iibup2W7E0CzCzh71u1Mcy3xk1sYePx-dwcxJnHmxJReBBWjJZEAeCrkbnn_OCuo2fA-EQyNJtlN5F2w";
58+
private static final String VERIFY_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAq4yKxb6SNePdDmQi9xFCrP6QvHosErQzryknQTTTffs0t3cy3Er3lIceuhZ7yQNSCDfPFqG8GoyoKhuChRiA5D+J2ab7bqTa1QJKfnCyERoscftgN2fXPHjHoiKbpGV2tMVw8mXl//tePOAiKbMJaBUnlAvJgkk1rVm08dSwpLC1sr2M19euf9jwnRGkMRZuhp9iCPgECRke5T8Ixpv0uQjSmGHnWUKTFlbj8sM83suROR1Ue64JSGScANc5vk3huJ/J97qTC+K2oKj6L8d9O8dpc4obijEOJwpydNvTYDgbiivYeSB00KS9jlBkQ5B2QqLvLVEygDl3dp59nGx6YQIDAQAB";
59+
60+
private static KeyFactory kf;
61+
62+
@BeforeClass
63+
public static void keyFactory() throws NoSuchAlgorithmException {
64+
kf = KeyFactory.getInstance("RSA");
65+
}
4566

4667
@Test
4768
public void withJwkSetUriWhenNullOrEmptyThenThrowsException() {
@@ -68,12 +89,57 @@ public void processWhenSignedThenOk() throws Exception {
6889
RestOperations restOperations = mockJwkSetResponse(JWK_SET);
6990
JWTProcessor<SecurityContext> processor =
7091
withJwkSetUri(JWK_SET_URI).restOperations(restOperations).build();
71-
assertThat(processor.process(SIGNED_JWT, null))
92+
assertThat(processor.process(RS256_SIGNED_JWT, null))
7293
.extracting(JWTClaimsSet::getExpirationTime)
7394
.isNotNull();
7495
verify(restOperations).exchange(any(RequestEntity.class), eq(String.class));
7596
}
7697

98+
@Test
99+
public void withPublicKeyWhenNullThenThrowsException() {
100+
assertThatThrownBy(() -> JwtProcessors.withPublicKey(null))
101+
.isInstanceOf(IllegalArgumentException.class);
102+
}
103+
104+
@Test
105+
public void buildWhenSignatureAlgorithmMismatchesKeyTypeThenThrowsException() {
106+
assertThatCode(() -> JwtProcessors.withPublicKey(key())
107+
.jwsAlgorithm(JwsAlgorithms.ES256)
108+
.build())
109+
.isInstanceOf(IllegalStateException.class);
110+
}
111+
112+
@Test
113+
public void processWhenUsingPublicKeyThenSuccessfullyDecodes() throws Exception {
114+
JWTProcessor<SecurityContext> processor = JwtProcessors.withPublicKey(key()).build();
115+
assertThat(processor.process(RS256_SIGNED_JWT, null))
116+
.extracting(JWTClaimsSet::getSubject)
117+
.isEqualTo("test-subject");
118+
}
119+
120+
@Test
121+
public void processWhenUsingPublicKeyWithRs512ThenSuccessfullyDecodes() throws Exception {
122+
JWTProcessor<SecurityContext> processor = JwtProcessors
123+
.withPublicKey(key()).jwsAlgorithm(JwsAlgorithms.RS512).build();
124+
assertThat(processor.process(RS512_SIGNED_JWT, null))
125+
.extracting(JWTClaimsSet::getSubject)
126+
.isEqualTo("test-subject");
127+
}
128+
129+
@Test
130+
public void processWhenSignatureMismatchesAlgorithmThenThrowsException() throws Exception {
131+
JWTProcessor<SecurityContext> processor = JwtProcessors
132+
.withPublicKey(key()).jwsAlgorithm(JwsAlgorithms.RS512).build();
133+
assertThatCode(() -> processor.process(RS256_SIGNED_JWT, null))
134+
.isInstanceOf(BadJOSEException.class);
135+
}
136+
137+
private RSAPublicKey key() throws InvalidKeySpecException {
138+
byte[] decoded = Base64.getDecoder().decode(VERIFY_KEY.getBytes());
139+
EncodedKeySpec spec = new X509EncodedKeySpec(decoded);
140+
return (RSAPublicKey) kf.generatePublic(spec);
141+
}
142+
77143
private static RestOperations mockJwkSetResponse(String response) {
78144
RestOperations restOperations = mock(RestOperations.class);
79145
when(restOperations.exchange(any(RequestEntity.class), eq(String.class)))

0 commit comments

Comments
 (0)