parent
adde18b873
commit
da9f027fa4
|
@ -75,6 +75,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
|||
* Tests for {@link OAuth2ClientConfigurer}.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @author Mark Heckler
|
||||
*/
|
||||
public class OAuth2ClientConfigurerTests {
|
||||
private static ClientRegistrationRepository clientRegistrationRepository;
|
||||
|
@ -138,7 +139,8 @@ public class OAuth2ClientConfigurerTests {
|
|||
assertThat(mvcResult.getResponse().getRedirectedUrl()).matches("https://provider.com/oauth2/authorize\\?" +
|
||||
"response_type=code&client_id=client-1&" +
|
||||
"scope=user&state=.{15,}&" +
|
||||
"redirect_uri=http://localhost/client-1");
|
||||
"redirect_uri=http://localhost/client-1&" +
|
||||
"nonce=([a-zA-Z0-9\\-\\.\\_\\~]){43}");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -151,7 +153,8 @@ public class OAuth2ClientConfigurerTests {
|
|||
assertThat(mvcResult.getResponse().getRedirectedUrl()).matches("https://provider.com/oauth2/authorize\\?" +
|
||||
"response_type=code&client_id=client-1&" +
|
||||
"scope=user&state=.{15,}&" +
|
||||
"redirect_uri=http://localhost/client-1");
|
||||
"redirect_uri=http://localhost/client-1&" +
|
||||
"nonce=([a-zA-Z0-9\\-\\.\\_\\~]){43}");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -203,7 +206,8 @@ public class OAuth2ClientConfigurerTests {
|
|||
assertThat(mvcResult.getResponse().getRedirectedUrl()).matches("https://provider.com/oauth2/authorize\\?" +
|
||||
"response_type=code&client_id=client-1&" +
|
||||
"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));
|
||||
}
|
||||
|
|
|
@ -43,6 +43,10 @@ import org.springframework.security.oauth2.jwt.JwtDecoderFactory;
|
|||
import org.springframework.security.oauth2.jwt.JwtException;
|
||||
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.Map;
|
||||
|
||||
|
@ -61,6 +65,7 @@ import java.util.Map;
|
|||
* to complete the authentication.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @author Mark Heckler
|
||||
* @since 5.0
|
||||
* @see OAuth2LoginAuthenticationToken
|
||||
* @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_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_NONCE_ERROR_CODE = "invalid_nonce";
|
||||
private final OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient;
|
||||
private final OAuth2UserService<OidcUserRequest, OidcUser> userService;
|
||||
private JwtDecoderFactory<ClientRegistration> jwtDecoderFactory = new OidcIdTokenDecoderFactory();
|
||||
|
@ -152,7 +158,23 @@ public class OidcAuthorizationCodeAuthenticationProvider implements Authenticati
|
|||
null);
|
||||
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(
|
||||
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());
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,13 +43,17 @@ import org.springframework.security.oauth2.jwt.ReactiveJwtDecoderFactory;
|
|||
import org.springframework.util.Assert;
|
||||
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.Map;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
* This {@link org.springframework.security.authentication.AuthenticationProvider} is responsible for authenticating
|
||||
* an Authorization Code credential with the Authorization Server's Token Endpoint
|
||||
* 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_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_NONCE_ERROR_CODE = "invalid_nonce";
|
||||
|
||||
private final ReactiveOAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient;
|
||||
|
||||
|
@ -170,7 +175,8 @@ public class OidcAuthorizationCodeReactiveAuthenticationManager implements
|
|||
}
|
||||
|
||||
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)
|
||||
.map(oauth2User -> {
|
||||
Collection<? extends GrantedAuthority> mappedAuthorities =
|
||||
|
@ -192,4 +198,33 @@ public class OidcAuthorizationCodeReactiveAuthenticationManager implements
|
|||
return jwtDecoder.decode(rawIdToken)
|
||||
.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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,6 +57,7 @@ import static org.springframework.security.oauth2.jwt.NimbusJwtDecoder.withSecre
|
|||
*
|
||||
* @author Joe Grandja
|
||||
* @author Rafael Dominguez
|
||||
* @author Mark Heckler
|
||||
* @since 5.2
|
||||
* @see JwtDecoderFactory
|
||||
* @see ClientRegistration
|
||||
|
@ -88,12 +89,14 @@ public final class OidcIdTokenDecoderFactory implements JwtDecoderFactory<Client
|
|||
Converter<Object, ?> booleanConverter = getConverter(TypeDescriptor.valueOf(Boolean.class));
|
||||
Converter<Object, ?> instantConverter = getConverter(TypeDescriptor.valueOf(Instant.class));
|
||||
Converter<Object, ?> urlConverter = getConverter(TypeDescriptor.valueOf(URL.class));
|
||||
Converter<Object, ?> stringConverter = getConverter(TypeDescriptor.valueOf(String.class));
|
||||
Converter<Object, ?> collectionStringConverter = getConverter(
|
||||
TypeDescriptor.collection(Collection.class, TypeDescriptor.valueOf(String.class)));
|
||||
|
||||
Map<String, Converter<Object, ?>> claimTypeConverters = new HashMap<>();
|
||||
claimTypeConverters.put(IdTokenClaimNames.ISS, urlConverter);
|
||||
claimTypeConverters.put(IdTokenClaimNames.AUD, collectionStringConverter);
|
||||
claimTypeConverters.put(IdTokenClaimNames.NONCE, stringConverter);
|
||||
claimTypeConverters.put(IdTokenClaimNames.EXP, instantConverter);
|
||||
claimTypeConverters.put(IdTokenClaimNames.IAT, instantConverter);
|
||||
claimTypeConverters.put(IdTokenClaimNames.AUTH_TIME, instantConverter);
|
||||
|
|
|
@ -107,13 +107,6 @@ public final class OidcIdTokenValidator implements OAuth2TokenValidator<Jwt> {
|
|||
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()) {
|
||||
return OAuth2TokenValidatorResult.failure(invalidIdToken(invalidClaims));
|
||||
}
|
||||
|
|
|
@ -57,6 +57,7 @@ import static org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder.w
|
|||
*
|
||||
* @author Joe Grandja
|
||||
* @author Rafael Dominguez
|
||||
* @author Mark Heckler
|
||||
* @since 5.2
|
||||
* @see ReactiveJwtDecoderFactory
|
||||
* @see ClientRegistration
|
||||
|
@ -88,12 +89,14 @@ public final class ReactiveOidcIdTokenDecoderFactory implements ReactiveJwtDecod
|
|||
Converter<Object, ?> booleanConverter = getConverter(TypeDescriptor.valueOf(Boolean.class));
|
||||
Converter<Object, ?> instantConverter = getConverter(TypeDescriptor.valueOf(Instant.class));
|
||||
Converter<Object, ?> urlConverter = getConverter(TypeDescriptor.valueOf(URL.class));
|
||||
Converter<Object, ?> stringConverter = getConverter(TypeDescriptor.valueOf(String.class));
|
||||
Converter<Object, ?> collectionStringConverter = getConverter(
|
||||
TypeDescriptor.collection(Collection.class, TypeDescriptor.valueOf(String.class)));
|
||||
|
||||
Map<String, Converter<Object, ?>> claimTypeConverters = new HashMap<>();
|
||||
claimTypeConverters.put(IdTokenClaimNames.ISS, urlConverter);
|
||||
claimTypeConverters.put(IdTokenClaimNames.AUD, collectionStringConverter);
|
||||
claimTypeConverters.put(IdTokenClaimNames.NONCE, stringConverter);
|
||||
claimTypeConverters.put(IdTokenClaimNames.EXP, instantConverter);
|
||||
claimTypeConverters.put(IdTokenClaimNames.IAT, instantConverter);
|
||||
claimTypeConverters.put(IdTokenClaimNames.AUTH_TIME, instantConverter);
|
||||
|
|
|
@ -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.OAuth2ParameterNames;
|
||||
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.matcher.AntPathRequestMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
|
@ -51,6 +52,7 @@ import java.util.Map;
|
|||
* @author Joe Grandja
|
||||
* @author Rob Winch
|
||||
* @author Eddú Meléndez
|
||||
* @author Mark Heckler
|
||||
* @since 5.1
|
||||
* @see OAuth2AuthorizationRequestResolver
|
||||
* @see OAuth2AuthorizationRequestRedirectFilter
|
||||
|
@ -61,7 +63,7 @@ public final class DefaultOAuth2AuthorizationRequestResolver implements OAuth2Au
|
|||
private final ClientRegistrationRepository clientRegistrationRepository;
|
||||
private final AntPathRequestMatcher authorizationRequestMatcher;
|
||||
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.
|
||||
|
@ -118,11 +120,15 @@ public final class DefaultOAuth2AuthorizationRequestResolver implements OAuth2Au
|
|||
OAuth2AuthorizationRequest.Builder builder;
|
||||
if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(clientRegistration.getAuthorizationGrantType())) {
|
||||
builder = OAuth2AuthorizationRequest.authorizationCode();
|
||||
Map<String, Object> additionalParameters = new HashMap<>();
|
||||
|
||||
addNonceParameters(attributes, additionalParameters);
|
||||
|
||||
if (ClientAuthenticationMethod.NONE.equals(clientRegistration.getClientAuthenticationMethod())) {
|
||||
Map<String, Object> additionalParameters = new HashMap<>();
|
||||
addPkceParameters(attributes, additionalParameters);
|
||||
builder.additionalParameters(additionalParameters);
|
||||
}
|
||||
|
||||
builder.additionalParameters(additionalParameters);
|
||||
} else if (AuthorizationGrantType.IMPLICIT.equals(clientRegistration.getAuthorizationGrantType())) {
|
||||
builder = OAuth2AuthorizationRequest.implicit();
|
||||
} else {
|
||||
|
@ -201,6 +207,27 @@ public final class DefaultOAuth2AuthorizationRequestResolver implements OAuth2Au
|
|||
.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
|
||||
*
|
||||
|
@ -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>
|
||||
*/
|
||||
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);
|
||||
try {
|
||||
String codeChallenge = createCodeChallenge(codeVerifier);
|
||||
String codeChallenge = createHash(codeVerifier);
|
||||
additionalParameters.put(PkceParameterNames.CODE_CHALLENGE, codeChallenge);
|
||||
additionalParameters.put(PkceParameterNames.CODE_CHALLENGE_METHOD, "S256");
|
||||
} 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");
|
||||
byte[] digest = md.digest(codeVerifier.getBytes(StandardCharsets.US_ASCII));
|
||||
byte[] digest = md.digest(value.getBytes(StandardCharsets.US_ASCII));
|
||||
return Base64.getUrlEncoder().withoutPadding().encodeToString(digest);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.OAuth2ParameterNames;
|
||||
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.ServerWebExchangeMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
|
@ -52,6 +53,7 @@ import java.util.Map;
|
|||
* used to resolve the {@link ClientRegistration} and create the {@link OAuth2AuthorizationRequest}.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @author Mark Heckler
|
||||
* @since 5.1
|
||||
*/
|
||||
public class DefaultServerOAuth2AuthorizationRequestResolver
|
||||
|
@ -75,7 +77,7 @@ public class DefaultServerOAuth2AuthorizationRequestResolver
|
|||
|
||||
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
|
||||
|
@ -132,16 +134,18 @@ public class DefaultServerOAuth2AuthorizationRequestResolver
|
|||
OAuth2AuthorizationRequest.Builder builder;
|
||||
if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(clientRegistration.getAuthorizationGrantType())) {
|
||||
builder = OAuth2AuthorizationRequest.authorizationCode();
|
||||
Map<String, Object> additionalParameters = new HashMap<>();
|
||||
|
||||
addNonceParameters(attributes, additionalParameters);
|
||||
|
||||
if (ClientAuthenticationMethod.NONE.equals(clientRegistration.getClientAuthenticationMethod())) {
|
||||
Map<String, Object> additionalParameters = new HashMap<>();
|
||||
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();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
"Invalid Authorization Grant Type (" + clientRegistration.getAuthorizationGrantType().getValue()
|
||||
+ ") for Client Registration with Id: " + clientRegistration.getRegistrationId());
|
||||
|
@ -207,6 +211,27 @@ public class DefaultServerOAuth2AuthorizationRequestResolver
|
|||
.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
|
||||
*
|
||||
|
@ -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>
|
||||
*/
|
||||
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);
|
||||
try {
|
||||
String codeChallenge = createCodeChallenge(codeVerifier);
|
||||
String codeChallenge = createHash(codeVerifier);
|
||||
additionalParameters.put(PkceParameterNames.CODE_CHALLENGE, codeChallenge);
|
||||
additionalParameters.put(PkceParameterNames.CODE_CHALLENGE_METHOD, "S256");
|
||||
} 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");
|
||||
byte[] digest = md.digest(codeVerifier.getBytes(StandardCharsets.US_ASCII));
|
||||
byte[] digest = md.digest(value.getBytes(StandardCharsets.US_ASCII));
|
||||
return Base64.getUrlEncoder().withoutPadding().encodeToString(digest);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -15,25 +15,17 @@
|
|||
*/
|
||||
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.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
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.endpoint.OAuth2AccessTokenResponseClient;
|
||||
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.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.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.when;
|
||||
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.TestOAuth2AuthorizationResponses.error;
|
||||
import static org.springframework.security.oauth2.core.endpoint.TestOAuth2AuthorizationResponses.success;
|
||||
import static org.springframework.security.oauth2.jwt.TestJwts.jwt;
|
||||
|
||||
/**
|
||||
* Tests for {@link OidcAuthorizationCodeAuthenticationProvider}.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @author Mark Heckler
|
||||
*/
|
||||
public class OidcAuthorizationCodeAuthenticationProviderTests {
|
||||
private ClientRegistration clientRegistration;
|
||||
|
@ -81,6 +84,9 @@ public class OidcAuthorizationCodeAuthenticationProviderTests {
|
|||
private OAuth2AccessTokenResponse accessTokenResponse;
|
||||
private OAuth2UserService<OidcUserRequest, OidcUser> userService;
|
||||
private OidcAuthorizationCodeAuthenticationProvider authenticationProvider;
|
||||
private StringKeyGenerator stringKeyGenerator = new Base64StringKeyGenerator(Base64.getUrlEncoder().withoutPadding(), 96);
|
||||
private String nonce = this.stringKeyGenerator.generateKey();
|
||||
private String nonceHash;
|
||||
|
||||
@Rule
|
||||
public ExpectedException exception = ExpectedException.none();
|
||||
|
@ -88,8 +94,21 @@ public class OidcAuthorizationCodeAuthenticationProviderTests {
|
|||
@Before
|
||||
@SuppressWarnings("unchecked")
|
||||
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.authorizationRequest = request().scope("openid", "profile", "email").build();
|
||||
this.authorizationRequest = request()
|
||||
.scope("openid", "profile", "email")
|
||||
.attributes(attributes)
|
||||
.additionalParameters(additionalParameters)
|
||||
.build();
|
||||
this.authorizationResponse = success().build();
|
||||
this.authorizationExchange = new OAuth2AuthorizationExchange(this.authorizationRequest, this.authorizationResponse);
|
||||
this.accessTokenResponseClient = mock(OAuth2AccessTokenResponseClient.class);
|
||||
|
@ -228,6 +247,7 @@ public class OidcAuthorizationCodeAuthenticationProviderTests {
|
|||
claims.put(IdTokenClaimNames.SUB, "subject1");
|
||||
claims.put(IdTokenClaimNames.AUD, Arrays.asList("client1", "client2"));
|
||||
claims.put(IdTokenClaimNames.AZP, "client1");
|
||||
claims.put(IdTokenClaimNames.NONCE, nonceHash);
|
||||
this.setUpIdToken(claims);
|
||||
|
||||
OidcUser principal = mock(OidcUser.class);
|
||||
|
@ -257,6 +277,7 @@ public class OidcAuthorizationCodeAuthenticationProviderTests {
|
|||
claims.put(IdTokenClaimNames.SUB, "subject1");
|
||||
claims.put(IdTokenClaimNames.AUD, Arrays.asList("client1", "client2"));
|
||||
claims.put(IdTokenClaimNames.AZP, "client1");
|
||||
claims.put(IdTokenClaimNames.NONCE, nonceHash);
|
||||
this.setUpIdToken(claims);
|
||||
|
||||
OidcUser principal = mock(OidcUser.class);
|
||||
|
@ -286,6 +307,7 @@ public class OidcAuthorizationCodeAuthenticationProviderTests {
|
|||
claims.put(IdTokenClaimNames.SUB, "subject1");
|
||||
claims.put(IdTokenClaimNames.AUD, Arrays.asList("client1", "client2"));
|
||||
claims.put(IdTokenClaimNames.AZP, "client1");
|
||||
claims.put(IdTokenClaimNames.NONCE, nonceHash);
|
||||
this.setUpIdToken(claims);
|
||||
|
||||
OidcUser principal = mock(OidcUser.class);
|
||||
|
@ -302,9 +324,49 @@ public class OidcAuthorizationCodeAuthenticationProviderTests {
|
|||
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);
|
||||
when(jwtDecoder.decode(anyString())).thenReturn(idToken);
|
||||
this.authenticationProvider.setJwtDecoderFactory(registration -> jwtDecoder);
|
||||
|
@ -317,6 +379,7 @@ public class OidcAuthorizationCodeAuthenticationProviderTests {
|
|||
additionalParameters.put("param1", "value1");
|
||||
additionalParameters.put("param2", "value2");
|
||||
additionalParameters.put(OidcParameterNames.ID_TOKEN, "id-token");
|
||||
additionalParameters.put(IdTokenClaimNames.NONCE, nonceHash);
|
||||
|
||||
return OAuth2AccessTokenResponse
|
||||
.withToken("access-token-1234")
|
||||
|
@ -328,4 +391,25 @@ public class OidcAuthorizationCodeAuthenticationProviderTests {
|
|||
.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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.core.AuthorizationGrantType;
|
||||
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.assertThatThrownBy;
|
||||
|
@ -37,6 +35,7 @@ import static org.assertj.core.api.Assertions.entry;
|
|||
* Tests for {@link DefaultOAuth2AuthorizationRequestResolver}.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @author Mark Heckler
|
||||
*/
|
||||
public class DefaultOAuth2AuthorizationRequestResolverTests {
|
||||
private ClientRegistration registration1;
|
||||
|
@ -119,12 +118,14 @@ public class DefaultOAuth2AuthorizationRequestResolverTests {
|
|||
assertThat(authorizationRequest.getState()).isNotNull();
|
||||
assertThat(authorizationRequest.getAdditionalParameters()).doesNotContainKey(OAuth2ParameterNames.REGISTRATION_ID);
|
||||
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())
|
||||
.matches("https://example.com/login/oauth/authorize\\?" +
|
||||
"response_type=code&client_id=client-id&" +
|
||||
"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
|
||||
|
@ -137,7 +138,8 @@ public class DefaultOAuth2AuthorizationRequestResolverTests {
|
|||
OAuth2AuthorizationRequest authorizationRequest = this.resolver.resolve(request, clientRegistration.getRegistrationId());
|
||||
assertThat(authorizationRequest).isNotNull();
|
||||
assertThat(authorizationRequest.getAttributes())
|
||||
.containsExactly(entry(OAuth2ParameterNames.REGISTRATION_ID, clientRegistration.getRegistrationId()));
|
||||
.containsOnlyKeys(OAuth2ParameterNames.REGISTRATION_ID, IdTokenClaimNames.NONCE)
|
||||
.contains(entry(OAuth2ParameterNames.REGISTRATION_ID, clientRegistration.getRegistrationId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -259,7 +261,8 @@ public class DefaultOAuth2AuthorizationRequestResolverTests {
|
|||
.matches("https://example.com/login/oauth/authorize\\?" +
|
||||
"response_type=code&client_id=client-id&" +
|
||||
"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
|
||||
|
@ -277,7 +280,8 @@ public class DefaultOAuth2AuthorizationRequestResolverTests {
|
|||
.matches("https://example.com/login/oauth/authorize\\?" +
|
||||
"response_type=code&client_id=client-id&" +
|
||||
"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
|
||||
|
@ -292,7 +296,8 @@ public class DefaultOAuth2AuthorizationRequestResolverTests {
|
|||
.matches("https://example.com/login/oauth/authorize\\?" +
|
||||
"response_type=code&client_id=client-id&" +
|
||||
"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
|
||||
|
@ -307,7 +312,8 @@ public class DefaultOAuth2AuthorizationRequestResolverTests {
|
|||
.matches("https://example.com/login/oauth/authorize\\?" +
|
||||
"response_type=code&client_id=client-id-2&" +
|
||||
"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
|
||||
|
@ -323,7 +329,8 @@ public class DefaultOAuth2AuthorizationRequestResolverTests {
|
|||
.matches("https://example.com/login/oauth/authorize\\?" +
|
||||
"response_type=code&client_id=client-id&" +
|
||||
"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
|
||||
|
@ -339,7 +346,8 @@ public class DefaultOAuth2AuthorizationRequestResolverTests {
|
|||
.matches("https://example.com/login/oauth/authorize\\?" +
|
||||
"response_type=code&client_id=client-id-2&" +
|
||||
"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
|
||||
|
@ -375,6 +383,7 @@ public class DefaultOAuth2AuthorizationRequestResolverTests {
|
|||
"scope=read:user&state=.{15,}&" +
|
||||
"redirect_uri=http://localhost/login/oauth2/code/pkce-client-registration-id&" +
|
||||
"code_challenge_method=S256&" +
|
||||
"nonce=([a-zA-Z0-9\\-\\.\\_\\~]){43}&" +
|
||||
"code_challenge=([a-zA-Z0-9\\-\\.\\_\\~]){43}");
|
||||
}
|
||||
|
||||
|
|
|
@ -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");
|
||||
* 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}.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @author Mark Heckler
|
||||
*/
|
||||
public class OAuth2AuthorizationRequestRedirectFilterTests {
|
||||
private ClientRegistration registration1;
|
||||
|
@ -154,7 +155,8 @@ public class OAuth2AuthorizationRequestRedirectFilterTests {
|
|||
assertThat(response.getRedirectedUrl()).matches("https://example.com/login/oauth/authorize\\?" +
|
||||
"response_type=code&client_id=client-id&" +
|
||||
"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
|
||||
|
@ -234,7 +236,8 @@ public class OAuth2AuthorizationRequestRedirectFilterTests {
|
|||
assertThat(response.getRedirectedUrl()).matches("https://example.com/login/oauth/authorize\\?" +
|
||||
"response_type=code&client_id=client-id&" +
|
||||
"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
|
||||
|
@ -255,7 +258,8 @@ public class OAuth2AuthorizationRequestRedirectFilterTests {
|
|||
assertThat(response.getRedirectedUrl()).matches("https://example.com/login/oauth/authorize\\?" +
|
||||
"response_type=code&client_id=client-id&" +
|
||||
"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));
|
||||
}
|
||||
|
||||
|
@ -359,6 +363,7 @@ public class OAuth2AuthorizationRequestRedirectFilterTests {
|
|||
"response_type=code&client_id=client-id&" +
|
||||
"scope=read:user&state=.{15,}&" +
|
||||
"redirect_uri=http://localhost/login/oauth2/code/registration-id&" +
|
||||
"nonce=([a-zA-Z0-9\\-\\.\\_\\~]){43}&" +
|
||||
"login_hint=user@provider\\.com");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,6 +41,7 @@ import static org.mockito.Mockito.when;
|
|||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
* @author Mark Heckler
|
||||
* @since 5.1
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
|
@ -82,7 +83,8 @@ public class DefaultServerOAuth2AuthorizationRequestResolverTests {
|
|||
assertThat(request.getAuthorizationRequestUri()).matches("https://example.com/login/oauth/authorize\\?" +
|
||||
"response_type=code&client_id=client-id&" +
|
||||
"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) {
|
||||
|
@ -101,7 +103,8 @@ public class DefaultServerOAuth2AuthorizationRequestResolverTests {
|
|||
assertThat(request.getAuthorizationRequestUri()).matches("https://example.com/login/oauth/authorize\\?" +
|
||||
"response_type=code&client_id=client-id&" +
|
||||
"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
|
||||
|
@ -121,6 +124,7 @@ public class DefaultServerOAuth2AuthorizationRequestResolverTests {
|
|||
"scope=read:user&state=.*?&" +
|
||||
"redirect_uri=/login/oauth2/code/registration-id&" +
|
||||
"code_challenge_method=S256&" +
|
||||
"nonce=([a-zA-Z0-9\\-\\.\\_\\~]){43}&" +
|
||||
"code_challenge=([a-zA-Z0-9\\-\\.\\_\\~]){43}");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
* 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.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @author Mark Heckler
|
||||
* @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>
|
||||
*/
|
||||
|
@ -30,4 +31,9 @@ public interface OidcParameterNames {
|
|||
*/
|
||||
String ID_TOKEN = "id_token";
|
||||
|
||||
/**
|
||||
* {@code nonce} - used in the Access Token Request and Response.
|
||||
*/
|
||||
String NONCE = "nonce";
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue