1515 */
1616package org .springframework .security .oauth2 .client .oidc .authentication ;
1717
18- import java .util .Map ;
19- import java .util .concurrent .ConcurrentHashMap ;
20- import java .util .function .Function ;
21-
2218import org .springframework .security .oauth2 .client .registration .ClientRegistration ;
2319import org .springframework .security .oauth2 .core .OAuth2AuthenticationException ;
2420import org .springframework .security .oauth2 .core .OAuth2Error ;
2521import org .springframework .security .oauth2 .core .OAuth2TokenValidator ;
2622import 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 ;
2726import org .springframework .security .oauth2 .jwt .Jwt ;
2827import org .springframework .security .oauth2 .jwt .JwtDecoder ;
2928import org .springframework .security .oauth2 .jwt .JwtDecoderFactory ;
3029import org .springframework .security .oauth2 .jwt .NimbusJwtDecoder ;
3130import org .springframework .util .Assert ;
3231import org .springframework .util .StringUtils ;
3332
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+
3440import static org .springframework .security .oauth2 .jwt .NimbusJwtDecoder .withJwkSetUri ;
41+ import static org .springframework .security .oauth2 .jwt .NimbusJwtDecoder .withSecretKey ;
3542
3643/**
3744 * A {@link JwtDecoderFactory factory} that provides a {@link JwtDecoder}
4754 */
4855public final class OidcIdTokenDecoderFactory implements JwtDecoderFactory <ClientRegistration > {
4956 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+ };
5064 private final Map <String , JwtDecoder > jwtDecoders = new ConcurrentHashMap <>();
5165 private Function <ClientRegistration , OAuth2TokenValidator <Jwt >> jwtValidatorFactory = OidcIdTokenValidator ::new ;
66+ private Function <ClientRegistration , JwsAlgorithm > jwsAlgorithmResolver = clientRegistration -> SignatureAlgorithm .RS256 ;
5267
5368 @ Override
5469 public JwtDecoder createDecoder (ClientRegistration clientRegistration ) {
5570 Assert .notNull (clientRegistration , "clientRegistration cannot be null" );
5671 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 )) {
5896 OAuth2Error oauth2Error = new OAuth2Error (
5997 MISSING_SIGNATURE_VERIFIER_ERROR_CODE ,
6098 "Failed to find a Signature Verifier for Client Registration: '" +
@@ -64,12 +102,42 @@ public JwtDecoder createDecoder(ClientRegistration clientRegistration) {
64102 );
65103 throw new OAuth2AuthenticationException (oauth2Error , oauth2Error .toString ());
66104 }
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 ());
73141 }
74142
75143 /**
@@ -82,4 +150,17 @@ public final void setJwtValidatorFactory(Function<ClientRegistration, OAuth2Toke
82150 Assert .notNull (jwtValidatorFactory , "jwtValidatorFactory cannot be null" );
83151 this .jwtValidatorFactory = jwtValidatorFactory ;
84152 }
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+ }
85166}
0 commit comments