15
15
*/
16
16
package org .springframework .security .oauth2 .client .oidc .authentication ;
17
17
18
- import java .util .Map ;
19
- import java .util .concurrent .ConcurrentHashMap ;
20
- import java .util .function .Function ;
21
-
22
18
import org .springframework .security .oauth2 .client .registration .ClientRegistration ;
23
19
import org .springframework .security .oauth2 .core .OAuth2AuthenticationException ;
24
20
import org .springframework .security .oauth2 .core .OAuth2Error ;
25
21
import org .springframework .security .oauth2 .core .OAuth2TokenValidator ;
26
22
import org .springframework .security .oauth2 .core .oidc .OidcIdToken ;
23
+ import org .springframework .security .oauth2 .jose .jws .JwsAlgorithm ;
24
+ import org .springframework .security .oauth2 .jose .jws .MacAlgorithm ;
25
+ import org .springframework .security .oauth2 .jose .jws .SignatureAlgorithm ;
27
26
import org .springframework .security .oauth2 .jwt .Jwt ;
28
27
import org .springframework .security .oauth2 .jwt .JwtDecoder ;
29
28
import org .springframework .security .oauth2 .jwt .JwtDecoderFactory ;
30
29
import org .springframework .security .oauth2 .jwt .NimbusJwtDecoder ;
31
30
import org .springframework .util .Assert ;
32
31
import org .springframework .util .StringUtils ;
33
32
33
+ import javax .crypto .spec .SecretKeySpec ;
34
+ import java .nio .charset .StandardCharsets ;
35
+ import java .util .HashMap ;
36
+ import java .util .Map ;
37
+ import java .util .concurrent .ConcurrentHashMap ;
38
+ import java .util .function .Function ;
39
+
34
40
import static org .springframework .security .oauth2 .jwt .NimbusJwtDecoder .withJwkSetUri ;
41
+ import static org .springframework .security .oauth2 .jwt .NimbusJwtDecoder .withSecretKey ;
35
42
36
43
/**
37
44
* A {@link JwtDecoderFactory factory} that provides a {@link JwtDecoder}
47
54
*/
48
55
public final class OidcIdTokenDecoderFactory implements JwtDecoderFactory <ClientRegistration > {
49
56
private static final String MISSING_SIGNATURE_VERIFIER_ERROR_CODE = "missing_signature_verifier" ;
57
+ private static Map <JwsAlgorithm , String > jcaAlgorithmMappings = new HashMap <JwsAlgorithm , String >() {
58
+ {
59
+ put (MacAlgorithm .HS256 , "HmacSHA256" );
60
+ put (MacAlgorithm .HS384 , "HmacSHA384" );
61
+ put (MacAlgorithm .HS512 , "HmacSHA512" );
62
+ }
63
+ };
50
64
private final Map <String , JwtDecoder > jwtDecoders = new ConcurrentHashMap <>();
51
65
private Function <ClientRegistration , OAuth2TokenValidator <Jwt >> jwtValidatorFactory = OidcIdTokenValidator ::new ;
66
+ private Function <ClientRegistration , JwsAlgorithm > jwsAlgorithmResolver = clientRegistration -> SignatureAlgorithm .RS256 ;
52
67
53
68
@ Override
54
69
public JwtDecoder createDecoder (ClientRegistration clientRegistration ) {
55
70
Assert .notNull (clientRegistration , "clientRegistration cannot be null" );
56
71
return this .jwtDecoders .computeIfAbsent (clientRegistration .getRegistrationId (), key -> {
57
- if (!StringUtils .hasText (clientRegistration .getProviderDetails ().getJwkSetUri ())) {
72
+ NimbusJwtDecoder jwtDecoder = buildDecoder (clientRegistration );
73
+ OAuth2TokenValidator <Jwt > jwtValidator = this .jwtValidatorFactory .apply (clientRegistration );
74
+ jwtDecoder .setJwtValidator (jwtValidator );
75
+ return jwtDecoder ;
76
+ });
77
+ }
78
+
79
+ private NimbusJwtDecoder buildDecoder (ClientRegistration clientRegistration ) {
80
+ JwsAlgorithm jwsAlgorithm = this .jwsAlgorithmResolver .apply (clientRegistration );
81
+ if (jwsAlgorithm != null && SignatureAlgorithm .class .isAssignableFrom (jwsAlgorithm .getClass ())) {
82
+ // https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation
83
+ //
84
+ // 6. If the ID Token is received via direct communication between the Client
85
+ // and the Token Endpoint (which it is in this flow),
86
+ // the TLS server validation MAY be used to validate the issuer in place of checking the token signature.
87
+ // The Client MUST validate the signature of all other ID Tokens according to JWS [JWS]
88
+ // using the algorithm specified in the JWT alg Header Parameter.
89
+ // The Client MUST use the keys provided by the Issuer.
90
+ //
91
+ // 7. The alg value SHOULD be the default of RS256 or the algorithm sent by the Client
92
+ // in the id_token_signed_response_alg parameter during Registration.
93
+
94
+ String jwkSetUri = clientRegistration .getProviderDetails ().getJwkSetUri ();
95
+ if (!StringUtils .hasText (jwkSetUri )) {
58
96
OAuth2Error oauth2Error = new OAuth2Error (
59
97
MISSING_SIGNATURE_VERIFIER_ERROR_CODE ,
60
98
"Failed to find a Signature Verifier for Client Registration: '" +
@@ -64,12 +102,42 @@ public JwtDecoder createDecoder(ClientRegistration clientRegistration) {
64
102
);
65
103
throw new OAuth2AuthenticationException (oauth2Error , oauth2Error .toString ());
66
104
}
67
- String jwkSetUri = clientRegistration .getProviderDetails ().getJwkSetUri ();
68
- NimbusJwtDecoder jwtDecoder = withJwkSetUri (jwkSetUri ).build ();
69
- OAuth2TokenValidator <Jwt > jwtValidator = this .jwtValidatorFactory .apply (clientRegistration );
70
- jwtDecoder .setJwtValidator (jwtValidator );
71
- return jwtDecoder ;
72
- });
105
+ return withJwkSetUri (jwkSetUri ).jwsAlgorithm (jwsAlgorithm ).build ();
106
+ } else if (jwsAlgorithm != null && MacAlgorithm .class .isAssignableFrom (jwsAlgorithm .getClass ())) {
107
+ // https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation
108
+ //
109
+ // 8. If the JWT alg Header Parameter uses a MAC based algorithm such as HS256, HS384, or HS512,
110
+ // the octets of the UTF-8 representation of the client_secret
111
+ // corresponding to the client_id contained in the aud (audience) Claim
112
+ // are used as the key to validate the signature.
113
+ // For MAC based algorithms, the behavior is unspecified if the aud is multi-valued or
114
+ // if an azp value is present that is different than the aud value.
115
+
116
+ String clientSecret = clientRegistration .getClientSecret ();
117
+ if (!StringUtils .hasText (clientSecret )) {
118
+ OAuth2Error oauth2Error = new OAuth2Error (
119
+ MISSING_SIGNATURE_VERIFIER_ERROR_CODE ,
120
+ "Failed to find a Signature Verifier for Client Registration: '" +
121
+ clientRegistration .getRegistrationId () +
122
+ "'. Check to ensure you have configured the client secret." ,
123
+ null
124
+ );
125
+ throw new OAuth2AuthenticationException (oauth2Error , oauth2Error .toString ());
126
+ }
127
+ SecretKeySpec secretKeySpec = new SecretKeySpec (
128
+ clientSecret .getBytes (StandardCharsets .UTF_8 ), jcaAlgorithmMappings .get (jwsAlgorithm ));
129
+ return withSecretKey (secretKeySpec ).macAlgorithm ((MacAlgorithm ) jwsAlgorithm ).build ();
130
+ }
131
+
132
+ OAuth2Error oauth2Error = new OAuth2Error (
133
+ MISSING_SIGNATURE_VERIFIER_ERROR_CODE ,
134
+ "Failed to find a Signature Verifier for Client Registration: '" +
135
+ clientRegistration .getRegistrationId () +
136
+ "'. Check to ensure you have configured a valid JWS Algorithm: '" +
137
+ jwsAlgorithm + "'" ,
138
+ null
139
+ );
140
+ throw new OAuth2AuthenticationException (oauth2Error , oauth2Error .toString ());
73
141
}
74
142
75
143
/**
@@ -82,4 +150,17 @@ public final void setJwtValidatorFactory(Function<ClientRegistration, OAuth2Toke
82
150
Assert .notNull (jwtValidatorFactory , "jwtValidatorFactory cannot be null" );
83
151
this .jwtValidatorFactory = jwtValidatorFactory ;
84
152
}
153
+
154
+ /**
155
+ * Sets the resolver that provides the expected {@link JwsAlgorithm JWS algorithm}
156
+ * used for the signature or MAC on the {@link OidcIdToken ID Token}.
157
+ * The default resolves to {@link SignatureAlgorithm#RS256 RS256} for all {@link ClientRegistration clients}.
158
+ *
159
+ * @param jwsAlgorithmResolver the resolver that provides the expected {@link JwsAlgorithm JWS algorithm}
160
+ * for a specific {@link ClientRegistration client}
161
+ */
162
+ public final void setJwsAlgorithmResolver (Function <ClientRegistration , JwsAlgorithm > jwsAlgorithmResolver ) {
163
+ Assert .notNull (jwsAlgorithmResolver , "jwsAlgorithmResolver cannot be null" );
164
+ this .jwsAlgorithmResolver = jwsAlgorithmResolver ;
165
+ }
85
166
}
0 commit comments