Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
931aaa4e94 | ||
|
|
ec7ab5c956 | ||
|
|
fdf0a2f94c | ||
|
|
b37d4dd31e | ||
|
|
4199ab0172 | ||
|
|
7dfdcf3a27 | ||
|
|
0cae3c693e | ||
|
|
d6ff0f3fc7 | ||
|
|
c75b8a1cb9 | ||
|
|
77d665fe97 | ||
|
|
dc79172a4b | ||
|
|
e204b6bced | ||
|
|
d46bdfc80b | ||
|
|
421a9723ea |
@@ -34,7 +34,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
jdk: [11]
|
||||
jdk: [8,11,17]
|
||||
fail-fast: false
|
||||
runs-on: ${{ matrix.os }}
|
||||
if: needs.prerequisites.outputs.runjobs
|
||||
@@ -70,7 +70,7 @@ jobs:
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 11
|
||||
java-version: 8
|
||||
- name: Setup Gradle
|
||||
uses: gradle/gradle-build-action@v2
|
||||
- name: Snapshot Tests
|
||||
@@ -90,7 +90,7 @@ jobs:
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 11
|
||||
java-version: 8
|
||||
- name: Setup Gradle
|
||||
uses: gradle/gradle-build-action@v2
|
||||
- name: Deploy Artifacts
|
||||
@@ -114,7 +114,7 @@ jobs:
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 11
|
||||
java-version: 8
|
||||
- name: Setup Gradle
|
||||
uses: gradle/gradle-build-action@v2
|
||||
- name: Deploy Docs
|
||||
|
||||
2
.github/workflows/pr-build-workflow.yml
vendored
2
.github/workflows/pr-build-workflow.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
jdk: [11]
|
||||
jdk: [8]
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
@@ -13,7 +13,7 @@ This project uses https://www.zenhub.com/[ZenHub] to prioritize the feature road
|
||||
The project board can be accessed https://app.zenhub.com/workspaces/authorization-server-5e8f3182b5e8f5841bfc4902/board?repos=248032165[here].
|
||||
It is recommended to install the ZenHub https://www.zenhub.com/extension[browser extension] as it integrates natively within GitHub's user interface.
|
||||
|
||||
The completed and upcoming feature list can be viewed in the https://github.com/spring-projects/spring-authorization-server/wiki/Feature-List[wiki].
|
||||
The feature list can be viewed in the https://docs.spring.io/spring-authorization-server/docs/current/reference/html/overview.html#feature-list[reference documentation].
|
||||
|
||||
== Support Policy
|
||||
The Spring Authorization Server project provides software support and is documented in its link:SUPPORT_POLICY.adoc[support policy].
|
||||
@@ -52,9 +52,9 @@ In the instructions below, https://vimeo.com/34436402[`./gradlew`] is invoked fr
|
||||
a cross-platform, self-contained bootstrap mechanism for the build.
|
||||
|
||||
=== Prerequisites
|
||||
https://help.github.com/set-up-git-redirect[Git] and the https://www.oracle.com/technetwork/java/javase/downloads[JDK11 build].
|
||||
https://help.github.com/set-up-git-redirect[Git] and the https://www.oracle.com/technetwork/java/javase/downloads[JDK8 build].
|
||||
|
||||
Be sure that your `JAVA_HOME` environment variable points to the `jdk11` folder extracted from the JDK download.
|
||||
Be sure that your `JAVA_HOME` environment variable points to the `jdk1.8.0` folder extracted from the JDK download.
|
||||
|
||||
=== Check out sources
|
||||
[indent=0]
|
||||
|
||||
@@ -4,7 +4,7 @@ plugins {
|
||||
id "groovy"
|
||||
}
|
||||
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
|
||||
@@ -73,14 +73,16 @@ public class SpringJavaPlugin implements Plugin<Project> {
|
||||
|
||||
// Apply Java source compatibility version
|
||||
JavaPluginExtension java = project.getExtensions().getByType(JavaPluginExtension.class);
|
||||
java.setTargetCompatibility(JavaVersion.VERSION_11);
|
||||
java.setTargetCompatibility(JavaVersion.VERSION_1_8);
|
||||
|
||||
// Configure Java tasks
|
||||
project.getTasks().withType(JavaCompile.class, (javaCompile) -> {
|
||||
CompileOptions options = javaCompile.getOptions();
|
||||
options.setEncoding("UTF-8");
|
||||
options.getCompilerArgs().add("-parameters");
|
||||
options.getRelease().set(11);
|
||||
if (JavaVersion.current().isJava11Compatible()) {
|
||||
options.getRelease().set(8);
|
||||
}
|
||||
});
|
||||
project.getTasks().withType(Jar.class, (jar) -> jar.manifest((manifest) -> {
|
||||
Map<String, String> attributes = new HashMap<>();
|
||||
|
||||
@@ -54,7 +54,7 @@ public class SpringJavadocApiPlugin implements Plugin<Project> {
|
||||
api.doLast(new Action<Task>() {
|
||||
@Override
|
||||
public void execute(Task task) {
|
||||
if (JavaVersion.current().isJava11Compatible()) {
|
||||
if (JavaVersion.current().isJava8Compatible()) {
|
||||
project.copy((copy) -> copy.from(api.getDestinationDir())
|
||||
.into(api.getDestinationDir())
|
||||
.include("element-list")
|
||||
|
||||
@@ -30,8 +30,6 @@ public class SpringJavadocOptionsPlugin implements Plugin<Project> {
|
||||
project.getTasks().withType(Javadoc.class, (javadoc) -> {
|
||||
StandardJavadocDocletOptions options = (StandardJavadocDocletOptions) javadoc.getOptions();
|
||||
options.addStringOption("Xdoclint:none", "-quiet");
|
||||
// Workaround for Java 11 javadoc search bug. Can be removed with Java 17.
|
||||
options.addBooleanOption("-no-module-directories", true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,6 @@ dependencies {
|
||||
api "com.squareup.okhttp3:mockwebserver:4.9.3"
|
||||
api "com.squareup.okhttp3:okhttp:4.9.3"
|
||||
api "com.jayway.jsonpath:json-path:2.7.0"
|
||||
api "org.hsqldb:hsqldb:2.6.1"
|
||||
api "org.hsqldb:hsqldb:2.5.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ plugins {
|
||||
|
||||
group = project.rootProject.group
|
||||
version = project.rootProject.version
|
||||
sourceCompatibility = "11"
|
||||
sourceCompatibility = "1.8"
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
|
||||
@@ -6,7 +6,7 @@ If you are just getting started with Spring Authorization Server, the following
|
||||
[[system-requirements]]
|
||||
== System Requirements
|
||||
|
||||
Spring Authorization Server requires a Java 11 or higher Runtime Environment.
|
||||
Spring Authorization Server requires a Java 8 or higher Runtime Environment.
|
||||
|
||||
[[installing-spring-authorization-server]]
|
||||
== Installing Spring Authorization Server
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
version=0.3.0
|
||||
org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError
|
||||
version=0.3.1
|
||||
org.gradle.jvmargs=-Xmx3g -XX:+HeapDumpOnOutOfMemoryError
|
||||
org.gradle.parallel=true
|
||||
org.gradle.caching=true
|
||||
springFrameworkVersion=5.3.20
|
||||
|
||||
@@ -24,7 +24,7 @@ package org.springframework.security.oauth2.core;
|
||||
public final class Version {
|
||||
private static final int MAJOR = 0;
|
||||
private static final int MINOR = 3;
|
||||
private static final int PATCH = 0;
|
||||
private static final int PATCH = 1;
|
||||
|
||||
/**
|
||||
* Global Serialization value for Spring Security Authorization Server classes.
|
||||
|
||||
@@ -122,9 +122,10 @@ final class CodeVerifierAuthenticator {
|
||||
} catch (NoSuchAlgorithmException ex) {
|
||||
// It is unlikely that SHA-256 is not available on the server. If it is not available,
|
||||
// there will likely be bigger issues as well. We default to SERVER_ERROR.
|
||||
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.SERVER_ERROR);
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void throwInvalidGrant(String parameterName) {
|
||||
|
||||
@@ -179,7 +179,12 @@ public final class OAuth2AuthorizationCodeAuthenticationProvider implements Auth
|
||||
// ----- ID token -----
|
||||
OidcIdToken idToken;
|
||||
if (authorizationRequest.getScopes().contains(OidcScopes.OPENID)) {
|
||||
tokenContext = tokenContextBuilder.tokenType(ID_TOKEN_TOKEN_TYPE).build();
|
||||
// @formatter:off
|
||||
tokenContext = tokenContextBuilder
|
||||
.tokenType(ID_TOKEN_TOKEN_TYPE)
|
||||
.authorization(authorizationBuilder.build()) // ID token customizer may need access to the access token and/or refresh token
|
||||
.build();
|
||||
// @formatter:on
|
||||
OAuth2Token generatedIdToken = this.tokenGenerator.generate(tokenContext);
|
||||
if (!(generatedIdToken instanceof Jwt)) {
|
||||
OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR,
|
||||
|
||||
@@ -209,11 +209,9 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationProvider implemen
|
||||
String codeChallenge = (String) authorizationCodeRequestAuthentication.getAdditionalParameters().get(PkceParameterNames.CODE_CHALLENGE);
|
||||
if (StringUtils.hasText(codeChallenge)) {
|
||||
String codeChallengeMethod = (String) authorizationCodeRequestAuthentication.getAdditionalParameters().get(PkceParameterNames.CODE_CHALLENGE_METHOD);
|
||||
if (StringUtils.hasText(codeChallengeMethod)) {
|
||||
if (!"S256".equals(codeChallengeMethod)) {
|
||||
throwError(OAuth2ErrorCodes.INVALID_REQUEST, PkceParameterNames.CODE_CHALLENGE_METHOD, PKCE_ERROR_URI,
|
||||
authorizationCodeRequestAuthentication, registeredClient, null);
|
||||
}
|
||||
if (!StringUtils.hasText(codeChallengeMethod) || !"S256".equals(codeChallengeMethod)) {
|
||||
throwError(OAuth2ErrorCodes.INVALID_REQUEST, PkceParameterNames.CODE_CHALLENGE_METHOD, PKCE_ERROR_URI,
|
||||
authorizationCodeRequestAuthentication, registeredClient, null);
|
||||
}
|
||||
} else if (registeredClient.getClientSettings().isRequireProofKey()) {
|
||||
throwError(OAuth2ErrorCodes.INVALID_REQUEST, PkceParameterNames.CODE_CHALLENGE, PKCE_ERROR_URI,
|
||||
@@ -477,69 +475,6 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationProvider implemen
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean isValidRedirectUri(String requestedRedirectUri, RegisteredClient registeredClient) {
|
||||
UriComponents requestedRedirect;
|
||||
try {
|
||||
requestedRedirect = UriComponentsBuilder.fromUriString(requestedRedirectUri).build();
|
||||
if (requestedRedirect.getFragment() != null) {
|
||||
return false;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String requestedRedirectHost = requestedRedirect.getHost();
|
||||
if (requestedRedirectHost == null || requestedRedirectHost.equals("localhost")) {
|
||||
// As per https://tools.ietf.org/html/draft-ietf-oauth-v2-1-01#section-9.7.1
|
||||
// While redirect URIs using localhost (i.e.,
|
||||
// "http://localhost:{port}/{path}") function similarly to loopback IP
|
||||
// redirects described in Section 10.3.3, the use of "localhost" is NOT RECOMMENDED.
|
||||
return false;
|
||||
}
|
||||
if (!isLoopbackAddress(requestedRedirectHost)) {
|
||||
// As per https://tools.ietf.org/html/draft-ietf-oauth-v2-1-01#section-9.7
|
||||
// When comparing client redirect URIs against pre-registered URIs,
|
||||
// authorization servers MUST utilize exact string matching.
|
||||
return registeredClient.getRedirectUris().contains(requestedRedirectUri);
|
||||
}
|
||||
|
||||
// As per https://tools.ietf.org/html/draft-ietf-oauth-v2-1-01#section-10.3.3
|
||||
// The authorization server MUST allow any port to be specified at the
|
||||
// time of the request for loopback IP redirect URIs, to accommodate
|
||||
// clients that obtain an available ephemeral port from the operating
|
||||
// system at the time of the request.
|
||||
for (String registeredRedirectUri : registeredClient.getRedirectUris()) {
|
||||
UriComponentsBuilder registeredRedirect = UriComponentsBuilder.fromUriString(registeredRedirectUri);
|
||||
registeredRedirect.port(requestedRedirect.getPort());
|
||||
if (registeredRedirect.build().toString().equals(requestedRedirect.toString())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean isLoopbackAddress(String host) {
|
||||
// IPv6 loopback address should either be "0:0:0:0:0:0:0:1" or "::1"
|
||||
if ("[0:0:0:0:0:0:0:1]".equals(host) || "[::1]".equals(host)) {
|
||||
return true;
|
||||
}
|
||||
// IPv4 loopback address ranges from 127.0.0.1 to 127.255.255.255
|
||||
String[] ipv4Octets = host.split("\\.");
|
||||
if (ipv4Octets.length != 4) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
int[] address = new int[ipv4Octets.length];
|
||||
for (int i=0; i < ipv4Octets.length; i++) {
|
||||
address[i] = Integer.parseInt(ipv4Octets[i]);
|
||||
}
|
||||
return address[0] == 127 && address[1] >= 0 && address[1] <= 255 && address[2] >= 0 &&
|
||||
address[2] <= 255 && address[3] >= 1 && address[3] <= 255;
|
||||
} catch (NumberFormatException ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isPrincipalAuthenticated(Authentication principal) {
|
||||
return principal != null &&
|
||||
!AnonymousAuthenticationToken.class.isAssignableFrom(principal.getClass()) &&
|
||||
@@ -562,9 +497,16 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationProvider implemen
|
||||
private static void throwError(String errorCode, String parameterName, String errorUri,
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication,
|
||||
RegisteredClient registeredClient, OAuth2AuthorizationRequest authorizationRequest) {
|
||||
OAuth2Error error = new OAuth2Error(errorCode, "OAuth 2.0 Parameter: " + parameterName, errorUri);
|
||||
throwError(error, parameterName, authorizationCodeRequestAuthentication, registeredClient, authorizationRequest);
|
||||
}
|
||||
|
||||
private static void throwError(OAuth2Error error, String parameterName,
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication,
|
||||
RegisteredClient registeredClient, OAuth2AuthorizationRequest authorizationRequest) {
|
||||
|
||||
boolean redirectOnError = true;
|
||||
if (errorCode.equals(OAuth2ErrorCodes.INVALID_REQUEST) &&
|
||||
if (error.getErrorCode().equals(OAuth2ErrorCodes.INVALID_REQUEST) &&
|
||||
(parameterName.equals(OAuth2ParameterNames.CLIENT_ID) ||
|
||||
parameterName.equals(OAuth2ParameterNames.REDIRECT_URI) ||
|
||||
parameterName.equals(OAuth2ParameterNames.STATE))) {
|
||||
@@ -589,7 +531,6 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationProvider implemen
|
||||
authorizationCodeRequestAuthenticationResult.setAuthenticated(authorizationCodeRequestAuthentication.isAuthenticated());
|
||||
}
|
||||
|
||||
OAuth2Error error = new OAuth2Error(errorCode, "OAuth 2.0 Parameter: " + parameterName, errorUri);
|
||||
throw new OAuth2AuthorizationCodeRequestAuthenticationException(error, authorizationCodeRequestAuthenticationResult);
|
||||
}
|
||||
|
||||
@@ -639,16 +580,95 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationProvider implemen
|
||||
authenticationContext.getAuthentication();
|
||||
RegisteredClient registeredClient = authenticationContext.get(RegisteredClient.class);
|
||||
|
||||
if (StringUtils.hasText(authorizationCodeRequestAuthentication.getRedirectUri())) {
|
||||
if (!isValidRedirectUri(authorizationCodeRequestAuthentication.getRedirectUri(), registeredClient)) {
|
||||
String requestedRedirectUri = authorizationCodeRequestAuthentication.getRedirectUri();
|
||||
|
||||
if (StringUtils.hasText(requestedRedirectUri)) {
|
||||
// ***** redirect_uri is available in authorization request
|
||||
|
||||
UriComponents requestedRedirect = null;
|
||||
try {
|
||||
requestedRedirect = UriComponentsBuilder.fromUriString(requestedRedirectUri).build();
|
||||
} catch (Exception ex) { }
|
||||
if (requestedRedirect == null || requestedRedirect.getFragment() != null) {
|
||||
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI,
|
||||
authorizationCodeRequestAuthentication, registeredClient);
|
||||
}
|
||||
} else if (authorizationCodeRequestAuthentication.getScopes().contains(OidcScopes.OPENID) ||
|
||||
registeredClient.getRedirectUris().size() != 1) {
|
||||
// redirect_uri is REQUIRED for OpenID Connect
|
||||
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI,
|
||||
authorizationCodeRequestAuthentication, registeredClient);
|
||||
|
||||
String requestedRedirectHost = requestedRedirect.getHost();
|
||||
if (requestedRedirectHost == null || requestedRedirectHost.equals("localhost")) {
|
||||
// As per https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-01#section-9.7.1
|
||||
// While redirect URIs using localhost (i.e., "http://localhost:{port}/{path}")
|
||||
// function similarly to loopback IP redirects described in Section 10.3.3,
|
||||
// the use of "localhost" is NOT RECOMMENDED.
|
||||
OAuth2Error error = new OAuth2Error(
|
||||
OAuth2ErrorCodes.INVALID_REQUEST,
|
||||
"localhost is not allowed for the redirect_uri (" + requestedRedirectUri + "). " +
|
||||
"Use the IP literal (127.0.0.1) instead.",
|
||||
"https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-01#section-9.7.1");
|
||||
throwError(error, OAuth2ParameterNames.REDIRECT_URI,
|
||||
authorizationCodeRequestAuthentication, registeredClient, null);
|
||||
}
|
||||
|
||||
if (!isLoopbackAddress(requestedRedirectHost)) {
|
||||
// As per https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-01#section-9.7
|
||||
// When comparing client redirect URIs against pre-registered URIs,
|
||||
// authorization servers MUST utilize exact string matching.
|
||||
if (!registeredClient.getRedirectUris().contains(requestedRedirectUri)) {
|
||||
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI,
|
||||
authorizationCodeRequestAuthentication, registeredClient);
|
||||
}
|
||||
} else {
|
||||
// As per https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-01#section-10.3.3
|
||||
// The authorization server MUST allow any port to be specified at the
|
||||
// time of the request for loopback IP redirect URIs, to accommodate
|
||||
// clients that obtain an available ephemeral port from the operating
|
||||
// system at the time of the request.
|
||||
boolean validRedirectUri = false;
|
||||
for (String registeredRedirectUri : registeredClient.getRedirectUris()) {
|
||||
UriComponentsBuilder registeredRedirect = UriComponentsBuilder.fromUriString(registeredRedirectUri);
|
||||
registeredRedirect.port(requestedRedirect.getPort());
|
||||
if (registeredRedirect.build().toString().equals(requestedRedirect.toString())) {
|
||||
validRedirectUri = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!validRedirectUri) {
|
||||
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI,
|
||||
authorizationCodeRequestAuthentication, registeredClient);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// ***** redirect_uri is NOT available in authorization request
|
||||
|
||||
if (authorizationCodeRequestAuthentication.getScopes().contains(OidcScopes.OPENID) ||
|
||||
registeredClient.getRedirectUris().size() != 1) {
|
||||
// redirect_uri is REQUIRED for OpenID Connect
|
||||
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI,
|
||||
authorizationCodeRequestAuthentication, registeredClient);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isLoopbackAddress(String host) {
|
||||
// IPv6 loopback address should either be "0:0:0:0:0:0:0:1" or "::1"
|
||||
if ("[0:0:0:0:0:0:0:1]".equals(host) || "[::1]".equals(host)) {
|
||||
return true;
|
||||
}
|
||||
// IPv4 loopback address ranges from 127.0.0.1 to 127.255.255.255
|
||||
String[] ipv4Octets = host.split("\\.");
|
||||
if (ipv4Octets.length != 4) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
int[] address = new int[ipv4Octets.length];
|
||||
for (int i=0; i < ipv4Octets.length; i++) {
|
||||
address[i] = Integer.parseInt(ipv4Octets[i]);
|
||||
}
|
||||
return address[0] == 127 && address[1] >= 0 && address[1] <= 255 && address[2] >= 0 &&
|
||||
address[2] <= 255 && address[3] >= 1 && address[3] <= 255;
|
||||
} catch (NumberFormatException ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -176,7 +176,12 @@ public final class OAuth2RefreshTokenAuthenticationProvider implements Authentic
|
||||
// ----- ID token -----
|
||||
OidcIdToken idToken;
|
||||
if (authorizedScopes.contains(OidcScopes.OPENID)) {
|
||||
tokenContext = tokenContextBuilder.tokenType(ID_TOKEN_TOKEN_TYPE).build();
|
||||
// @formatter:off
|
||||
tokenContext = tokenContextBuilder
|
||||
.tokenType(ID_TOKEN_TOKEN_TYPE)
|
||||
.authorization(authorizationBuilder.build()) // ID token customizer may need access to the access token and/or refresh token
|
||||
.build();
|
||||
// @formatter:on
|
||||
OAuth2Token generatedIdToken = this.tokenGenerator.generate(tokenContext);
|
||||
if (!(generatedIdToken instanceof Jwt)) {
|
||||
OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR,
|
||||
|
||||
@@ -93,6 +93,10 @@ public final class OidcProviderConfigurationEndpointFilter extends OncePerReques
|
||||
.grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue())
|
||||
.grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())
|
||||
.grantType(AuthorizationGrantType.REFRESH_TOKEN.getValue())
|
||||
.tokenRevocationEndpoint(asUrl(issuer, this.providerSettings.getTokenRevocationEndpoint()))
|
||||
.tokenRevocationEndpointAuthenticationMethods(clientAuthenticationMethods())
|
||||
.tokenIntrospectionEndpoint(asUrl(issuer, this.providerSettings.getTokenIntrospectionEndpoint()))
|
||||
.tokenIntrospectionEndpointAuthenticationMethods(clientAuthenticationMethods())
|
||||
.subjectType("public")
|
||||
.idTokenSigningAlgorithm(SignatureAlgorithm.RS256.getName())
|
||||
.scope(OidcScopes.OPENID)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2021 the original author or authors.
|
||||
* Copyright 2020-2022 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.
|
||||
@@ -28,6 +28,7 @@ import javax.servlet.http.HttpServletResponse;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.authentication.AuthenticationDetailsSource;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
@@ -45,6 +46,7 @@ import org.springframework.security.web.RedirectStrategy;
|
||||
import org.springframework.security.web.authentication.AuthenticationConverter;
|
||||
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
|
||||
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
|
||||
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
|
||||
import org.springframework.security.web.util.RedirectUrlBuilder;
|
||||
import org.springframework.security.web.util.UrlUtils;
|
||||
import org.springframework.security.web.util.matcher.AndRequestMatcher;
|
||||
@@ -82,6 +84,7 @@ public final class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilte
|
||||
private final AuthenticationManager authenticationManager;
|
||||
private final RequestMatcher authorizationEndpointMatcher;
|
||||
private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
|
||||
private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = new WebAuthenticationDetailsSource();
|
||||
private AuthenticationConverter authenticationConverter;
|
||||
private AuthenticationSuccessHandler authenticationSuccessHandler = this::sendAuthorizationResponse;
|
||||
private AuthenticationFailureHandler authenticationFailureHandler = this::sendErrorResponse;
|
||||
@@ -144,6 +147,7 @@ public final class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilte
|
||||
try {
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication =
|
||||
(OAuth2AuthorizationCodeRequestAuthenticationToken) this.authenticationConverter.convert(request);
|
||||
authorizationCodeRequestAuthentication.setDetails(this.authenticationDetailsSource.buildDetails(request));
|
||||
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthenticationResult =
|
||||
(OAuth2AuthorizationCodeRequestAuthenticationToken) this.authenticationManager.authenticate(authorizationCodeRequestAuthentication);
|
||||
@@ -169,6 +173,17 @@ public final class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilte
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link AuthenticationDetailsSource} used for building an authentication details instance from {@link HttpServletRequest}.
|
||||
*
|
||||
* @param authenticationDetailsSource the {@link AuthenticationDetailsSource} used for building an authentication details instance from {@link HttpServletRequest}
|
||||
* @since 0.3.1
|
||||
*/
|
||||
public void setAuthenticationDetailsSource(AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource) {
|
||||
Assert.notNull(authenticationDetailsSource, "authenticationDetailsSource cannot be null");
|
||||
this.authenticationDetailsSource = authenticationDetailsSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link AuthenticationConverter} used when attempting to extract an Authorization Request (or Consent) from {@link HttpServletRequest}
|
||||
* to an instance of {@link OAuth2AuthorizationCodeRequestAuthenticationToken} used for authenticating the request.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2021 the original author or authors.
|
||||
* Copyright 2020-2022 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.
|
||||
@@ -48,24 +48,29 @@ public class TestOAuth2Authorizations {
|
||||
}
|
||||
|
||||
public static OAuth2Authorization.Builder authorization(RegisteredClient registeredClient,
|
||||
OAuth2AccessToken accessToken, Map<String, Object> accessTokenClaims) {
|
||||
return authorization(registeredClient, accessToken, accessTokenClaims, Collections.emptyMap());
|
||||
}
|
||||
|
||||
public static OAuth2Authorization.Builder authorization(RegisteredClient registeredClient,
|
||||
Map<String, Object> authorizationRequestAdditionalParameters) {
|
||||
OAuth2AccessToken accessToken = new OAuth2AccessToken(
|
||||
OAuth2AccessToken.TokenType.BEARER, "access-token", Instant.now(), Instant.now().plusSeconds(300));
|
||||
return authorization(registeredClient, accessToken, Collections.emptyMap(), authorizationRequestAdditionalParameters);
|
||||
}
|
||||
|
||||
private static OAuth2Authorization.Builder authorization(RegisteredClient registeredClient,
|
||||
OAuth2AccessToken accessToken, Map<String, Object> accessTokenClaims,
|
||||
Map<String, Object> authorizationRequestAdditionalParameters) {
|
||||
OAuth2AuthorizationCode authorizationCode = new OAuth2AuthorizationCode(
|
||||
"code", Instant.now(), Instant.now().plusSeconds(120));
|
||||
OAuth2RefreshToken refreshToken = new OAuth2RefreshToken(
|
||||
"refresh-token", Instant.now(), Instant.now().plus(1, ChronoUnit.HOURS));
|
||||
OAuth2AccessToken accessToken = new OAuth2AccessToken(
|
||||
OAuth2AccessToken.TokenType.BEARER, "access-token", Instant.now(), Instant.now().plusSeconds(300));
|
||||
return authorization(registeredClient, authorizationCode, accessToken, Collections.emptyMap(), authorizationRequestAdditionalParameters);
|
||||
}
|
||||
|
||||
public static OAuth2Authorization.Builder authorization(RegisteredClient registeredClient,
|
||||
OAuth2AuthorizationCode authorizationCode) {
|
||||
return authorization(registeredClient, authorizationCode, null, Collections.emptyMap(), Collections.emptyMap());
|
||||
}
|
||||
|
||||
public static OAuth2Authorization.Builder authorization(RegisteredClient registeredClient,
|
||||
OAuth2AccessToken accessToken, Map<String, Object> accessTokenClaims) {
|
||||
OAuth2AuthorizationCode authorizationCode = new OAuth2AuthorizationCode(
|
||||
"code", Instant.now(), Instant.now().plusSeconds(120));
|
||||
return authorization(registeredClient, authorizationCode, accessToken, accessTokenClaims, Collections.emptyMap());
|
||||
}
|
||||
|
||||
private static OAuth2Authorization.Builder authorization(RegisteredClient registeredClient,
|
||||
OAuth2AuthorizationCode authorizationCode, OAuth2AccessToken accessToken,
|
||||
Map<String, Object> accessTokenClaims, Map<String, Object> authorizationRequestAdditionalParameters) {
|
||||
OAuth2AuthorizationRequest authorizationRequest = OAuth2AuthorizationRequest.authorizationCode()
|
||||
.authorizationUri("https://provider.com/oauth2/authorize")
|
||||
.clientId(registeredClient.getClientId())
|
||||
@@ -74,18 +79,25 @@ public class TestOAuth2Authorizations {
|
||||
.additionalParameters(authorizationRequestAdditionalParameters)
|
||||
.state("state")
|
||||
.build();
|
||||
return OAuth2Authorization.withRegisteredClient(registeredClient)
|
||||
OAuth2Authorization.Builder builder = OAuth2Authorization.withRegisteredClient(registeredClient)
|
||||
.id("id")
|
||||
.principalName("principal")
|
||||
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
|
||||
.token(authorizationCode)
|
||||
.token(accessToken, (metadata) -> metadata.putAll(tokenMetadata(accessTokenClaims)))
|
||||
.refreshToken(refreshToken)
|
||||
.attribute(OAuth2ParameterNames.STATE, "state")
|
||||
.attribute(OAuth2AuthorizationRequest.class.getName(), authorizationRequest)
|
||||
.attribute(Principal.class.getName(),
|
||||
new TestingAuthenticationToken("principal", null, "ROLE_A", "ROLE_B"))
|
||||
.attribute(OAuth2Authorization.AUTHORIZED_SCOPE_ATTRIBUTE_NAME, authorizationRequest.getScopes());
|
||||
if (accessToken != null) {
|
||||
OAuth2RefreshToken refreshToken = new OAuth2RefreshToken(
|
||||
"refresh-token", Instant.now(), Instant.now().plus(1, ChronoUnit.HOURS));
|
||||
builder
|
||||
.token(accessToken, (metadata) -> metadata.putAll(tokenMetadata(accessTokenClaims)))
|
||||
.refreshToken(refreshToken);
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
private static Map<String, Object> tokenMetadata(Map<String, Object> tokenClaims) {
|
||||
|
||||
@@ -443,7 +443,9 @@ public class OAuth2AuthorizationCodeAuthenticationProviderTests {
|
||||
@Test
|
||||
public void authenticateWhenValidCodeAndAuthenticationRequestThenReturnIdToken() {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scope(OidcScopes.OPENID).build();
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build();
|
||||
OAuth2AuthorizationCode authorizationCode = new OAuth2AuthorizationCode(
|
||||
"code", Instant.now(), Instant.now().plusSeconds(120));
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient, authorizationCode).build();
|
||||
when(this.authorizationService.findByToken(eq(AUTHORIZATION_CODE), eq(AUTHORIZATION_CODE_TOKEN_TYPE)))
|
||||
.thenReturn(authorization);
|
||||
|
||||
@@ -466,6 +468,7 @@ public class OAuth2AuthorizationCodeAuthenticationProviderTests {
|
||||
assertThat(accessTokenContext.getRegisteredClient()).isEqualTo(registeredClient);
|
||||
assertThat(accessTokenContext.<Authentication>getPrincipal()).isEqualTo(authorization.getAttribute(Principal.class.getName()));
|
||||
assertThat(accessTokenContext.getAuthorization()).isEqualTo(authorization);
|
||||
assertThat(accessTokenContext.getAuthorization().getAccessToken()).isNull();
|
||||
assertThat(accessTokenContext.getAuthorizedScopes())
|
||||
.isEqualTo(authorization.getAttribute(OAuth2Authorization.AUTHORIZED_SCOPE_ATTRIBUTE_NAME));
|
||||
assertThat(accessTokenContext.getTokenType()).isEqualTo(OAuth2TokenType.ACCESS_TOKEN);
|
||||
@@ -481,7 +484,8 @@ public class OAuth2AuthorizationCodeAuthenticationProviderTests {
|
||||
JwtEncodingContext idTokenContext = jwtEncodingContextCaptor.getAllValues().get(1);
|
||||
assertThat(idTokenContext.getRegisteredClient()).isEqualTo(registeredClient);
|
||||
assertThat(idTokenContext.<Authentication>getPrincipal()).isEqualTo(authorization.getAttribute(Principal.class.getName()));
|
||||
assertThat(idTokenContext.getAuthorization()).isEqualTo(authorization);
|
||||
assertThat(idTokenContext.getAuthorization()).isNotEqualTo(authorization);
|
||||
assertThat(idTokenContext.getAuthorization().getAccessToken()).isNotNull();
|
||||
assertThat(idTokenContext.getAuthorizedScopes())
|
||||
.isEqualTo(authorization.getAttribute(OAuth2Authorization.AUTHORIZED_SCOPE_ATTRIBUTE_NAME));
|
||||
assertThat(idTokenContext.getTokenType().getValue()).isEqualTo(OidcParameterNames.ID_TOKEN);
|
||||
@@ -503,8 +507,8 @@ public class OAuth2AuthorizationCodeAuthenticationProviderTests {
|
||||
assertThat(accessTokenAuthentication.getAccessToken().getScopes()).isEqualTo(accessTokenScopes);
|
||||
assertThat(accessTokenAuthentication.getRefreshToken()).isNotNull();
|
||||
assertThat(accessTokenAuthentication.getRefreshToken()).isEqualTo(updatedAuthorization.getRefreshToken().getToken());
|
||||
OAuth2Authorization.Token<OAuth2AuthorizationCode> authorizationCode = updatedAuthorization.getToken(OAuth2AuthorizationCode.class);
|
||||
assertThat(authorizationCode.isInvalidated()).isTrue();
|
||||
OAuth2Authorization.Token<OAuth2AuthorizationCode> authorizationCodeToken = updatedAuthorization.getToken(OAuth2AuthorizationCode.class);
|
||||
assertThat(authorizationCodeToken.isInvalidated()).isTrue();
|
||||
OAuth2Authorization.Token<OidcIdToken> idToken = updatedAuthorization.getToken(OidcIdToken.class);
|
||||
assertThat(idToken).isNotNull();
|
||||
assertThat(accessTokenAuthentication.getAdditionalParameters())
|
||||
|
||||
@@ -207,7 +207,10 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
|
||||
.satisfies(ex ->
|
||||
assertAuthenticationException((OAuth2AuthorizationCodeRequestAuthenticationException) ex,
|
||||
OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI, null)
|
||||
);
|
||||
)
|
||||
.extracting(ex -> ((OAuth2AuthorizationCodeRequestAuthenticationException) ex).getError())
|
||||
.satisfies(error ->
|
||||
assertThat(error.getDescription()).isEqualTo("localhost is not allowed for the redirect_uri (https://localhost:5000). Use the IP literal (127.0.0.1) instead."));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -377,6 +380,26 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
|
||||
);
|
||||
}
|
||||
|
||||
// gh-770
|
||||
@Test
|
||||
public void authenticateWhenPkceMissingCodeChallengeMethodThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||
.thenReturn(registeredClient);
|
||||
Map<String, Object> additionalParameters = new HashMap<>();
|
||||
additionalParameters.put(PkceParameterNames.CODE_CHALLENGE, "code-challenge");
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
|
||||
authorizationCodeRequestAuthentication(registeredClient, this.principal)
|
||||
.additionalParameters(additionalParameters)
|
||||
.build();
|
||||
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||
.isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class)
|
||||
.satisfies(ex ->
|
||||
assertAuthenticationException((OAuth2AuthorizationCodeRequestAuthenticationException) ex,
|
||||
OAuth2ErrorCodes.INVALID_REQUEST, PkceParameterNames.CODE_CHALLENGE_METHOD, authentication.getRedirectUri())
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenPrincipalNotAuthenticatedThenReturnAuthorizationCodeRequest() {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||
|
||||
@@ -233,7 +233,8 @@ public class OAuth2RefreshTokenAuthenticationProviderTests {
|
||||
JwtEncodingContext idTokenContext = jwtEncodingContextCaptor.getAllValues().get(1);
|
||||
assertThat(idTokenContext.getRegisteredClient()).isEqualTo(registeredClient);
|
||||
assertThat(idTokenContext.<Authentication>getPrincipal()).isEqualTo(authorization.getAttribute(Principal.class.getName()));
|
||||
assertThat(idTokenContext.getAuthorization()).isEqualTo(authorization);
|
||||
assertThat(idTokenContext.getAuthorization()).isNotEqualTo(authorization);
|
||||
assertThat(idTokenContext.getAuthorization().getAccessToken()).isNotEqualTo(authorization.getAccessToken());
|
||||
assertThat(idTokenContext.getAuthorizedScopes())
|
||||
.isEqualTo(authorization.getAttribute(OAuth2Authorization.AUTHORIZED_SCOPE_ATTRIBUTE_NAME));
|
||||
assertThat(idTokenContext.getTokenType().getValue()).isEqualTo(OidcParameterNames.ID_TOKEN);
|
||||
|
||||
@@ -275,7 +275,7 @@ public class PublicClientAuthenticationProviderTests {
|
||||
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||
.extracting("errorCode")
|
||||
.isEqualTo(OAuth2ErrorCodes.SERVER_ERROR);
|
||||
.isEqualTo(OAuth2ErrorCodes.INVALID_GRANT);
|
||||
}
|
||||
|
||||
private static Map<String, Object> createAuthorizationCodeTokenParameters() {
|
||||
|
||||
@@ -95,6 +95,8 @@ public class OidcProviderConfigurationEndpointFilterTests {
|
||||
String tokenEndpoint = "/oauth2/v1/token";
|
||||
String jwkSetEndpoint = "/oauth2/v1/jwks";
|
||||
String userInfoEndpoint = "/userinfo";
|
||||
String tokenRevocationEndpoint = "/oauth2/v1/revoke";
|
||||
String tokenIntrospectionEndpoint = "/oauth2/v1/introspect";
|
||||
|
||||
ProviderSettings providerSettings = ProviderSettings.builder()
|
||||
.issuer(issuer)
|
||||
@@ -102,6 +104,8 @@ public class OidcProviderConfigurationEndpointFilterTests {
|
||||
.tokenEndpoint(tokenEndpoint)
|
||||
.jwkSetEndpoint(jwkSetEndpoint)
|
||||
.oidcUserInfoEndpoint(userInfoEndpoint)
|
||||
.tokenRevocationEndpoint(tokenRevocationEndpoint)
|
||||
.tokenIntrospectionEndpoint(tokenIntrospectionEndpoint)
|
||||
.build();
|
||||
ProviderContextHolder.setProviderContext(new ProviderContext(providerSettings, null));
|
||||
OidcProviderConfigurationEndpointFilter filter =
|
||||
@@ -126,6 +130,10 @@ public class OidcProviderConfigurationEndpointFilterTests {
|
||||
assertThat(providerConfigurationResponse).contains("\"scopes_supported\":[\"openid\"]");
|
||||
assertThat(providerConfigurationResponse).contains("\"response_types_supported\":[\"code\"]");
|
||||
assertThat(providerConfigurationResponse).contains("\"grant_types_supported\":[\"authorization_code\",\"client_credentials\",\"refresh_token\"]");
|
||||
assertThat(providerConfigurationResponse).contains("\"revocation_endpoint\":\"https://example.com/issuer1/oauth2/v1/revoke\"");
|
||||
assertThat(providerConfigurationResponse).contains("\"revocation_endpoint_auth_methods_supported\":[\"client_secret_basic\",\"client_secret_post\",\"client_secret_jwt\",\"private_key_jwt\"]");
|
||||
assertThat(providerConfigurationResponse).contains("\"introspection_endpoint\":\"https://example.com/issuer1/oauth2/v1/introspect\"");
|
||||
assertThat(providerConfigurationResponse).contains("\"introspection_endpoint_auth_methods_supported\":[\"client_secret_basic\",\"client_secret_post\",\"client_secret_jwt\",\"private_key_jwt\"]");
|
||||
assertThat(providerConfigurationResponse).contains("\"subject_types_supported\":[\"public\"]");
|
||||
assertThat(providerConfigurationResponse).contains("\"id_token_signing_alg_values_supported\":[\"RS256\"]");
|
||||
assertThat(providerConfigurationResponse).contains("\"userinfo_endpoint\":\"https://example.com/issuer1/userinfo\"");
|
||||
|
||||
@@ -32,10 +32,12 @@ import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.security.authentication.AuthenticationDetailsSource;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
@@ -55,10 +57,12 @@ import org.springframework.security.oauth2.server.authorization.client.TestRegis
|
||||
import org.springframework.security.web.authentication.AuthenticationConverter;
|
||||
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
|
||||
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
|
||||
import org.springframework.security.web.authentication.WebAuthenticationDetails;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.assertj.core.api.InstanceOfAssertFactories.type;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.same;
|
||||
import static org.mockito.Mockito.mock;
|
||||
@@ -78,6 +82,7 @@ import static org.mockito.Mockito.when;
|
||||
*/
|
||||
public class OAuth2AuthorizationEndpointFilterTests {
|
||||
private static final String DEFAULT_AUTHORIZATION_ENDPOINT_URI = "/oauth2/authorize";
|
||||
private static final String REMOTE_ADDRESS = "remote-address";
|
||||
private AuthenticationManager authenticationManager;
|
||||
private OAuth2AuthorizationEndpointFilter filter;
|
||||
private TestingAuthenticationToken principal;
|
||||
@@ -116,6 +121,13 @@ public class OAuth2AuthorizationEndpointFilterTests {
|
||||
.hasMessage("authorizationEndpointUri cannot be empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setAuthenticationDetailsSourceWhenNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> this.filter.setAuthenticationDetailsSource(null))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("authenticationDetailsSource cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setAuthenticationConverterWhenNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> this.filter.setAuthenticationConverter(null))
|
||||
@@ -364,6 +376,32 @@ public class OAuth2AuthorizationEndpointFilterTests {
|
||||
verify(authenticationFailureHandler).onAuthenticationFailure(any(), any(), same(authenticationException));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterWhenCustomAuthenticationDetailsSourceThenUsed() throws Exception {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication =
|
||||
authorizationCodeRequestAuthentication(registeredClient, this.principal).build();
|
||||
MockHttpServletRequest request = createAuthorizationRequest(registeredClient);
|
||||
|
||||
AuthenticationDetailsSource<HttpServletRequest, WebAuthenticationDetails> authenticationDetailsSource =
|
||||
mock(AuthenticationDetailsSource.class);
|
||||
WebAuthenticationDetails webAuthenticationDetails = new WebAuthenticationDetails(request);
|
||||
when(authenticationDetailsSource.buildDetails(request)).thenReturn(webAuthenticationDetails);
|
||||
this.filter.setAuthenticationDetailsSource(authenticationDetailsSource);
|
||||
|
||||
when(this.authenticationManager.authenticate(any()))
|
||||
.thenReturn(authorizationCodeRequestAuthentication);
|
||||
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
FilterChain filterChain = mock(FilterChain.class);
|
||||
|
||||
this.filter.doFilter(request, response, filterChain);
|
||||
|
||||
verify(authenticationDetailsSource).buildDetails(any());
|
||||
verify(this.authenticationManager).authenticate(any());
|
||||
verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterWhenAuthorizationRequestPrincipalNotAuthenticatedThenCommenceAuthentication() throws Exception {
|
||||
this.principal.setAuthenticated(false);
|
||||
@@ -507,9 +545,15 @@ public class OAuth2AuthorizationEndpointFilterTests {
|
||||
|
||||
this.filter.doFilter(request, response, filterChain);
|
||||
|
||||
verify(this.authenticationManager).authenticate(any());
|
||||
ArgumentCaptor<OAuth2AuthorizationCodeRequestAuthenticationToken> authorizationCodeRequestAuthenticationCaptor =
|
||||
ArgumentCaptor.forClass(OAuth2AuthorizationCodeRequestAuthenticationToken.class);
|
||||
verify(this.authenticationManager).authenticate(authorizationCodeRequestAuthenticationCaptor.capture());
|
||||
verifyNoInteractions(filterChain);
|
||||
|
||||
assertThat(authorizationCodeRequestAuthenticationCaptor.getValue().getDetails())
|
||||
.asInstanceOf(type(WebAuthenticationDetails.class))
|
||||
.extracting(WebAuthenticationDetails::getRemoteAddress)
|
||||
.isEqualTo(REMOTE_ADDRESS);
|
||||
assertThat(response.getStatus()).isEqualTo(HttpStatus.FOUND.value());
|
||||
assertThat(response.getRedirectedUrl()).isEqualTo("https://example.com?code=code&state=state");
|
||||
}
|
||||
@@ -578,6 +622,7 @@ public class OAuth2AuthorizationEndpointFilterTests {
|
||||
String requestUri = DEFAULT_AUTHORIZATION_ENDPOINT_URI;
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
|
||||
request.setServletPath(requestUri);
|
||||
request.setRemoteAddr(REMOTE_ADDRESS);
|
||||
|
||||
request.addParameter(OAuth2ParameterNames.RESPONSE_TYPE, OAuth2AuthorizationResponseType.CODE.getValue());
|
||||
request.addParameter(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId());
|
||||
@@ -593,6 +638,7 @@ public class OAuth2AuthorizationEndpointFilterTests {
|
||||
String requestUri = DEFAULT_AUTHORIZATION_ENDPOINT_URI;
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("POST", requestUri);
|
||||
request.setServletPath(requestUri);
|
||||
request.setRemoteAddr(REMOTE_ADDRESS);
|
||||
|
||||
request.addParameter(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId());
|
||||
registeredClient.getScopes().forEach((scope) -> request.addParameter(OAuth2ParameterNames.SCOPE, scope));
|
||||
|
||||
@@ -6,7 +6,7 @@ plugins {
|
||||
|
||||
group = project.rootProject.group
|
||||
version = project.rootProject.version
|
||||
sourceCompatibility = "11"
|
||||
sourceCompatibility = "1.8"
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
|
||||
@@ -6,7 +6,7 @@ plugins {
|
||||
|
||||
group = project.rootProject.group
|
||||
version = project.rootProject.version
|
||||
sourceCompatibility = "11"
|
||||
sourceCompatibility = "1.8"
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
|
||||
@@ -6,7 +6,7 @@ plugins {
|
||||
|
||||
group = project.rootProject.group
|
||||
version = project.rootProject.version
|
||||
sourceCompatibility = "11"
|
||||
sourceCompatibility = "1.8"
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
|
||||
@@ -6,7 +6,7 @@ plugins {
|
||||
|
||||
group = project.rootProject.group
|
||||
version = project.rootProject.version
|
||||
sourceCompatibility = "11"
|
||||
sourceCompatibility = "1.8"
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
|
||||
@@ -6,7 +6,7 @@ plugins {
|
||||
|
||||
group = project.rootProject.group
|
||||
version = project.rootProject.version
|
||||
sourceCompatibility = "11"
|
||||
sourceCompatibility = "1.8"
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
|
||||
Reference in New Issue
Block a user