16
16
17
17
package org .springframework .security .oauth2 .jwt ;
18
18
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 ;
19
28
import com .nimbusds .jose .proc .SecurityContext ;
20
29
import com .nimbusds .jwt .JWTClaimsSet ;
21
30
import com .nimbusds .jwt .proc .JWTProcessor ;
31
+ import org .junit .BeforeClass ;
22
32
import org .junit .Test ;
23
33
24
34
import org .springframework .http .HttpStatus ;
25
35
import org .springframework .http .RequestEntity ;
26
36
import org .springframework .http .ResponseEntity ;
37
+ import org .springframework .security .oauth2 .jose .jws .JwsAlgorithms ;
27
38
import org .springframework .web .client .RestOperations ;
28
39
29
40
import static org .assertj .core .api .Assertions .assertThat ;
30
41
import static org .assertj .core .api .Assertions .assertThatCode ;
42
+ import static org .assertj .core .api .AssertionsForClassTypes .assertThatThrownBy ;
31
43
import static org .mockito .ArgumentMatchers .any ;
32
44
import static org .mockito .ArgumentMatchers .eq ;
33
45
import static org .mockito .Mockito .mock ;
39
51
* Tests for {@link JwtProcessors}
40
52
*/
41
53
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\" }]}" ;
44
55
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
+ }
45
66
46
67
@ Test
47
68
public void withJwkSetUriWhenNullOrEmptyThenThrowsException () {
@@ -68,12 +89,57 @@ public void processWhenSignedThenOk() throws Exception {
68
89
RestOperations restOperations = mockJwkSetResponse (JWK_SET );
69
90
JWTProcessor <SecurityContext > processor =
70
91
withJwkSetUri (JWK_SET_URI ).restOperations (restOperations ).build ();
71
- assertThat (processor .process (SIGNED_JWT , null ))
92
+ assertThat (processor .process (RS256_SIGNED_JWT , null ))
72
93
.extracting (JWTClaimsSet ::getExpirationTime )
73
94
.isNotNull ();
74
95
verify (restOperations ).exchange (any (RequestEntity .class ), eq (String .class ));
75
96
}
76
97
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
+
77
143
private static RestOperations mockJwkSetResponse (String response ) {
78
144
RestOperations restOperations = mock (RestOperations .class );
79
145
when (restOperations .exchange (any (RequestEntity .class ), eq (String .class )))
0 commit comments