Bläddra i källkod

Add nonce to OIDC Authentication Request

Fixes gh-4442
Mark Heckler 6 år sedan
förälder
incheckning
da9f027fa4
13 ändrade filer med 295 tillägg och 69 borttagningar
  1. 7 3
      config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2ClientConfigurerTests.java
  2. 29 1
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcAuthorizationCodeAuthenticationProvider.java
  3. 37 2
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcAuthorizationCodeReactiveAuthenticationManager.java
  4. 3 0
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcIdTokenDecoderFactory.java
  5. 0 7
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcIdTokenValidator.java
  6. 3 0
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/ReactiveOidcIdTokenDecoderFactory.java
  7. 34 7
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/DefaultOAuth2AuthorizationRequestResolver.java
  8. 36 11
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/server/DefaultServerOAuth2AuthorizationRequestResolver.java
  9. 102 18
      oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/authentication/OidcAuthorizationCodeAuthenticationProviderTests.java
  10. 22 13
      oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/DefaultOAuth2AuthorizationRequestResolverTests.java
  11. 9 4
      oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizationRequestRedirectFilterTests.java
  12. 6 2
      oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/server/DefaultServerOAuth2AuthorizationRequestResolverTests.java
  13. 7 1
      oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/oidc/endpoint/OidcParameterNames.java

+ 7 - 3
config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2ClientConfigurerTests.java

@@ -75,6 +75,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
  * Tests for {@link OAuth2ClientConfigurer}.
  * Tests for {@link OAuth2ClientConfigurer}.
  *
  *
  * @author Joe Grandja
  * @author Joe Grandja
+ * @author Mark Heckler
  */
  */
 public class OAuth2ClientConfigurerTests {
 public class OAuth2ClientConfigurerTests {
 	private static ClientRegistrationRepository clientRegistrationRepository;
 	private static ClientRegistrationRepository clientRegistrationRepository;
@@ -138,7 +139,8 @@ public class OAuth2ClientConfigurerTests {
 		assertThat(mvcResult.getResponse().getRedirectedUrl()).matches("https://provider.com/oauth2/authorize\\?" +
 		assertThat(mvcResult.getResponse().getRedirectedUrl()).matches("https://provider.com/oauth2/authorize\\?" +
 				"response_type=code&client_id=client-1&" +
 				"response_type=code&client_id=client-1&" +
 				"scope=user&state=.{15,}&" +
 				"scope=user&state=.{15,}&" +
-				"redirect_uri=http://localhost/client-1");
+				"redirect_uri=http://localhost/client-1&" +
+				"nonce=([a-zA-Z0-9\\-\\.\\_\\~]){43}");
 	}
 	}
 
 
 	@Test
 	@Test
@@ -151,7 +153,8 @@ public class OAuth2ClientConfigurerTests {
 		assertThat(mvcResult.getResponse().getRedirectedUrl()).matches("https://provider.com/oauth2/authorize\\?" +
 		assertThat(mvcResult.getResponse().getRedirectedUrl()).matches("https://provider.com/oauth2/authorize\\?" +
 				"response_type=code&client_id=client-1&" +
 				"response_type=code&client_id=client-1&" +
 				"scope=user&state=.{15,}&" +
 				"scope=user&state=.{15,}&" +
-				"redirect_uri=http://localhost/client-1");
+				"redirect_uri=http://localhost/client-1&" +
+				"nonce=([a-zA-Z0-9\\-\\.\\_\\~]){43}");
 	}
 	}
 
 
 	@Test
 	@Test
@@ -203,7 +206,8 @@ public class OAuth2ClientConfigurerTests {
 		assertThat(mvcResult.getResponse().getRedirectedUrl()).matches("https://provider.com/oauth2/authorize\\?" +
 		assertThat(mvcResult.getResponse().getRedirectedUrl()).matches("https://provider.com/oauth2/authorize\\?" +
 				"response_type=code&client_id=client-1&" +
 				"response_type=code&client_id=client-1&" +
 				"scope=user&state=.{15,}&" +
 				"scope=user&state=.{15,}&" +
-				"redirect_uri=http://localhost/client-1");
+				"redirect_uri=http://localhost/client-1&" +
+				"nonce=([a-zA-Z0-9\\-\\.\\_\\~]){43}");
 
 
 		verify(requestCache).saveRequest(any(HttpServletRequest.class), any(HttpServletResponse.class));
 		verify(requestCache).saveRequest(any(HttpServletRequest.class), any(HttpServletResponse.class));
 	}
 	}

+ 29 - 1
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcAuthorizationCodeAuthenticationProvider.java

@@ -43,6 +43,10 @@ import org.springframework.security.oauth2.jwt.JwtDecoderFactory;
 import org.springframework.security.oauth2.jwt.JwtException;
 import org.springframework.security.oauth2.jwt.JwtException;
 import org.springframework.util.Assert;
 import org.springframework.util.Assert;
 
 
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Base64;
 import java.util.Collection;
 import java.util.Collection;
 import java.util.Map;
 import java.util.Map;
 
 
@@ -61,6 +65,7 @@ import java.util.Map;
  * to complete the authentication.
  * to complete the authentication.
  *
  *
  * @author Joe Grandja
  * @author Joe Grandja
+ * @author Mark Heckler
  * @since 5.0
  * @since 5.0
  * @see OAuth2LoginAuthenticationToken
  * @see OAuth2LoginAuthenticationToken
  * @see OAuth2AccessTokenResponseClient
  * @see OAuth2AccessTokenResponseClient
@@ -75,6 +80,7 @@ public class OidcAuthorizationCodeAuthenticationProvider implements Authenticati
 	private static final String INVALID_STATE_PARAMETER_ERROR_CODE = "invalid_state_parameter";
 	private static final String INVALID_STATE_PARAMETER_ERROR_CODE = "invalid_state_parameter";
 	private static final String INVALID_REDIRECT_URI_PARAMETER_ERROR_CODE = "invalid_redirect_uri_parameter";
 	private static final String INVALID_REDIRECT_URI_PARAMETER_ERROR_CODE = "invalid_redirect_uri_parameter";
 	private static final String INVALID_ID_TOKEN_ERROR_CODE = "invalid_id_token";
 	private static final String INVALID_ID_TOKEN_ERROR_CODE = "invalid_id_token";
+	private static final String INVALID_NONCE_ERROR_CODE = "invalid_nonce";
 	private final OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient;
 	private final OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient;
 	private final OAuth2UserService<OidcUserRequest, OidcUser> userService;
 	private final OAuth2UserService<OidcUserRequest, OidcUser> userService;
 	private JwtDecoderFactory<ClientRegistration> jwtDecoderFactory = new OidcIdTokenDecoderFactory();
 	private JwtDecoderFactory<ClientRegistration> jwtDecoderFactory = new OidcIdTokenDecoderFactory();
@@ -152,7 +158,23 @@ public class OidcAuthorizationCodeAuthenticationProvider implements Authenticati
 				null);
 				null);
 			throw new OAuth2AuthenticationException(invalidIdTokenError, invalidIdTokenError.toString());
 			throw new OAuth2AuthenticationException(invalidIdTokenError, invalidIdTokenError.toString());
 		}
 		}
-		OidcIdToken idToken = createOidcToken(clientRegistration, accessTokenResponse);
+			OidcIdToken idToken = createOidcToken(clientRegistration, accessTokenResponse);
+
+		String requestNonce = authorizationRequest.getAttribute(OidcParameterNames.NONCE);
+		if (requestNonce != null) {
+			String nonceHash;
+
+			try {
+				nonceHash = createHash(requestNonce);
+			} catch (NoSuchAlgorithmException e) {
+				throw new OAuth2AuthenticationException(new OAuth2Error(INVALID_NONCE_ERROR_CODE));
+			}
+
+			String nonceHashClaim = idToken.getClaim(OidcParameterNames.NONCE);
+			if (nonceHashClaim == null || !nonceHashClaim.equals(nonceHash)) {
+				throw new OAuth2AuthenticationException(new OAuth2Error(INVALID_NONCE_ERROR_CODE));
+			}
+		}
 
 
 		OidcUser oidcUser = this.userService.loadUser(new OidcUserRequest(
 		OidcUser oidcUser = this.userService.loadUser(new OidcUserRequest(
 				clientRegistration, accessTokenResponse.getAccessToken(), idToken, additionalParameters));
 				clientRegistration, accessTokenResponse.getAccessToken(), idToken, additionalParameters));
@@ -211,4 +233,10 @@ public class OidcAuthorizationCodeAuthenticationProvider implements Authenticati
 		OidcIdToken idToken = new OidcIdToken(jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), jwt.getClaims());
 		OidcIdToken idToken = new OidcIdToken(jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), jwt.getClaims());
 		return idToken;
 		return idToken;
 	}
 	}
+
+	private String createHash(String nonce) throws NoSuchAlgorithmException {
+		MessageDigest md = MessageDigest.getInstance("SHA-256");
+		byte[] digest = md.digest(nonce.getBytes(StandardCharsets.US_ASCII));
+		return Base64.getUrlEncoder().withoutPadding().encodeToString(digest);
+	}
 }
 }

+ 37 - 2
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcAuthorizationCodeReactiveAuthenticationManager.java

@@ -43,13 +43,17 @@ import org.springframework.security.oauth2.jwt.ReactiveJwtDecoderFactory;
 import org.springframework.util.Assert;
 import org.springframework.util.Assert;
 import reactor.core.publisher.Mono;
 import reactor.core.publisher.Mono;
 
 
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Base64;
 import java.util.Collection;
 import java.util.Collection;
 import java.util.Map;
 import java.util.Map;
 
 
 /**
 /**
  * An implementation of an {@link org.springframework.security.authentication.AuthenticationProvider} for OAuth 2.0 Login,
  * An implementation of an {@link org.springframework.security.authentication.AuthenticationProvider} for OAuth 2.0 Login,
  * which leverages the OAuth 2.0 Authorization Code Grant Flow.
  * which leverages the OAuth 2.0 Authorization Code Grant Flow.
- *
+ * <p>
  * This {@link org.springframework.security.authentication.AuthenticationProvider} is responsible for authenticating
  * This {@link org.springframework.security.authentication.AuthenticationProvider} is responsible for authenticating
  * an Authorization Code credential with the Authorization Server's Token Endpoint
  * an Authorization Code credential with the Authorization Server's Token Endpoint
  * and if valid, exchanging it for an Access Token credential.
  * and if valid, exchanging it for an Access Token credential.
@@ -77,6 +81,7 @@ public class OidcAuthorizationCodeReactiveAuthenticationManager implements
 	private static final String INVALID_STATE_PARAMETER_ERROR_CODE = "invalid_state_parameter";
 	private static final String INVALID_STATE_PARAMETER_ERROR_CODE = "invalid_state_parameter";
 	private static final String INVALID_REDIRECT_URI_PARAMETER_ERROR_CODE = "invalid_redirect_uri_parameter";
 	private static final String INVALID_REDIRECT_URI_PARAMETER_ERROR_CODE = "invalid_redirect_uri_parameter";
 	private static final String INVALID_ID_TOKEN_ERROR_CODE = "invalid_id_token";
 	private static final String INVALID_ID_TOKEN_ERROR_CODE = "invalid_id_token";
+	private static final String INVALID_NONCE_ERROR_CODE = "invalid_nonce";
 
 
 	private final ReactiveOAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient;
 	private final ReactiveOAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient;
 
 
@@ -170,7 +175,8 @@ public class OidcAuthorizationCodeReactiveAuthenticationManager implements
 		}
 		}
 
 
 		return createOidcToken(clientRegistration, accessTokenResponse)
 		return createOidcToken(clientRegistration, accessTokenResponse)
-				.map(idToken ->  new OidcUserRequest(clientRegistration, accessToken, idToken, additionalParameters))
+				.doOnNext(idToken -> validateNonce(authorizationCodeAuthentication, idToken))
+				.map(idToken -> new OidcUserRequest(clientRegistration, accessToken, idToken, additionalParameters))
 				.flatMap(this.userService::loadUser)
 				.flatMap(this.userService::loadUser)
 				.map(oauth2User -> {
 				.map(oauth2User -> {
 					Collection<? extends GrantedAuthority> mappedAuthorities =
 					Collection<? extends GrantedAuthority> mappedAuthorities =
@@ -192,4 +198,33 @@ public class OidcAuthorizationCodeReactiveAuthenticationManager implements
 		return jwtDecoder.decode(rawIdToken)
 		return jwtDecoder.decode(rawIdToken)
 				.map(jwt -> new OidcIdToken(jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), jwt.getClaims()));
 				.map(jwt -> new OidcIdToken(jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), jwt.getClaims()));
 	}
 	}
+
+	private Mono<OidcIdToken> validateNonce(OAuth2AuthorizationCodeAuthenticationToken authorizationCodeAuthentication, OidcIdToken idToken) {
+		String requestNonce = authorizationCodeAuthentication
+				.getAuthorizationExchange()
+				.getAuthorizationRequest()
+				.getAttribute(OidcParameterNames.NONCE);
+		if (requestNonce != null) {
+			String nonceHash;
+
+			try {
+				nonceHash = createHash(requestNonce);
+			} catch (NoSuchAlgorithmException e) {
+				throw new OAuth2AuthenticationException(new OAuth2Error(INVALID_NONCE_ERROR_CODE));
+			}
+
+			String nonceHashClaim = idToken.getClaim(OidcParameterNames.NONCE);
+			if (nonceHashClaim == null || !nonceHashClaim.equals(nonceHash)) {
+				throw new OAuth2AuthenticationException(new OAuth2Error(INVALID_NONCE_ERROR_CODE));
+			}
+		}
+
+		return Mono.just(idToken);
+	}
+
+	private String createHash(String nonce) throws NoSuchAlgorithmException {
+		MessageDigest md = MessageDigest.getInstance("SHA-256");
+		byte[] digest = md.digest(nonce.getBytes(StandardCharsets.US_ASCII));
+		return Base64.getUrlEncoder().withoutPadding().encodeToString(digest);
+	}
 }
 }

+ 3 - 0
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcIdTokenDecoderFactory.java

@@ -57,6 +57,7 @@ import static org.springframework.security.oauth2.jwt.NimbusJwtDecoder.withSecre
  *
  *
  * @author Joe Grandja
  * @author Joe Grandja
  * @author Rafael Dominguez
  * @author Rafael Dominguez
+ * @author Mark Heckler
  * @since 5.2
  * @since 5.2
  * @see JwtDecoderFactory
  * @see JwtDecoderFactory
  * @see ClientRegistration
  * @see ClientRegistration
@@ -88,12 +89,14 @@ public final class OidcIdTokenDecoderFactory implements JwtDecoderFactory<Client
 		Converter<Object, ?> booleanConverter = getConverter(TypeDescriptor.valueOf(Boolean.class));
 		Converter<Object, ?> booleanConverter = getConverter(TypeDescriptor.valueOf(Boolean.class));
 		Converter<Object, ?> instantConverter = getConverter(TypeDescriptor.valueOf(Instant.class));
 		Converter<Object, ?> instantConverter = getConverter(TypeDescriptor.valueOf(Instant.class));
 		Converter<Object, ?> urlConverter = getConverter(TypeDescriptor.valueOf(URL.class));
 		Converter<Object, ?> urlConverter = getConverter(TypeDescriptor.valueOf(URL.class));
+		Converter<Object, ?> stringConverter = getConverter(TypeDescriptor.valueOf(String.class));
 		Converter<Object, ?> collectionStringConverter = getConverter(
 		Converter<Object, ?> collectionStringConverter = getConverter(
 				TypeDescriptor.collection(Collection.class, TypeDescriptor.valueOf(String.class)));
 				TypeDescriptor.collection(Collection.class, TypeDescriptor.valueOf(String.class)));
 
 
 		Map<String, Converter<Object, ?>> claimTypeConverters = new HashMap<>();
 		Map<String, Converter<Object, ?>> claimTypeConverters = new HashMap<>();
 		claimTypeConverters.put(IdTokenClaimNames.ISS, urlConverter);
 		claimTypeConverters.put(IdTokenClaimNames.ISS, urlConverter);
 		claimTypeConverters.put(IdTokenClaimNames.AUD, collectionStringConverter);
 		claimTypeConverters.put(IdTokenClaimNames.AUD, collectionStringConverter);
+		claimTypeConverters.put(IdTokenClaimNames.NONCE, stringConverter);
 		claimTypeConverters.put(IdTokenClaimNames.EXP, instantConverter);
 		claimTypeConverters.put(IdTokenClaimNames.EXP, instantConverter);
 		claimTypeConverters.put(IdTokenClaimNames.IAT, instantConverter);
 		claimTypeConverters.put(IdTokenClaimNames.IAT, instantConverter);
 		claimTypeConverters.put(IdTokenClaimNames.AUTH_TIME, instantConverter);
 		claimTypeConverters.put(IdTokenClaimNames.AUTH_TIME, instantConverter);

+ 0 - 7
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcIdTokenValidator.java

@@ -107,13 +107,6 @@ public final class OidcIdTokenValidator implements OAuth2TokenValidator<Jwt> {
 			invalidClaims.put(IdTokenClaimNames.IAT, idToken.getIssuedAt());
 			invalidClaims.put(IdTokenClaimNames.IAT, idToken.getIssuedAt());
 		}
 		}
 
 
-		// 11. If a nonce value was sent in the Authentication Request,
-		// a nonce Claim MUST be present and its value checked to verify
-		// that it is the same value as the one that was sent in the Authentication Request.
-		// The Client SHOULD check the nonce value for replay attacks.
-		// The precise method for detecting replay attacks is Client specific.
-		// TODO Depends on gh-4442
-
 		if (!invalidClaims.isEmpty()) {
 		if (!invalidClaims.isEmpty()) {
 			return OAuth2TokenValidatorResult.failure(invalidIdToken(invalidClaims));
 			return OAuth2TokenValidatorResult.failure(invalidIdToken(invalidClaims));
 		}
 		}

+ 3 - 0
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/ReactiveOidcIdTokenDecoderFactory.java

@@ -57,6 +57,7 @@ import static org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder.w
  *
  *
  * @author Joe Grandja
  * @author Joe Grandja
  * @author Rafael Dominguez
  * @author Rafael Dominguez
+ * @author Mark Heckler
  * @since 5.2
  * @since 5.2
  * @see ReactiveJwtDecoderFactory
  * @see ReactiveJwtDecoderFactory
  * @see ClientRegistration
  * @see ClientRegistration
@@ -88,12 +89,14 @@ public final class ReactiveOidcIdTokenDecoderFactory implements ReactiveJwtDecod
 		Converter<Object, ?> booleanConverter = getConverter(TypeDescriptor.valueOf(Boolean.class));
 		Converter<Object, ?> booleanConverter = getConverter(TypeDescriptor.valueOf(Boolean.class));
 		Converter<Object, ?> instantConverter = getConverter(TypeDescriptor.valueOf(Instant.class));
 		Converter<Object, ?> instantConverter = getConverter(TypeDescriptor.valueOf(Instant.class));
 		Converter<Object, ?> urlConverter = getConverter(TypeDescriptor.valueOf(URL.class));
 		Converter<Object, ?> urlConverter = getConverter(TypeDescriptor.valueOf(URL.class));
+		Converter<Object, ?> stringConverter = getConverter(TypeDescriptor.valueOf(String.class));
 		Converter<Object, ?> collectionStringConverter = getConverter(
 		Converter<Object, ?> collectionStringConverter = getConverter(
 				TypeDescriptor.collection(Collection.class, TypeDescriptor.valueOf(String.class)));
 				TypeDescriptor.collection(Collection.class, TypeDescriptor.valueOf(String.class)));
 
 
 		Map<String, Converter<Object, ?>> claimTypeConverters = new HashMap<>();
 		Map<String, Converter<Object, ?>> claimTypeConverters = new HashMap<>();
 		claimTypeConverters.put(IdTokenClaimNames.ISS, urlConverter);
 		claimTypeConverters.put(IdTokenClaimNames.ISS, urlConverter);
 		claimTypeConverters.put(IdTokenClaimNames.AUD, collectionStringConverter);
 		claimTypeConverters.put(IdTokenClaimNames.AUD, collectionStringConverter);
+		claimTypeConverters.put(IdTokenClaimNames.NONCE, stringConverter);
 		claimTypeConverters.put(IdTokenClaimNames.EXP, instantConverter);
 		claimTypeConverters.put(IdTokenClaimNames.EXP, instantConverter);
 		claimTypeConverters.put(IdTokenClaimNames.IAT, instantConverter);
 		claimTypeConverters.put(IdTokenClaimNames.IAT, instantConverter);
 		claimTypeConverters.put(IdTokenClaimNames.AUTH_TIME, instantConverter);
 		claimTypeConverters.put(IdTokenClaimNames.AUTH_TIME, instantConverter);

+ 34 - 7
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/DefaultOAuth2AuthorizationRequestResolver.java

@@ -24,6 +24,7 @@ import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
 import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
 import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
 import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
 import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
 import org.springframework.security.oauth2.core.endpoint.PkceParameterNames;
 import org.springframework.security.oauth2.core.endpoint.PkceParameterNames;
+import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames;
 import org.springframework.security.web.util.UrlUtils;
 import org.springframework.security.web.util.UrlUtils;
 import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
 import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
 import org.springframework.util.Assert;
 import org.springframework.util.Assert;
@@ -51,6 +52,7 @@ import java.util.Map;
  * @author Joe Grandja
  * @author Joe Grandja
  * @author Rob Winch
  * @author Rob Winch
  * @author Eddú Meléndez
  * @author Eddú Meléndez
+ * @author Mark Heckler
  * @since 5.1
  * @since 5.1
  * @see OAuth2AuthorizationRequestResolver
  * @see OAuth2AuthorizationRequestResolver
  * @see OAuth2AuthorizationRequestRedirectFilter
  * @see OAuth2AuthorizationRequestRedirectFilter
@@ -61,7 +63,7 @@ public final class DefaultOAuth2AuthorizationRequestResolver implements OAuth2Au
 	private final ClientRegistrationRepository clientRegistrationRepository;
 	private final ClientRegistrationRepository clientRegistrationRepository;
 	private final AntPathRequestMatcher authorizationRequestMatcher;
 	private final AntPathRequestMatcher authorizationRequestMatcher;
 	private final StringKeyGenerator stateGenerator = new Base64StringKeyGenerator(Base64.getUrlEncoder());
 	private final StringKeyGenerator stateGenerator = new Base64StringKeyGenerator(Base64.getUrlEncoder());
-	private final StringKeyGenerator codeVerifierGenerator = new Base64StringKeyGenerator(Base64.getUrlEncoder().withoutPadding(), 96);
+	private final StringKeyGenerator stringKeyGenerator = new Base64StringKeyGenerator(Base64.getUrlEncoder().withoutPadding(), 96);
 
 
 	/**
 	/**
 	 * Constructs a {@code DefaultOAuth2AuthorizationRequestResolver} using the provided parameters.
 	 * Constructs a {@code DefaultOAuth2AuthorizationRequestResolver} using the provided parameters.
@@ -118,11 +120,15 @@ public final class DefaultOAuth2AuthorizationRequestResolver implements OAuth2Au
 		OAuth2AuthorizationRequest.Builder builder;
 		OAuth2AuthorizationRequest.Builder builder;
 		if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(clientRegistration.getAuthorizationGrantType())) {
 		if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(clientRegistration.getAuthorizationGrantType())) {
 			builder = OAuth2AuthorizationRequest.authorizationCode();
 			builder = OAuth2AuthorizationRequest.authorizationCode();
+			Map<String, Object> additionalParameters = new HashMap<>();
+
+			addNonceParameters(attributes, additionalParameters);
+
 			if (ClientAuthenticationMethod.NONE.equals(clientRegistration.getClientAuthenticationMethod())) {
 			if (ClientAuthenticationMethod.NONE.equals(clientRegistration.getClientAuthenticationMethod())) {
-				Map<String, Object> additionalParameters = new HashMap<>();
 				addPkceParameters(attributes, additionalParameters);
 				addPkceParameters(attributes, additionalParameters);
-				builder.additionalParameters(additionalParameters);
 			}
 			}
+
+			builder.additionalParameters(additionalParameters);
 		} else if (AuthorizationGrantType.IMPLICIT.equals(clientRegistration.getAuthorizationGrantType())) {
 		} else if (AuthorizationGrantType.IMPLICIT.equals(clientRegistration.getAuthorizationGrantType())) {
 			builder = OAuth2AuthorizationRequest.implicit();
 			builder = OAuth2AuthorizationRequest.implicit();
 		} else {
 		} else {
@@ -201,6 +207,27 @@ public final class DefaultOAuth2AuthorizationRequestResolver implements OAuth2Au
 				.toUriString();
 				.toUriString();
 	}
 	}
 
 
+	/**
+	 * Creates nonce and its hash for use in OpenID Connect Authentication Requests
+	 *
+	 * @param attributes where {@link OidcParameterNames#NONCE} is stored for the token request
+	 * @param additionalParameters where hash of {@link OidcParameterNames#NONCE} is added to the authentication request
+	 *
+	 * @since 5.2
+	 * @see <a target="_blank" href="https://openid.net/specs/openid-connect-core-1_0.html#NonceNotes">15.5.2.  Nonce Implementation Notes</a>
+	 * @see <a target="_blank" href="https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation">3.1.3.7.  ID Token Validation</a>
+	 */
+	private void addNonceParameters(Map<String, Object> attributes, Map<String, Object> additionalParameters) {
+		try {
+			String nonce = this.stringKeyGenerator.generateKey();
+			attributes.put(OidcParameterNames.NONCE, nonce);
+			
+			String nonceHash = createHash(nonce);
+			additionalParameters.put(OidcParameterNames.NONCE, nonceHash);
+		} catch (NoSuchAlgorithmException ignored) {
+		}
+	}
+
 	/**
 	/**
 	 * Creates and adds additional PKCE parameters for use in the OAuth 2.0 Authorization and Access Token Requests
 	 * Creates and adds additional PKCE parameters for use in the OAuth 2.0 Authorization and Access Token Requests
 	 *
 	 *
@@ -214,10 +241,10 @@ public final class DefaultOAuth2AuthorizationRequestResolver implements OAuth2Au
 	 * @see <a target="_blank" href="https://tools.ietf.org/html/rfc7636#section-4.2">4.2.  Client Creates the Code Challenge</a>
 	 * @see <a target="_blank" href="https://tools.ietf.org/html/rfc7636#section-4.2">4.2.  Client Creates the Code Challenge</a>
 	 */
 	 */
 	private void addPkceParameters(Map<String, Object> attributes, Map<String, Object> additionalParameters) {
 	private void addPkceParameters(Map<String, Object> attributes, Map<String, Object> additionalParameters) {
-		String codeVerifier = this.codeVerifierGenerator.generateKey();
+		String codeVerifier = this.stringKeyGenerator.generateKey();
 		attributes.put(PkceParameterNames.CODE_VERIFIER, codeVerifier);
 		attributes.put(PkceParameterNames.CODE_VERIFIER, codeVerifier);
 		try {
 		try {
-			String codeChallenge = createCodeChallenge(codeVerifier);
+			String codeChallenge = createHash(codeVerifier);
 			additionalParameters.put(PkceParameterNames.CODE_CHALLENGE, codeChallenge);
 			additionalParameters.put(PkceParameterNames.CODE_CHALLENGE, codeChallenge);
 			additionalParameters.put(PkceParameterNames.CODE_CHALLENGE_METHOD, "S256");
 			additionalParameters.put(PkceParameterNames.CODE_CHALLENGE_METHOD, "S256");
 		} catch (NoSuchAlgorithmException e) {
 		} catch (NoSuchAlgorithmException e) {
@@ -225,9 +252,9 @@ public final class DefaultOAuth2AuthorizationRequestResolver implements OAuth2Au
 		}
 		}
 	}
 	}
 
 
-	private String createCodeChallenge(String codeVerifier) throws NoSuchAlgorithmException {
+	private String createHash(String value) throws NoSuchAlgorithmException {
 		MessageDigest md = MessageDigest.getInstance("SHA-256");
 		MessageDigest md = MessageDigest.getInstance("SHA-256");
-		byte[] digest = md.digest(codeVerifier.getBytes(StandardCharsets.US_ASCII));
+		byte[] digest = md.digest(value.getBytes(StandardCharsets.US_ASCII));
 		return Base64.getUrlEncoder().withoutPadding().encodeToString(digest);
 		return Base64.getUrlEncoder().withoutPadding().encodeToString(digest);
 	}
 	}
 }
 }

+ 36 - 11
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/server/DefaultServerOAuth2AuthorizationRequestResolver.java

@@ -27,6 +27,7 @@ import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
 import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
 import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
 import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
 import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
 import org.springframework.security.oauth2.core.endpoint.PkceParameterNames;
 import org.springframework.security.oauth2.core.endpoint.PkceParameterNames;
+import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames;
 import org.springframework.security.web.server.util.matcher.PathPatternParserServerWebExchangeMatcher;
 import org.springframework.security.web.server.util.matcher.PathPatternParserServerWebExchangeMatcher;
 import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
 import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
 import org.springframework.util.Assert;
 import org.springframework.util.Assert;
@@ -52,6 +53,7 @@ import java.util.Map;
  * used to resolve the {@link ClientRegistration} and create the {@link OAuth2AuthorizationRequest}.
  * used to resolve the {@link ClientRegistration} and create the {@link OAuth2AuthorizationRequest}.
  *
  *
  * @author Rob Winch
  * @author Rob Winch
+ * @author Mark Heckler
  * @since 5.1
  * @since 5.1
  */
  */
 public class DefaultServerOAuth2AuthorizationRequestResolver
 public class DefaultServerOAuth2AuthorizationRequestResolver
@@ -75,7 +77,7 @@ public class DefaultServerOAuth2AuthorizationRequestResolver
 
 
 	private final StringKeyGenerator stateGenerator = new Base64StringKeyGenerator(Base64.getUrlEncoder());
 	private final StringKeyGenerator stateGenerator = new Base64StringKeyGenerator(Base64.getUrlEncoder());
 
 
-	private final StringKeyGenerator codeVerifierGenerator = new Base64StringKeyGenerator(Base64.getUrlEncoder().withoutPadding(), 96);
+	private final StringKeyGenerator stringKeyGenerator = new Base64StringKeyGenerator(Base64.getUrlEncoder().withoutPadding(), 96);
 
 
 	/**
 	/**
 	 * Creates a new instance
 	 * Creates a new instance
@@ -132,16 +134,18 @@ public class DefaultServerOAuth2AuthorizationRequestResolver
 		OAuth2AuthorizationRequest.Builder builder;
 		OAuth2AuthorizationRequest.Builder builder;
 		if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(clientRegistration.getAuthorizationGrantType())) {
 		if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(clientRegistration.getAuthorizationGrantType())) {
 			builder = OAuth2AuthorizationRequest.authorizationCode();
 			builder = OAuth2AuthorizationRequest.authorizationCode();
+			Map<String, Object> additionalParameters = new HashMap<>();
+
+			addNonceParameters(attributes, additionalParameters);
+
 			if (ClientAuthenticationMethod.NONE.equals(clientRegistration.getClientAuthenticationMethod())) {
 			if (ClientAuthenticationMethod.NONE.equals(clientRegistration.getClientAuthenticationMethod())) {
-				Map<String, Object> additionalParameters = new HashMap<>();
 				addPkceParameters(attributes, additionalParameters);
 				addPkceParameters(attributes, additionalParameters);
-				builder.additionalParameters(additionalParameters);
 			}
 			}
-		}
-		else if (AuthorizationGrantType.IMPLICIT.equals(clientRegistration.getAuthorizationGrantType())) {
+
+			builder.additionalParameters(additionalParameters);
+		} else if (AuthorizationGrantType.IMPLICIT.equals(clientRegistration.getAuthorizationGrantType())) {
 			builder = OAuth2AuthorizationRequest.implicit();
 			builder = OAuth2AuthorizationRequest.implicit();
-		}
-		else {
+		} else {
 			throw new IllegalArgumentException(
 			throw new IllegalArgumentException(
 					"Invalid Authorization Grant Type (" + clientRegistration.getAuthorizationGrantType().getValue()
 					"Invalid Authorization Grant Type (" + clientRegistration.getAuthorizationGrantType().getValue()
 							+ ") for Client Registration with Id: " + clientRegistration.getRegistrationId());
 							+ ") for Client Registration with Id: " + clientRegistration.getRegistrationId());
@@ -207,6 +211,27 @@ public class DefaultServerOAuth2AuthorizationRequestResolver
 				.toUriString();
 				.toUriString();
 	}
 	}
 
 
+	/**
+	 * Creates nonce and its hash for use in OpenID Connect Authentication Requests
+	 *
+	 * @param attributes where {@link OidcParameterNames#NONCE} is stored for the token request
+	 * @param additionalParameters where hash of {@link OidcParameterNames#NONCE} is added to the authentication request
+	 *
+	 * @since 5.2
+	 * @see <a target="_blank" href="https://openid.net/specs/openid-connect-core-1_0.html#NonceNotes">15.5.2.  Nonce Implementation Notes</a>
+	 * @see <a target="_blank" href="https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation">3.1.3.7.  ID Token Validation</a>
+	 */
+	private void addNonceParameters(Map<String, Object> attributes, Map<String, Object> additionalParameters) {
+		try {
+			String nonce = this.stringKeyGenerator.generateKey();
+			attributes.put(OidcParameterNames.NONCE, nonce);
+			
+			String nonceHash = createHash(nonce);
+			additionalParameters.put(OidcParameterNames.NONCE, nonceHash);
+		} catch (NoSuchAlgorithmException ignored) {
+		}
+	}
+
 	/**
 	/**
 	 * Creates and adds additional PKCE parameters for use in the OAuth 2.0 Authorization and Access Token Requests
 	 * Creates and adds additional PKCE parameters for use in the OAuth 2.0 Authorization and Access Token Requests
 	 *
 	 *
@@ -220,10 +245,10 @@ public class DefaultServerOAuth2AuthorizationRequestResolver
 	 * @see <a target="_blank" href="https://tools.ietf.org/html/rfc7636#section-4.2">4.2.  Client Creates the Code Challenge</a>
 	 * @see <a target="_blank" href="https://tools.ietf.org/html/rfc7636#section-4.2">4.2.  Client Creates the Code Challenge</a>
 	 */
 	 */
 	private void addPkceParameters(Map<String, Object> attributes, Map<String, Object> additionalParameters) {
 	private void addPkceParameters(Map<String, Object> attributes, Map<String, Object> additionalParameters) {
-		String codeVerifier = this.codeVerifierGenerator.generateKey();
+		String codeVerifier = this.stringKeyGenerator.generateKey();
 		attributes.put(PkceParameterNames.CODE_VERIFIER, codeVerifier);
 		attributes.put(PkceParameterNames.CODE_VERIFIER, codeVerifier);
 		try {
 		try {
-			String codeChallenge = createCodeChallenge(codeVerifier);
+			String codeChallenge = createHash(codeVerifier);
 			additionalParameters.put(PkceParameterNames.CODE_CHALLENGE, codeChallenge);
 			additionalParameters.put(PkceParameterNames.CODE_CHALLENGE, codeChallenge);
 			additionalParameters.put(PkceParameterNames.CODE_CHALLENGE_METHOD, "S256");
 			additionalParameters.put(PkceParameterNames.CODE_CHALLENGE_METHOD, "S256");
 		} catch (NoSuchAlgorithmException e) {
 		} catch (NoSuchAlgorithmException e) {
@@ -231,9 +256,9 @@ public class DefaultServerOAuth2AuthorizationRequestResolver
 		}
 		}
 	}
 	}
 
 
-	private String createCodeChallenge(String codeVerifier) throws NoSuchAlgorithmException {
+	private String createHash(String value) throws NoSuchAlgorithmException {
 		MessageDigest md = MessageDigest.getInstance("SHA-256");
 		MessageDigest md = MessageDigest.getInstance("SHA-256");
-		byte[] digest = md.digest(codeVerifier.getBytes(StandardCharsets.US_ASCII));
+		byte[] digest = md.digest(value.getBytes(StandardCharsets.US_ASCII));
 		return Base64.getUrlEncoder().withoutPadding().encodeToString(digest);
 		return Base64.getUrlEncoder().withoutPadding().encodeToString(digest);
 	}
 	}
 }
 }

+ 102 - 18
oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/authentication/OidcAuthorizationCodeAuthenticationProviderTests.java

@@ -1,5 +1,5 @@
 /*
 /*
- * Copyright 2002-2018 the original author or authors.
+ * Copyright 2002-2019 the original author or authors.
  *
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * you may not use this file except in compliance with the License.
@@ -15,25 +15,17 @@
  */
  */
 package org.springframework.security.oauth2.client.oidc.authentication;
 package org.springframework.security.oauth2.client.oidc.authentication;
 
 
-import java.time.Instant;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
 import org.junit.Before;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
 import org.junit.rules.ExpectedException;
 import org.mockito.ArgumentCaptor;
 import org.mockito.ArgumentCaptor;
 import org.mockito.stubbing.Answer;
 import org.mockito.stubbing.Answer;
-
 import org.springframework.security.core.GrantedAuthority;
 import org.springframework.security.core.GrantedAuthority;
 import org.springframework.security.core.authority.AuthorityUtils;
 import org.springframework.security.core.authority.AuthorityUtils;
 import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
 import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
+import org.springframework.security.crypto.keygen.Base64StringKeyGenerator;
+import org.springframework.security.crypto.keygen.StringKeyGenerator;
 import org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationToken;
 import org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationToken;
 import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
 import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
 import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
 import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
@@ -54,23 +46,34 @@ import org.springframework.security.oauth2.jwt.Jwt;
 import org.springframework.security.oauth2.jwt.JwtDecoder;
 import org.springframework.security.oauth2.jwt.JwtDecoder;
 import org.springframework.security.oauth2.jwt.JwtException;
 import org.springframework.security.oauth2.jwt.JwtException;
 
 
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.time.Instant;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.containsString;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyCollection;
-import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.*;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 import static org.mockito.Mockito.when;
 import static org.springframework.security.oauth2.client.registration.TestClientRegistrations.clientRegistration;
 import static org.springframework.security.oauth2.client.registration.TestClientRegistrations.clientRegistration;
 import static org.springframework.security.oauth2.core.endpoint.TestOAuth2AuthorizationRequests.request;
 import static org.springframework.security.oauth2.core.endpoint.TestOAuth2AuthorizationRequests.request;
 import static org.springframework.security.oauth2.core.endpoint.TestOAuth2AuthorizationResponses.error;
 import static org.springframework.security.oauth2.core.endpoint.TestOAuth2AuthorizationResponses.error;
 import static org.springframework.security.oauth2.core.endpoint.TestOAuth2AuthorizationResponses.success;
 import static org.springframework.security.oauth2.core.endpoint.TestOAuth2AuthorizationResponses.success;
-import static org.springframework.security.oauth2.jwt.TestJwts.jwt;
 
 
 /**
 /**
  * Tests for {@link OidcAuthorizationCodeAuthenticationProvider}.
  * Tests for {@link OidcAuthorizationCodeAuthenticationProvider}.
  *
  *
  * @author Joe Grandja
  * @author Joe Grandja
+ * @author Mark Heckler
  */
  */
 public class OidcAuthorizationCodeAuthenticationProviderTests {
 public class OidcAuthorizationCodeAuthenticationProviderTests {
 	private ClientRegistration clientRegistration;
 	private ClientRegistration clientRegistration;
@@ -81,6 +84,9 @@ public class OidcAuthorizationCodeAuthenticationProviderTests {
 	private OAuth2AccessTokenResponse accessTokenResponse;
 	private OAuth2AccessTokenResponse accessTokenResponse;
 	private OAuth2UserService<OidcUserRequest, OidcUser> userService;
 	private OAuth2UserService<OidcUserRequest, OidcUser> userService;
 	private OidcAuthorizationCodeAuthenticationProvider authenticationProvider;
 	private OidcAuthorizationCodeAuthenticationProvider authenticationProvider;
+	private StringKeyGenerator stringKeyGenerator = new Base64StringKeyGenerator(Base64.getUrlEncoder().withoutPadding(), 96);
+	private String nonce = this.stringKeyGenerator.generateKey();
+	private String nonceHash;
 
 
 	@Rule
 	@Rule
 	public ExpectedException exception = ExpectedException.none();
 	public ExpectedException exception = ExpectedException.none();
@@ -88,8 +94,21 @@ public class OidcAuthorizationCodeAuthenticationProviderTests {
 	@Before
 	@Before
 	@SuppressWarnings("unchecked")
 	@SuppressWarnings("unchecked")
 	public void setUp() {
 	public void setUp() {
+		try {
+			nonceHash = createHash(nonce);
+		} catch (NoSuchAlgorithmException e) {
+			e.printStackTrace();
+		}
+		Map<String, Object> attributes = new HashMap<>();
+		Map<String, Object> additionalParameters = new HashMap<>();
+		addNonceToRequest(attributes, additionalParameters);
+
 		this.clientRegistration = clientRegistration().clientId("client1").build();
 		this.clientRegistration = clientRegistration().clientId("client1").build();
-		this.authorizationRequest = request().scope("openid", "profile", "email").build();
+		this.authorizationRequest = request()
+				.scope("openid", "profile", "email")
+				.attributes(attributes)
+				.additionalParameters(additionalParameters)
+				.build();
 		this.authorizationResponse = success().build();
 		this.authorizationResponse = success().build();
 		this.authorizationExchange = new OAuth2AuthorizationExchange(this.authorizationRequest, this.authorizationResponse);
 		this.authorizationExchange = new OAuth2AuthorizationExchange(this.authorizationRequest, this.authorizationResponse);
 		this.accessTokenResponseClient = mock(OAuth2AccessTokenResponseClient.class);
 		this.accessTokenResponseClient = mock(OAuth2AccessTokenResponseClient.class);
@@ -228,6 +247,7 @@ public class OidcAuthorizationCodeAuthenticationProviderTests {
 		claims.put(IdTokenClaimNames.SUB, "subject1");
 		claims.put(IdTokenClaimNames.SUB, "subject1");
 		claims.put(IdTokenClaimNames.AUD, Arrays.asList("client1", "client2"));
 		claims.put(IdTokenClaimNames.AUD, Arrays.asList("client1", "client2"));
 		claims.put(IdTokenClaimNames.AZP, "client1");
 		claims.put(IdTokenClaimNames.AZP, "client1");
+		claims.put(IdTokenClaimNames.NONCE, nonceHash);
 		this.setUpIdToken(claims);
 		this.setUpIdToken(claims);
 
 
 		OidcUser principal = mock(OidcUser.class);
 		OidcUser principal = mock(OidcUser.class);
@@ -257,6 +277,7 @@ public class OidcAuthorizationCodeAuthenticationProviderTests {
 		claims.put(IdTokenClaimNames.SUB, "subject1");
 		claims.put(IdTokenClaimNames.SUB, "subject1");
 		claims.put(IdTokenClaimNames.AUD, Arrays.asList("client1", "client2"));
 		claims.put(IdTokenClaimNames.AUD, Arrays.asList("client1", "client2"));
 		claims.put(IdTokenClaimNames.AZP, "client1");
 		claims.put(IdTokenClaimNames.AZP, "client1");
+		claims.put(IdTokenClaimNames.NONCE, nonceHash);
 		this.setUpIdToken(claims);
 		this.setUpIdToken(claims);
 
 
 		OidcUser principal = mock(OidcUser.class);
 		OidcUser principal = mock(OidcUser.class);
@@ -286,6 +307,7 @@ public class OidcAuthorizationCodeAuthenticationProviderTests {
 		claims.put(IdTokenClaimNames.SUB, "subject1");
 		claims.put(IdTokenClaimNames.SUB, "subject1");
 		claims.put(IdTokenClaimNames.AUD, Arrays.asList("client1", "client2"));
 		claims.put(IdTokenClaimNames.AUD, Arrays.asList("client1", "client2"));
 		claims.put(IdTokenClaimNames.AZP, "client1");
 		claims.put(IdTokenClaimNames.AZP, "client1");
+		claims.put(IdTokenClaimNames.NONCE, nonceHash);
 		this.setUpIdToken(claims);
 		this.setUpIdToken(claims);
 
 
 		OidcUser principal = mock(OidcUser.class);
 		OidcUser principal = mock(OidcUser.class);
@@ -302,9 +324,49 @@ public class OidcAuthorizationCodeAuthenticationProviderTests {
 				this.accessTokenResponse.getAdditionalParameters());
 				this.accessTokenResponse.getAdditionalParameters());
 	}
 	}
 
 
-	private void setUpIdToken(Map<String, Object> claims) {
-		Jwt idToken = jwt().claims(c -> c.putAll(claims)).build();
+	// gh-4442
+	@Test
+	public void authenticateWhenTokenSuccessResponseThenAdditionalParametersAddedToUserRequestNoNonce() {
+		OAuth2AuthorizationRequest authorizationRequestNoNonce = request()
+				.scope("openid", "profile", "email")
+				.attributes(new HashMap<>())
+				.additionalParameters(new HashMap<>())
+				.build();
+		OAuth2AuthorizationExchange authorizationExchangeNoNonce = new OAuth2AuthorizationExchange(authorizationRequestNoNonce, this.authorizationResponse);
 
 
+		Map<String, Object> claims = new HashMap<>();
+		claims.put(IdTokenClaimNames.ISS, "https://provider.com");
+		claims.put(IdTokenClaimNames.SUB, "subject1");
+		claims.put(IdTokenClaimNames.AUD, Arrays.asList("client1", "client2"));
+		claims.put(IdTokenClaimNames.AZP, "client1");
+		this.setUpIdToken(claims);
+
+		OidcUser principal = mock(OidcUser.class);
+		List<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList("ROLE_USER");
+		when(principal.getAuthorities()).thenAnswer(
+				(Answer<List<GrantedAuthority>>) invocation -> authorities);
+		ArgumentCaptor<OidcUserRequest> userRequestArgCaptor = ArgumentCaptor.forClass(OidcUserRequest.class);
+		when(this.userService.loadUser(userRequestArgCaptor.capture())).thenReturn(principal);
+
+		this.authenticationProvider.authenticate(new OAuth2LoginAuthenticationToken(
+				this.clientRegistration, authorizationExchangeNoNonce));
+
+		assertThat(userRequestArgCaptor.getValue().getAdditionalParameters()).containsAllEntriesOf(
+				this.accessTokenResponse.getAdditionalParameters());
+	}
+
+	private void setUpIdToken(Map<String, Object> claims) {
+		Jwt idToken = Jwt.withTokenValue("token")
+				.header("alg", "none")
+				.audience(Collections.singletonList("https://audience.example.org"))
+				.expiresAt(Instant.MAX)
+				.issuedAt(Instant.MIN)
+				.issuer("https://issuer.example.org")
+				.jti("jti")
+				.notBefore(Instant.MIN)
+				.subject("mock-test-subject")
+				.claims(c -> c.putAll(claims))
+				.build();
 		JwtDecoder jwtDecoder = mock(JwtDecoder.class);
 		JwtDecoder jwtDecoder = mock(JwtDecoder.class);
 		when(jwtDecoder.decode(anyString())).thenReturn(idToken);
 		when(jwtDecoder.decode(anyString())).thenReturn(idToken);
 		this.authenticationProvider.setJwtDecoderFactory(registration -> jwtDecoder);
 		this.authenticationProvider.setJwtDecoderFactory(registration -> jwtDecoder);
@@ -317,6 +379,7 @@ public class OidcAuthorizationCodeAuthenticationProviderTests {
 		additionalParameters.put("param1", "value1");
 		additionalParameters.put("param1", "value1");
 		additionalParameters.put("param2", "value2");
 		additionalParameters.put("param2", "value2");
 		additionalParameters.put(OidcParameterNames.ID_TOKEN, "id-token");
 		additionalParameters.put(OidcParameterNames.ID_TOKEN, "id-token");
+		additionalParameters.put(IdTokenClaimNames.NONCE, nonceHash);
 
 
 		return OAuth2AccessTokenResponse
 		return OAuth2AccessTokenResponse
 				.withToken("access-token-1234")
 				.withToken("access-token-1234")
@@ -328,4 +391,25 @@ public class OidcAuthorizationCodeAuthenticationProviderTests {
 				.build();
 				.build();
 
 
 	}
 	}
+
+	/**
+	 * Adds nonce for use in OpenID Connect Authentication Requests
+	 *
+	 * @param attributes where {@link IdTokenClaimNames#NONCE} is stored for the token request
+	 * @param additionalParameters where the hash of {@link IdTokenClaimNames#NONCE} is added to be used in the authentication request
+	 *
+	 * @since 5.2
+	 * @see <a target="_blank" href="https://openid.net/specs/openid-connect-core-1_0.html#NonceNotes">15.5.2.  Nonce Implementation Notes</a>
+	 * @see <a target="_blank" href="https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation">3.1.3.7.  ID Token Validation</a>
+	 */
+	private void addNonceToRequest(Map<String, Object> attributes, Map<String, Object> additionalParameters) {
+		attributes.put(IdTokenClaimNames.NONCE, nonce);
+		additionalParameters.put(IdTokenClaimNames.NONCE, nonceHash);
+	}
+
+	private String createHash(String nonce) throws NoSuchAlgorithmException {
+		MessageDigest md = MessageDigest.getInstance("SHA-256");
+		byte[] digest = md.digest(nonce.getBytes(StandardCharsets.US_ASCII));
+		return Base64.getUrlEncoder().withoutPadding().encodeToString(digest);
+	}
 }
 }

+ 22 - 13
oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/DefaultOAuth2AuthorizationRequestResolverTests.java

@@ -24,10 +24,8 @@ import org.springframework.security.oauth2.client.registration.InMemoryClientReg
 import org.springframework.security.oauth2.client.registration.TestClientRegistrations;
 import org.springframework.security.oauth2.client.registration.TestClientRegistrations;
 import org.springframework.security.oauth2.core.AuthorizationGrantType;
 import org.springframework.security.oauth2.core.AuthorizationGrantType;
 import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
 import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
-import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
-import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType;
-import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
-import org.springframework.security.oauth2.core.endpoint.PkceParameterNames;
+import org.springframework.security.oauth2.core.endpoint.*;
+import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames;
 
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -37,6 +35,7 @@ import static org.assertj.core.api.Assertions.entry;
  * Tests for {@link DefaultOAuth2AuthorizationRequestResolver}.
  * Tests for {@link DefaultOAuth2AuthorizationRequestResolver}.
  *
  *
  * @author Joe Grandja
  * @author Joe Grandja
+ * @author Mark Heckler
  */
  */
 public class DefaultOAuth2AuthorizationRequestResolverTests {
 public class DefaultOAuth2AuthorizationRequestResolverTests {
 	private ClientRegistration registration1;
 	private ClientRegistration registration1;
@@ -119,12 +118,14 @@ public class DefaultOAuth2AuthorizationRequestResolverTests {
 		assertThat(authorizationRequest.getState()).isNotNull();
 		assertThat(authorizationRequest.getState()).isNotNull();
 		assertThat(authorizationRequest.getAdditionalParameters()).doesNotContainKey(OAuth2ParameterNames.REGISTRATION_ID);
 		assertThat(authorizationRequest.getAdditionalParameters()).doesNotContainKey(OAuth2ParameterNames.REGISTRATION_ID);
 		assertThat(authorizationRequest.getAttributes())
 		assertThat(authorizationRequest.getAttributes())
-				.containsExactly(entry(OAuth2ParameterNames.REGISTRATION_ID, clientRegistration.getRegistrationId()));
+				.containsOnlyKeys(OAuth2ParameterNames.REGISTRATION_ID, IdTokenClaimNames.NONCE)
+				.contains(entry(OAuth2ParameterNames.REGISTRATION_ID, clientRegistration.getRegistrationId()));
 		assertThat(authorizationRequest.getAuthorizationRequestUri())
 		assertThat(authorizationRequest.getAuthorizationRequestUri())
 				.matches("https://example.com/login/oauth/authorize\\?" +
 				.matches("https://example.com/login/oauth/authorize\\?" +
 						"response_type=code&client_id=client-id&" +
 						"response_type=code&client_id=client-id&" +
 						"scope=read:user&state=.{15,}&" +
 						"scope=read:user&state=.{15,}&" +
-						"redirect_uri=http://localhost/login/oauth2/code/registration-id");
+						"redirect_uri=http://localhost/login/oauth2/code/registration-id&" +
+						"nonce=([a-zA-Z0-9\\-\\.\\_\\~]){43}");
 	}
 	}
 
 
 	@Test
 	@Test
@@ -137,7 +138,8 @@ public class DefaultOAuth2AuthorizationRequestResolverTests {
 		OAuth2AuthorizationRequest authorizationRequest = this.resolver.resolve(request, clientRegistration.getRegistrationId());
 		OAuth2AuthorizationRequest authorizationRequest = this.resolver.resolve(request, clientRegistration.getRegistrationId());
 		assertThat(authorizationRequest).isNotNull();
 		assertThat(authorizationRequest).isNotNull();
 		assertThat(authorizationRequest.getAttributes())
 		assertThat(authorizationRequest.getAttributes())
-				.containsExactly(entry(OAuth2ParameterNames.REGISTRATION_ID, clientRegistration.getRegistrationId()));
+				.containsOnlyKeys(OAuth2ParameterNames.REGISTRATION_ID, IdTokenClaimNames.NONCE)
+				.contains(entry(OAuth2ParameterNames.REGISTRATION_ID, clientRegistration.getRegistrationId()));
 	}
 	}
 
 
 	@Test
 	@Test
@@ -259,7 +261,8 @@ public class DefaultOAuth2AuthorizationRequestResolverTests {
 				.matches("https://example.com/login/oauth/authorize\\?" +
 				.matches("https://example.com/login/oauth/authorize\\?" +
 						"response_type=code&client_id=client-id&" +
 						"response_type=code&client_id=client-id&" +
 						"scope=read:user&state=.{15,}&" +
 						"scope=read:user&state=.{15,}&" +
-						"redirect_uri=http://localhost/login/oauth2/code/registration-id");
+						"redirect_uri=http://localhost/login/oauth2/code/registration-id&" +
+						"nonce=([a-zA-Z0-9\\-\\.\\_\\~]){43}");
 	}
 	}
 
 
 	@Test
 	@Test
@@ -277,7 +280,8 @@ public class DefaultOAuth2AuthorizationRequestResolverTests {
 				.matches("https://example.com/login/oauth/authorize\\?" +
 				.matches("https://example.com/login/oauth/authorize\\?" +
 						"response_type=code&client_id=client-id&" +
 						"response_type=code&client_id=client-id&" +
 						"scope=read:user&state=.{15,}&" +
 						"scope=read:user&state=.{15,}&" +
-						"redirect_uri=https://example.com/login/oauth2/code/registration-id");
+						"redirect_uri=https://example.com/login/oauth2/code/registration-id&" +
+						"nonce=([a-zA-Z0-9\\-\\.\\_\\~]){43}");
 	}
 	}
 
 
 	@Test
 	@Test
@@ -292,7 +296,8 @@ public class DefaultOAuth2AuthorizationRequestResolverTests {
 				.matches("https://example.com/login/oauth/authorize\\?" +
 				.matches("https://example.com/login/oauth/authorize\\?" +
 						"response_type=code&client_id=client-id&" +
 						"response_type=code&client_id=client-id&" +
 						"scope=read:user&state=.{15,}&" +
 						"scope=read:user&state=.{15,}&" +
-						"redirect_uri=http://localhost/authorize/oauth2/code/registration-id");
+						"redirect_uri=http://localhost/authorize/oauth2/code/registration-id&" +
+						"nonce=([a-zA-Z0-9\\-\\.\\_\\~]){43}");
 	}
 	}
 
 
 	@Test
 	@Test
@@ -307,7 +312,8 @@ public class DefaultOAuth2AuthorizationRequestResolverTests {
 				.matches("https://example.com/login/oauth/authorize\\?" +
 				.matches("https://example.com/login/oauth/authorize\\?" +
 						"response_type=code&client_id=client-id-2&" +
 						"response_type=code&client_id=client-id-2&" +
 						"scope=read:user&state=.{15,}&" +
 						"scope=read:user&state=.{15,}&" +
-						"redirect_uri=http://localhost/login/oauth2/code/registration-id-2");
+						"redirect_uri=http://localhost/login/oauth2/code/registration-id-2&" +
+						"nonce=([a-zA-Z0-9\\-\\.\\_\\~]){43}");
 	}
 	}
 
 
 	@Test
 	@Test
@@ -323,7 +329,8 @@ public class DefaultOAuth2AuthorizationRequestResolverTests {
 				.matches("https://example.com/login/oauth/authorize\\?" +
 				.matches("https://example.com/login/oauth/authorize\\?" +
 						"response_type=code&client_id=client-id&" +
 						"response_type=code&client_id=client-id&" +
 						"scope=read:user&state=.{15,}&" +
 						"scope=read:user&state=.{15,}&" +
-						"redirect_uri=http://localhost/authorize/oauth2/code/registration-id");
+						"redirect_uri=http://localhost/authorize/oauth2/code/registration-id&" +
+						"nonce=([a-zA-Z0-9\\-\\.\\_\\~]){43}");
 	}
 	}
 
 
 	@Test
 	@Test
@@ -339,7 +346,8 @@ public class DefaultOAuth2AuthorizationRequestResolverTests {
 				.matches("https://example.com/login/oauth/authorize\\?" +
 				.matches("https://example.com/login/oauth/authorize\\?" +
 						"response_type=code&client_id=client-id-2&" +
 						"response_type=code&client_id=client-id-2&" +
 						"scope=read:user&state=.{15,}&" +
 						"scope=read:user&state=.{15,}&" +
-						"redirect_uri=http://localhost/login/oauth2/code/registration-id-2");
+						"redirect_uri=http://localhost/login/oauth2/code/registration-id-2&" +
+						"nonce=([a-zA-Z0-9\\-\\.\\_\\~]){43}");
 	}
 	}
 
 
 	@Test
 	@Test
@@ -375,6 +383,7 @@ public class DefaultOAuth2AuthorizationRequestResolverTests {
 						"scope=read:user&state=.{15,}&" +
 						"scope=read:user&state=.{15,}&" +
 						"redirect_uri=http://localhost/login/oauth2/code/pkce-client-registration-id&" +
 						"redirect_uri=http://localhost/login/oauth2/code/pkce-client-registration-id&" +
 						"code_challenge_method=S256&" +
 						"code_challenge_method=S256&" +
+						"nonce=([a-zA-Z0-9\\-\\.\\_\\~]){43}&" +
 						"code_challenge=([a-zA-Z0-9\\-\\.\\_\\~]){43}");
 						"code_challenge=([a-zA-Z0-9\\-\\.\\_\\~]){43}");
 	}
 	}
 
 

+ 9 - 4
oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizationRequestRedirectFilterTests.java

@@ -1,5 +1,5 @@
 /*
 /*
- * Copyright 2002-2018 the original author or authors.
+ * Copyright 2002-2019 the original author or authors.
  *
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * you may not use this file except in compliance with the License.
@@ -49,6 +49,7 @@ import static org.mockito.Mockito.*;
  * Tests for {@link OAuth2AuthorizationRequestRedirectFilter}.
  * Tests for {@link OAuth2AuthorizationRequestRedirectFilter}.
  *
  *
  * @author Joe Grandja
  * @author Joe Grandja
+ * @author Mark Heckler
  */
  */
 public class OAuth2AuthorizationRequestRedirectFilterTests {
 public class OAuth2AuthorizationRequestRedirectFilterTests {
 	private ClientRegistration registration1;
 	private ClientRegistration registration1;
@@ -154,7 +155,8 @@ public class OAuth2AuthorizationRequestRedirectFilterTests {
 		assertThat(response.getRedirectedUrl()).matches("https://example.com/login/oauth/authorize\\?" +
 		assertThat(response.getRedirectedUrl()).matches("https://example.com/login/oauth/authorize\\?" +
 				"response_type=code&client_id=client-id&" +
 				"response_type=code&client_id=client-id&" +
 				"scope=read:user&state=.{15,}&" +
 				"scope=read:user&state=.{15,}&" +
-				"redirect_uri=http://localhost/login/oauth2/code/registration-id");
+				"redirect_uri=http://localhost/login/oauth2/code/registration-id&" +
+				"nonce=([a-zA-Z0-9\\-\\.\\_\\~]){43}");
 	}
 	}
 
 
 	@Test
 	@Test
@@ -234,7 +236,8 @@ public class OAuth2AuthorizationRequestRedirectFilterTests {
 		assertThat(response.getRedirectedUrl()).matches("https://example.com/login/oauth/authorize\\?" +
 		assertThat(response.getRedirectedUrl()).matches("https://example.com/login/oauth/authorize\\?" +
 				"response_type=code&client_id=client-id&" +
 				"response_type=code&client_id=client-id&" +
 				"scope=read:user&state=.{15,}&" +
 				"scope=read:user&state=.{15,}&" +
-				"redirect_uri=http://localhost/login/oauth2/code/registration-id");
+				"redirect_uri=http://localhost/login/oauth2/code/registration-id&" +
+				"nonce=([a-zA-Z0-9\\-\\.\\_\\~]){43}");
 	}
 	}
 
 
 	@Test
 	@Test
@@ -255,7 +258,8 @@ public class OAuth2AuthorizationRequestRedirectFilterTests {
 		assertThat(response.getRedirectedUrl()).matches("https://example.com/login/oauth/authorize\\?" +
 		assertThat(response.getRedirectedUrl()).matches("https://example.com/login/oauth/authorize\\?" +
 				"response_type=code&client_id=client-id&" +
 				"response_type=code&client_id=client-id&" +
 				"scope=read:user&state=.{15,}&" +
 				"scope=read:user&state=.{15,}&" +
-				"redirect_uri=http://localhost/authorize/oauth2/code/registration-id");
+				"redirect_uri=http://localhost/authorize/oauth2/code/registration-id&" +
+				"nonce=([a-zA-Z0-9\\-\\.\\_\\~]){43}");
 		verify(this.requestCache).saveRequest(any(HttpServletRequest.class), any(HttpServletResponse.class));
 		verify(this.requestCache).saveRequest(any(HttpServletRequest.class), any(HttpServletResponse.class));
 	}
 	}
 
 
@@ -359,6 +363,7 @@ public class OAuth2AuthorizationRequestRedirectFilterTests {
 				"response_type=code&client_id=client-id&" +
 				"response_type=code&client_id=client-id&" +
 				"scope=read:user&state=.{15,}&" +
 				"scope=read:user&state=.{15,}&" +
 				"redirect_uri=http://localhost/login/oauth2/code/registration-id&" +
 				"redirect_uri=http://localhost/login/oauth2/code/registration-id&" +
+				"nonce=([a-zA-Z0-9\\-\\.\\_\\~]){43}&" +
 				"login_hint=user@provider\\.com");
 				"login_hint=user@provider\\.com");
 	}
 	}
 }
 }

+ 6 - 2
oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/server/DefaultServerOAuth2AuthorizationRequestResolverTests.java

@@ -41,6 +41,7 @@ import static org.mockito.Mockito.when;
 
 
 /**
 /**
  * @author Rob Winch
  * @author Rob Winch
+ * @author Mark Heckler
  * @since 5.1
  * @since 5.1
  */
  */
 @RunWith(MockitoJUnitRunner.class)
 @RunWith(MockitoJUnitRunner.class)
@@ -82,7 +83,8 @@ public class DefaultServerOAuth2AuthorizationRequestResolverTests {
 		assertThat(request.getAuthorizationRequestUri()).matches("https://example.com/login/oauth/authorize\\?" +
 		assertThat(request.getAuthorizationRequestUri()).matches("https://example.com/login/oauth/authorize\\?" +
 				"response_type=code&client_id=client-id&" +
 				"response_type=code&client_id=client-id&" +
 				"scope=read:user&state=.*?&" +
 				"scope=read:user&state=.*?&" +
-				"redirect_uri=/login/oauth2/code/registration-id");
+				"redirect_uri=/login/oauth2/code/registration-id&" +
+				"nonce=([a-zA-Z0-9\\-\\.\\_\\~]){43}");
 	}
 	}
 
 
 	private OAuth2AuthorizationRequest resolve(String path) {
 	private OAuth2AuthorizationRequest resolve(String path) {
@@ -101,7 +103,8 @@ public class DefaultServerOAuth2AuthorizationRequestResolverTests {
 		assertThat(request.getAuthorizationRequestUri()).matches("https://example.com/login/oauth/authorize\\?" +
 		assertThat(request.getAuthorizationRequestUri()).matches("https://example.com/login/oauth/authorize\\?" +
 				"response_type=code&client_id=client-id&" +
 				"response_type=code&client_id=client-id&" +
 				"scope=read:user&state=.*?&" +
 				"scope=read:user&state=.*?&" +
-				"redirect_uri=/login/oauth2/code/registration-id");
+				"redirect_uri=/login/oauth2/code/registration-id&" +
+				"nonce=([a-zA-Z0-9\\-\\.\\_\\~]){43}");
 	}
 	}
 
 
 	@Test
 	@Test
@@ -121,6 +124,7 @@ public class DefaultServerOAuth2AuthorizationRequestResolverTests {
 				"scope=read:user&state=.*?&" +
 				"scope=read:user&state=.*?&" +
 				"redirect_uri=/login/oauth2/code/registration-id&" +
 				"redirect_uri=/login/oauth2/code/registration-id&" +
 				"code_challenge_method=S256&" +
 				"code_challenge_method=S256&" +
+				"nonce=([a-zA-Z0-9\\-\\.\\_\\~]){43}&" +
 				"code_challenge=([a-zA-Z0-9\\-\\.\\_\\~]){43}");
 				"code_challenge=([a-zA-Z0-9\\-\\.\\_\\~]){43}");
 	}
 	}
 }
 }

+ 7 - 1
oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/oidc/endpoint/OidcParameterNames.java

@@ -1,5 +1,5 @@
 /*
 /*
- * Copyright 2002-2017 the original author or authors.
+ * Copyright 2002-2019 the original author or authors.
  *
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@ package org.springframework.security.oauth2.core.oidc.endpoint;
  * and used by the authorization endpoint and token endpoint.
  * and used by the authorization endpoint and token endpoint.
  *
  *
  * @author Joe Grandja
  * @author Joe Grandja
+ * @author Mark Heckler
  * @since 5.0
  * @since 5.0
  * @see <a target="_blank" href="https://openid.net/specs/openid-connect-core-1_0.html#OAuthParametersRegistry">18.2 OAuth Parameters Registration</a>
  * @see <a target="_blank" href="https://openid.net/specs/openid-connect-core-1_0.html#OAuthParametersRegistry">18.2 OAuth Parameters Registration</a>
  */
  */
@@ -30,4 +31,9 @@ public interface OidcParameterNames {
 	 */
 	 */
 	String ID_TOKEN = "id_token";
 	String ID_TOKEN = "id_token";
 
 
+	/**
+	 * {@code nonce} - used in the Access Token Request and Response.
+	 */
+	String NONCE = "nonce";
+
 }
 }