diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/AuthorizationRequestRedirectFilter.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/AuthorizationRequestRedirectFilter.java index d62a7b244cf..3c0fb23b2c5 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/AuthorizationRequestRedirectFilter.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/AuthorizationRequestRedirectFilter.java @@ -50,6 +50,7 @@ * once access is granted (or denied) by the end-user (resource owner). * * @author Joe Grandja + * @author Shazin Sadakath * @since 5.0 * @see AuthorizationRequest * @see AuthorizationRequestRepository @@ -131,6 +132,8 @@ private void sendRedirectForAuthorization(HttpServletRequest request, HttpServle Map additionalParameters = new HashMap<>(); additionalParameters.put(OAuth2Parameter.REGISTRATION_ID, clientRegistration.getRegistrationId()); + String nonce = request.getParameter(OAuth2Parameter.NONCE); + AuthorizationRequest.Builder builder; if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(clientRegistration.getAuthorizationGrantType())) { builder = AuthorizationRequest.authorizationCode(); @@ -146,6 +149,7 @@ private void sendRedirectForAuthorization(HttpServletRequest request, HttpServle .redirectUri(redirectUriStr) .scope(clientRegistration.getScope()) .state(this.stateGenerator.generateKey()) + .nonce(nonce) .additionalParameters(additionalParameters) .build(); diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/DefaultAuthorizationRequestUriBuilder.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/DefaultAuthorizationRequestUriBuilder.java index 6234509317a..347fb2f3978 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/DefaultAuthorizationRequestUriBuilder.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/DefaultAuthorizationRequestUriBuilder.java @@ -27,6 +27,7 @@ * which internally uses a {@link UriComponentsBuilder} to construct the OAuth 2.0 Authorization Request. * * @author Joe Grandja + * @author Shazin Sadakath * @since 5.0 * @see AuthorizationRequest * @see Section 4.1.1 Authorization Code Grant Request @@ -42,7 +43,8 @@ public URI build(AuthorizationRequest authorizationRequest) { .queryParam(OAuth2Parameter.CLIENT_ID, authorizationRequest.getClientId()) .queryParam(OAuth2Parameter.SCOPE, authorizationRequest.getScope().stream().collect(Collectors.joining(" "))) - .queryParam(OAuth2Parameter.STATE, authorizationRequest.getState()); + .queryParam(OAuth2Parameter.STATE, authorizationRequest.getState()) + .queryParam(OAuth2Parameter.NONCE, authorizationRequest.getNonce()); if (authorizationRequest.getRedirectUri() != null) { uriBuilder.queryParam(OAuth2Parameter.REDIRECT_URI, authorizationRequest.getRedirectUri()); } diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/AuthorizationCodeAuthenticationFilterTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/AuthorizationCodeAuthenticationFilterTests.java index 48b47836d43..a69ffbbd4cb 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/AuthorizationCodeAuthenticationFilterTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/AuthorizationCodeAuthenticationFilterTests.java @@ -52,6 +52,7 @@ * Tests {@link AuthorizationCodeAuthenticationFilter}. * * @author Joe Grandja + * @author Shazin Sadakath */ public class AuthorizationCodeAuthenticationFilterTests { @@ -115,10 +116,11 @@ public void doFilterWhenAuthorizationCodeSuccessResponseThenAuthenticationSucces MockHttpServletRequest request = this.setupRequest(clientRegistration); String authCode = "some code"; String state = "some state"; + String nonce = "some nonce"; request.addParameter(OAuth2Parameter.CODE, authCode); request.addParameter(OAuth2Parameter.STATE, state); MockHttpServletResponse response = new MockHttpServletResponse(); - setupAuthorizationRequest(authorizationRequestRepository, request, response, clientRegistration, state); + setupAuthorizationRequest(authorizationRequestRepository, request, response, clientRegistration, state, nonce); FilterChain filterChain = mock(FilterChain.class); filter.doFilter(request, response, filterChain); @@ -191,7 +193,8 @@ private void setupAuthorizationRequest(AuthorizationRequestRepository authorizat HttpServletRequest request, HttpServletResponse response, ClientRegistration clientRegistration, - String state) { + String state, + String nonce) { Map additionalParameters = new HashMap<>(); additionalParameters.put(OAuth2Parameter.REGISTRATION_ID, clientRegistration.getRegistrationId()); @@ -203,6 +206,7 @@ private void setupAuthorizationRequest(AuthorizationRequestRepository authorizat .redirectUri(clientRegistration.getRedirectUri()) .scope(clientRegistration.getScope()) .state(state) + .nonce(nonce) .additionalParameters(additionalParameters) .build(); diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/AuthorizationRequestRedirectFilterTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/AuthorizationRequestRedirectFilterTests.java index a7ae22c23cc..9d4fb27e528 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/AuthorizationRequestRedirectFilterTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/AuthorizationRequestRedirectFilterTests.java @@ -24,6 +24,7 @@ import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.security.oauth2.core.endpoint.AuthorizationRequest; +import org.springframework.security.oauth2.core.endpoint.OAuth2Parameter; import javax.servlet.FilterChain; import javax.servlet.http.HttpServletRequest; @@ -34,6 +35,7 @@ * Tests {@link AuthorizationRequestRedirectFilter}. * * @author Joe Grandja + * @author Shazin Sadakath */ public class AuthorizationRequestRedirectFilterTests { @@ -92,6 +94,7 @@ public void doFilterWhenRequestMatchesClientThenAuthorizationRequestSavedInSessi String requestUri = TestUtil.AUTHORIZATION_BASE_URI + "/" + clientRegistration.getRegistrationId(); MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri); request.setServletPath(requestUri); + request.addParameter(OAuth2Parameter.NONCE, "nonce"); MockHttpServletResponse response = new MockHttpServletResponse(); FilterChain filterChain = Mockito.mock(FilterChain.class); @@ -111,6 +114,7 @@ public void doFilterWhenRequestMatchesClientThenAuthorizationRequestSavedInSessi Assertions.assertThat(authorizationRequest.getRedirectUri()).isNotNull(); Assertions.assertThat(authorizationRequest.getScope()).isNotNull(); Assertions.assertThat(authorizationRequest.getState()).isNotNull(); + Assertions.assertThat(authorizationRequest.getNonce()).isNotNull(); } private AuthorizationRequestRedirectFilter setupFilter(String authorizationUri, diff --git a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/endpoint/AuthorizationRequest.java b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/endpoint/AuthorizationRequest.java index ba7b217c476..fe30dd08078 100644 --- a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/endpoint/AuthorizationRequest.java +++ b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/endpoint/AuthorizationRequest.java @@ -31,6 +31,7 @@ * for the authorization code grant type or implicit grant type. * * @author Joe Grandja + * @author Shazin Sadakath * @since 5.0 * @see AuthorizationGrantType * @see ResponseType @@ -45,6 +46,7 @@ public final class AuthorizationRequest implements Serializable { private String redirectUri; private Set scope; private String state; + private String nonce; private Map additionalParameters; private AuthorizationRequest() { @@ -78,6 +80,10 @@ public String getState() { return this.state; } + public String getNonce() { + return nonce; + } + public Map getAdditionalParameters() { return this.additionalParameters; } @@ -98,6 +104,7 @@ public static class Builder { private String redirectUri; private Set scope; private String state; + private String nonce; private Map additionalParameters; private Builder(AuthorizationGrantType authorizationGrantType) { @@ -135,6 +142,11 @@ public Builder state(String state) { return this; } + public Builder nonce(String nonce) { + this.nonce = nonce; + return this; + } + public Builder additionalParameters(Map additionalParameters) { this.additionalParameters = additionalParameters; return this; @@ -154,6 +166,7 @@ public AuthorizationRequest build() { authorizationRequest.clientId = this.clientId; authorizationRequest.redirectUri = this.redirectUri; authorizationRequest.state = this.state; + authorizationRequest.nonce = this.nonce; authorizationRequest.scope = Collections.unmodifiableSet( CollectionUtils.isEmpty(this.scope) ? Collections.emptySet() : new LinkedHashSet<>(this.scope)); diff --git a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/endpoint/OAuth2Parameter.java b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/endpoint/OAuth2Parameter.java index c548b3ec2f0..470254dd121 100644 --- a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/endpoint/OAuth2Parameter.java +++ b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/endpoint/OAuth2Parameter.java @@ -20,6 +20,7 @@ * and used by the authorization endpoint and token endpoint. * * @author Joe Grandja + * @author Shazin Sadakath * @since 5.0 * @see 11.2 OAuth Parameters Registry */ @@ -45,4 +46,6 @@ public interface OAuth2Parameter { String REGISTRATION_ID = "registration_id"; // Non-standard additional parameter + String NONCE = "nonce"; + } diff --git a/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/endpoint/AuthorizationRequestTest.java b/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/endpoint/AuthorizationRequestTest.java index 7a56563e0c1..6a00d25a4a1 100644 --- a/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/endpoint/AuthorizationRequestTest.java +++ b/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/endpoint/AuthorizationRequestTest.java @@ -34,6 +34,7 @@ public class AuthorizationRequestTest { private static final String REDIRECT_URI = "http://redirect.uri/"; private static final Set SCOPE = Collections.singleton("scope"); private static final String STATE = "xyz"; + private static final String NONCE = "1234-456-0393"; @Test(expected = IllegalArgumentException.class) public void buildWhenAuthorizationUriIsNullThenThrowIllegalArgumentException() { @@ -43,6 +44,7 @@ public void buildWhenAuthorizationUriIsNullThenThrowIllegalArgumentException() { .redirectUri(REDIRECT_URI) .scope(SCOPE) .state(STATE) + .nonce(NONCE) .build(); } @@ -53,6 +55,7 @@ public void buildWhenAuthorizeUriNotSetThenThrowIllegalArgumentException() { .redirectUri(REDIRECT_URI) .scope(SCOPE) .state(STATE) + .nonce(NONCE) .build(); } @@ -64,6 +67,7 @@ public void buildWhenClientIdIsNullThenThrowIllegalArgumentException() { .redirectUri(REDIRECT_URI) .scope(SCOPE) .state(STATE) + .nonce(NONCE) .build(); } @@ -74,6 +78,7 @@ public void buildWhenClientIdNotSetThenThrowIllegalArgumentException() { .redirectUri(REDIRECT_URI) .scope(SCOPE) .state(STATE) + .nonce(NONCE) .build(); } @@ -86,6 +91,7 @@ public void buildWhenGetResponseTypeIsCalledThenReturnCode() { .redirectUri(REDIRECT_URI) .scope(SCOPE) .state(STATE) + .nonce(NONCE) .build(); assertThat(authorizationRequest.getResponseType()).isEqualTo(ResponseType.CODE); @@ -99,6 +105,7 @@ public void buildWhenRedirectUriIsNullThenDoesNotThrowAnyException() { .redirectUri(null) .scope(SCOPE) .state(STATE) + .nonce(NONCE) .build()).doesNotThrowAnyException(); } @@ -109,6 +116,7 @@ public void buildWhenRedirectUriNotSetThenDoesNotThrowAnyException() { .clientId(CLIENT_ID) .scope(SCOPE) .state(STATE) + .nonce(NONCE) .build()).doesNotThrowAnyException(); } @@ -120,6 +128,7 @@ public void buildWhenScopesIsNullThenDoesNotThrowAnyException() { .redirectUri(REDIRECT_URI) .scope(null) .state(STATE) + .nonce(NONCE) .build()).doesNotThrowAnyException(); } @@ -130,6 +139,7 @@ public void buildWhenScopesNotSetThenDoesNotThrowAnyException() { .clientId(CLIENT_ID) .redirectUri(REDIRECT_URI) .state(STATE) + .nonce(NONCE) .build()).doesNotThrowAnyException(); } @@ -141,6 +151,19 @@ public void buildWhenStateIsNullThenDoesNotThrowAnyException() { .redirectUri(REDIRECT_URI) .scope(SCOPE) .state(null) + .nonce(NONCE) + .build()).doesNotThrowAnyException(); + } + + @Test + public void buildWhenNonceIsNullThenDoesNotThrowAnyException() { + assertThatCode(() -> AuthorizationRequest.authorizationCode() + .authorizationUri(AUTHORIZE_URI) + .clientId(CLIENT_ID) + .redirectUri(REDIRECT_URI) + .scope(SCOPE) + .state(STATE) + .nonce(null) .build()).doesNotThrowAnyException(); } @@ -151,6 +174,7 @@ public void buildWhenStateNotSetThenDoesNotThrowAnyException() { .clientId(CLIENT_ID) .redirectUri(REDIRECT_URI) .scope(SCOPE) + .nonce(NONCE) .build()).doesNotThrowAnyException(); } }