1
1
/*
2
- * Copyright 2002-2021 the original author or authors.
2
+ * Copyright 2002-2022 the original author or authors.
3
3
*
4
4
* Licensed under the Apache License, Version 2.0 (the "License");
5
5
* you may not use this file except in compliance with the License.
32
32
import java .util .Date ;
33
33
import java .util .List ;
34
34
import java .util .Map ;
35
- import java .util .concurrent .Callable ;
36
35
37
36
import javax .crypto .SecretKey ;
38
37
43
42
import com .nimbusds .jose .JWSSigner ;
44
43
import com .nimbusds .jose .crypto .MACSigner ;
45
44
import com .nimbusds .jose .crypto .RSASSASigner ;
46
- import com .nimbusds .jose .jwk .JWKSet ;
47
- import com .nimbusds .jose .jwk .RSAKey ;
48
- import com .nimbusds .jose .jwk .gen .RSAKeyGenerator ;
49
45
import com .nimbusds .jose .jwk .source .JWKSource ;
50
46
import com .nimbusds .jose .proc .BadJOSEException ;
51
47
import com .nimbusds .jose .proc .DefaultJOSEObjectTypeVerifier ;
@@ -102,10 +98,14 @@ public class NimbusJwtDecoderTests {
102
98
103
99
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\" }]}" ;
104
100
101
+ private static final String NEW_KID_JWK_SET = "{\" keys\" :[{\" kty\" :\" RSA\" ,\" e\" :\" AQAB\" ,\" kid\" :\" two\" ,\" n\" :\" ra9UJw4I0fCHuOqr1xWJsh-qcVeZWtKEU3uoqq1sAg5fG67dujNCm_Q16yuO0ZdDiU0vlJkbc_MXFAvm4ZxdJ_qR7PAneV-BOGNtLpSaiPclscCy3m7zjRWkaqwt9ZZEsdK5UqXyPlBpcYhNKsmnQGjnX4sYb7d8b2jSCM_qto48-6451rbyEhXXywtFy_JqtTpbsw_IIdQHMr1O-MdSjsQxX9kkvZwPU8LsC-CcqlcsZ7mnpOhmIXaf4tbRwAaluXwYft0yykFsp8e5C4t9mMs9Vu8AB5gT8o-D_ovXd2qh4k3ejzVpYLtzD4nbfvPJA_TXmjhn-9GOPAqkzfON2Q\" }]}" ;
102
+
105
103
private static final String MALFORMED_JWK_SET = "malformed" ;
106
104
107
105
private static final String SIGNED_JWT = "eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ0ZXN0LXN1YmplY3QiLCJzY3AiOlsibWVzc2FnZTpyZWFkIl0sImV4cCI6NDY4Mzg5Nzc3Nn0.LtMVtIiRIwSyc3aX35Zl0JVwLTcQZAB3dyBOMHNaHCKUljwMrf20a_gT79LfhjDzE_fUVUmFiAO32W1vFnYpZSVaMDUgeIOIOpxfoe9shj_uYenAwIS-_UxqGVIJiJoXNZh_MK80ShNpvsQwamxWEEOAMBtpWNiVYNDMdfgho9n3o5_Z7Gjy8RLBo1tbDREbO9kTFwGIxm_EYpezmRCRq4w1DdS6UDW321hkwMxPnCMSWOvp-hRpmgY2yjzLgPJ6Aucmg9TJ8jloAP1DjJoF1gRR7NTAk8LOGkSjTzVYDYMbCF51YdpojhItSk80YzXiEsv1mTz4oMM49jXBmfXFMA" ;
108
106
107
+ private static final String NEW_KID_SIGNED_JWT = "eyJraWQiOiJ0d28iLCJhbGciOiJSUzI1NiJ9.eyJleHAiOjIxMzMyNzg4MjV9.DQJn_qg0HfZ_sjlx9MJkdCjkp9t-0zOj3FzVp_UPzx6RCcBb8Jk373dNgcyfOP5CS29wv5gKX6geWEDj5cgqcJdTS53zqOaLETdNnKACd056SkPqgTLJv12gdJx7tr5WbBqRB9Y0ce96vbH6wwQGfqU_1Lz1RhZ7ZZuvIuWLp75ujld7dOshScg728Z9BQsiFOH_yFp09XraO15spwTXp9RO5TJRUSLih-5V3sdxHa5rPTm6by7me8I_l4iMJN81Z95_O7sbLeYH-4zZ-3T49uPyAC5suEOd-P5aFP89zPKh9Y3Uviu2OyvpUuXmpUjTtdAKf3p96dOEeLJvT3hkSg" ;
108
+
109
109
private static final String MALFORMED_JWT = "eyJhbGciOiJSUzI1NiJ9.eyJuYmYiOnt9LCJleHAiOjQ2ODQyMjUwODd9.guoQvujdWvd3xw7FYQEn4D6-gzM_WqFvXdmvAUNSLbxG7fv2_LLCNujPdrBHJoYPbOwS1BGNxIKQWS1tylvqzmr1RohQ-RZ2iAM1HYQzboUlkoMkcd8ENM__ELqho8aNYBfqwkNdUOyBFoy7Syu_w2SoJADw2RTjnesKO6CVVa05bW118pDS4xWxqC4s7fnBjmZoTn4uQ-Kt9YSQZQk8YQxkJSiyanozzgyfgXULA6mPu1pTNU3FVFaK1i1av_xtH_zAPgb647ZeaNe4nahgqC5h8nhOlm8W2dndXbwAt29nd2ZWBsru_QwZz83XSKLhTPFz-mPBByZZDsyBbIHf9A" ;
110
110
111
111
private static final String UNSIGNED_JWT = "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJleHAiOi0yMDMzMjI0OTcsImp0aSI6IjEyMyIsInR5cCI6IkpXVCJ9." ;
@@ -649,35 +649,59 @@ public void decodeWhenCacheThenStoreRetrievedJwkSetToCache() {
649
649
}
650
650
651
651
@ Test
652
- public void decodeWhenCacheThenRetrieveFromCache () {
652
+ public void decodeWhenCacheStoredThenAbleToRetrieveJwkSetFromCache () {
653
+ Cache cache = new ConcurrentMapCache ("test-jwk-set-cache" );
654
+ RestOperations restOperations = mock (RestOperations .class );
655
+ given (restOperations .exchange (any (RequestEntity .class ), eq (String .class )))
656
+ .willReturn (new ResponseEntity <>(JWK_SET , HttpStatus .OK ));
657
+ // @formatter:off
658
+ NimbusJwtDecoder jwtDecoder1 = NimbusJwtDecoder .withJwkSetUri (JWK_SET_URI )
659
+ .restOperations (restOperations )
660
+ .cache (cache )
661
+ .build ();
662
+ // @formatter:on
663
+ jwtDecoder1 .decode (SIGNED_JWT );
664
+ assertThat (cache .get (JWK_SET_URI , String .class )).isEqualTo (JWK_SET );
665
+ verify (restOperations ).exchange (any (RequestEntity .class ), eq (String .class ));
666
+
667
+ // @formatter:off
668
+ NimbusJwtDecoder jwtDecoder2 = NimbusJwtDecoder .withJwkSetUri (JWK_SET_URI )
669
+ .restOperations (restOperations )
670
+ .cache (cache )
671
+ .build ();
672
+ // @formatter:on
673
+ jwtDecoder2 .decode (SIGNED_JWT );
674
+ verifyNoMoreInteractions (restOperations );
675
+ }
676
+
677
+ // gh-11621
678
+ @ Test
679
+ public void decodeWhenCacheThenRetrieveFromCache () throws Exception {
653
680
RestOperations restOperations = mock (RestOperations .class );
654
681
Cache cache = mock (Cache .class );
655
- given (cache .get (eq (JWK_SET_URI ), any (Callable .class ))).willReturn (JWK_SET );
682
+ given (cache .get (eq (JWK_SET_URI ), eq (String .class ))).willReturn (JWK_SET );
683
+ given (cache .get (eq (JWK_SET_URI ))).willReturn (mock (Cache .ValueWrapper .class ));
656
684
// @formatter:off
657
685
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder .withJwkSetUri (JWK_SET_URI )
658
686
.cache (cache )
659
687
.restOperations (restOperations )
660
688
.build ();
661
689
// @formatter:on
662
690
jwtDecoder .decode (SIGNED_JWT );
663
- verify (cache ).get (eq (JWK_SET_URI ), any (Callable .class ));
691
+ verify (cache ).get (eq (JWK_SET_URI ), eq (String .class ));
692
+ verify (cache , times (2 )).get (eq (JWK_SET_URI ));
664
693
verifyNoMoreInteractions (cache );
665
694
verifyNoInteractions (restOperations );
666
695
}
667
696
697
+ // gh-11621
668
698
@ Test
669
699
public void decodeWhenCacheAndUnknownKidShouldTriggerFetchOfJwkSet () throws JOSEException {
670
700
RestOperations restOperations = mock (RestOperations .class );
671
-
672
701
Cache cache = mock (Cache .class );
673
- given (cache .get (eq (JWK_SET_URI ), any (Callable .class ))).willReturn (JWK_SET );
674
-
675
- RSAKey rsaJWK = new RSAKeyGenerator (2048 )
676
- .keyID ("new_kid" )
677
- .generate ();
678
- String jwkSetWithNewKid = new JWKSet (rsaJWK ).toPublicJWKSet ().toString ();
702
+ given (cache .get (eq (JWK_SET_URI ), eq (String .class ))).willReturn (JWK_SET );
679
703
given (restOperations .exchange (any (RequestEntity .class ), eq (String .class )))
680
- .willReturn (new ResponseEntity <>(jwkSetWithNewKid , HttpStatus .OK ));
704
+ .willReturn (new ResponseEntity <>(NEW_KID_JWK_SET , HttpStatus .OK ));
681
705
682
706
// @formatter:off
683
707
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder .withJwkSetUri (JWK_SET_URI )
@@ -687,32 +711,21 @@ public void decodeWhenCacheAndUnknownKidShouldTriggerFetchOfJwkSet() throws JOSE
687
711
// @formatter:on
688
712
689
713
// Decode JWT with new KID
690
- JWSSigner signer = new RSASSASigner (rsaJWK );
691
- JWTClaimsSet claimsSet = new JWTClaimsSet .Builder ()
692
- .expirationTime (Date .from (Instant .now ().plusSeconds (60 )))
693
- .build ();
694
- SignedJWT signedJWT = new SignedJWT (new JWSHeader .Builder (JWSAlgorithm .RS256 ).keyID (rsaJWK .getKeyID ()).build (), claimsSet );
695
- signedJWT .sign (signer );
696
- String token = signedJWT .serialize ();
697
-
698
- jwtDecoder .decode (token );
714
+ jwtDecoder .decode (NEW_KID_SIGNED_JWT );
699
715
700
- ArgumentCaptor <RequestEntity > requestEntityCaptor = ArgumentCaptor .forClass (RequestEntity .class );
716
+ ArgumentCaptor <RequestEntity > requestEntityCaptor = ArgumentCaptor .forClass (RequestEntity .class );
701
717
verify (restOperations ).exchange (requestEntityCaptor .capture (), eq (String .class ));
702
718
verifyNoMoreInteractions (restOperations );
703
- assertThat (requestEntityCaptor .getValue ().getHeaders ().getAccept ()).contains (MediaType .APPLICATION_JSON , APPLICATION_JWK_SET_JSON );
719
+ assertThat (requestEntityCaptor .getValue ().getHeaders ().getAccept ()).contains (MediaType .APPLICATION_JSON ,
720
+ APPLICATION_JWK_SET_JSON );
704
721
}
705
722
723
+ // gh-11621
706
724
@ Test
707
725
public void decodeWithoutCacheSpecifiedAndUnknownKidShouldTriggerFetchOfJwkSet () throws JOSEException {
708
726
RestOperations restOperations = mock (RestOperations .class );
709
-
710
- RSAKey rsaJWK = new RSAKeyGenerator (2048 )
711
- .keyID ("new_kid" )
712
- .generate ();
713
- String jwkSetWithNewKid = new JWKSet (rsaJWK ).toPublicJWKSet ().toString ();
714
- given (restOperations .exchange (any (RequestEntity .class ), eq (String .class )))
715
- .willReturn (new ResponseEntity <>(JWK_SET , HttpStatus .OK ), new ResponseEntity <>(jwkSetWithNewKid , HttpStatus .OK ));
727
+ given (restOperations .exchange (any (RequestEntity .class ), eq (String .class ))).willReturn (
728
+ new ResponseEntity <>(JWK_SET , HttpStatus .OK ), new ResponseEntity <>(NEW_KID_JWK_SET , HttpStatus .OK ));
716
729
717
730
// @formatter:off
718
731
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder .withJwkSetUri (JWK_SET_URI )
@@ -722,22 +735,16 @@ public void decodeWithoutCacheSpecifiedAndUnknownKidShouldTriggerFetchOfJwkSet()
722
735
jwtDecoder .decode (SIGNED_JWT );
723
736
724
737
// Decode JWT with new KID
725
- JWSSigner signer = new RSASSASigner (rsaJWK );
726
- JWTClaimsSet claimsSet = new JWTClaimsSet .Builder ()
727
- .expirationTime (Date .from (Instant .now ().plusSeconds (60 )))
728
- .build ();
729
- SignedJWT signedJWT = new SignedJWT (new JWSHeader .Builder (JWSAlgorithm .RS256 ).keyID (rsaJWK .getKeyID ()).build (), claimsSet );
730
- signedJWT .sign (signer );
731
- String token = signedJWT .serialize ();
732
-
733
- jwtDecoder .decode (token );
738
+ jwtDecoder .decode (NEW_KID_SIGNED_JWT );
734
739
735
- ArgumentCaptor <RequestEntity > requestEntityCaptor = ArgumentCaptor .forClass (RequestEntity .class );
740
+ ArgumentCaptor <RequestEntity > requestEntityCaptor = ArgumentCaptor .forClass (RequestEntity .class );
736
741
verify (restOperations , times (2 )).exchange (requestEntityCaptor .capture (), eq (String .class ));
737
742
verifyNoMoreInteractions (restOperations );
738
743
List <RequestEntity > requestEntities = requestEntityCaptor .getAllValues ();
739
- assertThat (requestEntities .get (0 ).getHeaders ().getAccept ()).contains (MediaType .APPLICATION_JSON , APPLICATION_JWK_SET_JSON );
740
- assertThat (requestEntities .get (1 ).getHeaders ().getAccept ()).contains (MediaType .APPLICATION_JSON , APPLICATION_JWK_SET_JSON );
744
+ assertThat (requestEntities .get (0 ).getHeaders ().getAccept ()).contains (MediaType .APPLICATION_JSON ,
745
+ APPLICATION_JWK_SET_JSON );
746
+ assertThat (requestEntities .get (1 ).getHeaders ().getAccept ()).contains (MediaType .APPLICATION_JSON ,
747
+ APPLICATION_JWK_SET_JSON );
741
748
}
742
749
743
750
@ Test
@@ -758,6 +765,27 @@ public void decodeWhenCacheIsConfiguredAndValueLoaderErrorsThenThrowsJwtExceptio
758
765
// @formatter:on
759
766
}
760
767
768
+ // gh-11621
769
+ @ Test
770
+ public void decodeWhenCacheIsConfiguredAndParseFailsOnCachedValueThenExceptionIgnored () {
771
+ RestOperations restOperations = mock (RestOperations .class );
772
+ Cache cache = mock (Cache .class );
773
+ given (cache .get (eq (JWK_SET_URI ), eq (String .class ))).willReturn (JWK_SET );
774
+ given (cache .get (eq (JWK_SET_URI ))).willReturn (mock (Cache .ValueWrapper .class ));
775
+ // @formatter:off
776
+ NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder .withJwkSetUri (JWK_SET_URI )
777
+ .cache (cache )
778
+ .restOperations (restOperations )
779
+ .build ();
780
+ // @formatter:on
781
+ jwtDecoder .decode (SIGNED_JWT );
782
+ verify (cache ).get (eq (JWK_SET_URI ), eq (String .class ));
783
+ verify (cache , times (2 )).get (eq (JWK_SET_URI ));
784
+ verifyNoMoreInteractions (cache );
785
+ verifyNoInteractions (restOperations );
786
+
787
+ }
788
+
761
789
// gh-8730
762
790
@ Test
763
791
public void withJwkSetUriWhenUsingCustomTypeHeaderThenRefuseOmittedType () throws Exception {
0 commit comments