Explorar o código

Update oauth login samples

Closes gh-29
Steve Riesenberg %!s(int64=4) %!d(string=hai) anos
pai
achega
52cc331d9c

+ 99 - 12
reactive/webflux/java/oauth2/login/README.adoc

@@ -1,18 +1,105 @@
-NOTE: Spring Security Reactive OAuth only supports authentication using a user info endpoint.
-Support for JWT validation will be added in https://github.com/spring-projects/spring-security/issues/5330[gh-5330].
-
 = OAuth 2.0 Login Sample
 = OAuth 2.0 Login Sample
 
 
 This guide provides instructions on setting up the sample application with OAuth 2.0 Login using an OAuth 2.0 Provider or OpenID Connect 1.0 Provider.
 This guide provides instructions on setting up the sample application with OAuth 2.0 Login using an OAuth 2.0 Provider or OpenID Connect 1.0 Provider.
-The sample application uses Spring Boot 2.0.0.M6 and the `spring-security-oauth2-client` module which is new in Spring Security 5.0.
+The sample application uses Spring Boot 2.5 and the `spring-security-oauth2-client` module which is new in Spring Security 5.0.
 
 
 The following sections provide detailed steps for setting up OAuth 2.0 Login for these Providers:
 The following sections provide detailed steps for setting up OAuth 2.0 Login for these Providers:
 
 
+* <<spring-login, Spring Authorization Server>>
 * <<google-login, Google>>
 * <<google-login, Google>>
 * <<github-login, GitHub>>
 * <<github-login, GitHub>>
 * <<facebook-login, Facebook>>
 * <<facebook-login, Facebook>>
 * <<okta-login, Okta>>
 * <<okta-login, Okta>>
 
 
+[[spring-login]]
+== Login with Spring Authorization Server
+
+This section shows how to configure the sample application using Spring Authorization Server as the Authentication Provider and covers the following topics:
+
+* <<spring-initial-setup,Initial setup>>
+* <<spring-redirect-uri,Setting the redirect URI>>
+* <<spring-application-config,Configure application.yml>>
+* <<spring-boot-application,Boot up the application>>
+
+[[spring-initial-setup]]
+=== Initial setup
+
+The sample application is pre-configured to work out of the box with Spring Authorization Server, which runs locally on port `9000`. See the https://github.com/spring-projects/spring-security-samples/tree/main/servlet/spring-boot/java/oauth2/authorization-server[authorization-server sample] to run the authorization server used in this section.
+
+NOTE: https://github.com/spring-projects-external/spring-authorization-server[Spring Authorization Server] supports the https://openid.net/connect/[OpenID Connect 1.0] specification.
+
+[[spring-redirect-uri]]
+=== Setting the redirect URI
+
+The redirect URI is the path in the application that the end-user's user-agent is redirected back to after they have authenticated with Spring Authorization Server
+and have granted access to the OAuth Client on the Consent page.
+
+The default redirect URI is `http://127.0.0.1:8080/login/oauth2/code/login-client`. No special setup is required to use the sample locally.
+
+TIP: The default redirect URI template is `{baseUrl}/login/oauth2/code/{registrationId}`.
+The *_registrationId_* is a unique identifier for the `ClientRegistration`.
+
+IMPORTANT: If the application is running behind a proxy server, it is recommended to check https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#appendix-proxy-server[Proxy Server Configuration] to ensure the application is correctly configured.
+Also, see the supported https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#oauth2Client-auth-code-redirect-uri[`URI` template variables] for `redirect-uri`.
+
+[[spring-application-config]]
+=== Configure application.yml
+
+If you wish to customize the OAuth Client to work with a non-local deployment of Spring Authorization Server, you need to configure the application to use the OAuth Client for the _authentication flow_. To do so:
+
+. Go to `application.yml` and set the following configuration:
++
+[source,yaml]
+----
+spring:
+  security:
+    oauth2:
+      client:
+        registration:	<1>
+          login-client:		<2>
+            provider: spring	<3>
+            client-id: login-client
+            client-secret: openid-connect
+            client-authentication-method: client_secret_basic
+            authorization-grant-type: authorization_code
+            redirect-uri: http://127.0.0.1:8080/login/oauth2/code/login-client
+            scope: openid,profile	<4>
+            client-name: Spring
+        provider:<5>
+          spring:
+            authorization-uri: http://localhost:9000/oauth2/authorize
+            token-uri: http://localhost:9000/oauth2/token
+            jwk-set-uri: http://localhost:9000/oauth2/jwks
+            issuer-uri: http://localhost:9000
+----
++
+.OAuth Client properties
+====
+<1> `spring.security.oauth2.client.registration` is the base property prefix for OAuth Client properties.
+<2> Following the base property prefix is the ID for the `ClientRegistration`, such as login-client.
+<3> The `provider` property specifies which provider configuration is used by this `ClientRegistration`.
+<4> The `openid` scope is required by Spring Authorization Server to perform https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth[authentication using OpenID Connect 1.0].
+<5> `spring.security.oauth2.client.provider` is the base property prefix for OAuth Provider properties.
+====
+
+. Replace the values in the `client-id` and `client-secret` property with the OAuth 2.0 credentials for your Spring Authorization Server. As well, replace `http://localhost:9000` in `authorization-uri`, `token-uri` and `jwk-set-uri` with the actual domain of your authorization server.
+
+[[spring-boot-application]]
+=== Boot up the application
+
+Launch the Spring Boot 2.0 sample and go to `http://127.0.0.1:8080`.
+You are then redirected to the default _auto-generated_ login page, which displays a link for Spring.
+
+Click on the Spring link, and you are then redirected to the Spring Authorization Server for authentication.
+
+After authenticating with your credentials (`user` and `password` by default), the next page presented to you is the Consent screen.
+The Consent screen asks you to either allow or deny access to the OAuth Client. Select "profile" and
+click *Submit Consent* to authorize the OAuth Client to access your basic profile information.
+
+At this point, the OAuth Client retrieves your basic profile information via the https://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken[ID Token] and establishes an authenticated session.
+
+NOTE: Spring Authorization Server does not currently support the https://openid.net/specs/openid-connect-core-1_0.html#UserInfo[UserInfo Endpoint], which is optional in OpenID Connect 1.0. See https://github.com/spring-projects-experimental/spring-authorization-server/issues/176[#176] fo more information.
+
 [[google-login]]
 [[google-login]]
 == Login with Google
 == Login with Google
 
 
@@ -41,7 +128,7 @@ After completing the "Obtain OAuth 2.0 credentials" instructions, you should hav
 The redirect URI is the path in the application that the end-user's user-agent is redirected back to after they have authenticated with Google
 The redirect URI is the path in the application that the end-user's user-agent is redirected back to after they have authenticated with Google
 and have granted access to the OAuth Client _(created in the previous step)_ on the Consent page.
 and have granted access to the OAuth Client _(created in the previous step)_ on the Consent page.
 
 
-In the "Set a redirect URI" sub-section, ensure that the *Authorized redirect URIs* field is set to `http://localhost:8080/login/oauth2/code/google`.
+In the "Set a redirect URI" sub-section, ensure that the *Authorized redirect URIs* field is set to `http://127.0.0.1:8080/login/oauth2/code/google`.
 
 
 TIP: The default redirect URI template is `{baseUrl}/login/oauth2/code/{registrationId}`.
 TIP: The default redirect URI template is `{baseUrl}/login/oauth2/code/{registrationId}`.
  The *_registrationId_* is a unique identifier for the `ClientRegistration`.
  The *_registrationId_* is a unique identifier for the `ClientRegistration`.
@@ -79,7 +166,7 @@ spring:
 [[google-boot-application]]
 [[google-boot-application]]
 === Boot up the application
 === Boot up the application
 
 
-Launch the Spring Boot 2.0 sample and go to `http://localhost:8080`.
+Launch the Spring Boot 2.0 sample and go to `http://127.0.0.1:8080`.
 You are then redirected to the default _auto-generated_ login page, which displays a link for Google.
 You are then redirected to the default _auto-generated_ login page, which displays a link for Google.
 
 
 Click on the Google link, and you are then redirected to Google for authentication.
 Click on the Google link, and you are then redirected to Google for authentication.
@@ -105,7 +192,7 @@ This section shows how to configure the sample application using GitHub as the A
 
 
 To use GitHub's OAuth 2.0 authentication system for login, you must https://github.com/settings/applications/new[Register a new OAuth application].
 To use GitHub's OAuth 2.0 authentication system for login, you must https://github.com/settings/applications/new[Register a new OAuth application].
 
 
-When registering the OAuth application, ensure the *Authorization callback URL* is set to `http://localhost:8080/login/oauth2/code/github`.
+When registering the OAuth application, ensure the *Authorization callback URL* is set to `http://127.0.0.1:8080/login/oauth2/code/github`.
 
 
 The Authorization callback URL (redirect URI) is the path in the application that the end-user's user-agent is redirected back to after they have authenticated with GitHub
 The Authorization callback URL (redirect URI) is the path in the application that the end-user's user-agent is redirected back to after they have authenticated with GitHub
 and have granted access to the OAuth application on the _Authorize application_ page.
 and have granted access to the OAuth application on the _Authorize application_ page.
@@ -146,7 +233,7 @@ spring:
 [[github-boot-application]]
 [[github-boot-application]]
 === Boot up the application
 === Boot up the application
 
 
-Launch the Spring Boot 2.0 sample and go to `http://localhost:8080`.
+Launch the Spring Boot 2.0 sample and go to `http://127.0.0.1:8080`.
 You are then redirected to the default _auto-generated_ login page, which displays a link for GitHub.
 You are then redirected to the default _auto-generated_ login page, which displays a link for GitHub.
 
 
 Click on the GitHub link, and you are then redirected to GitHub for authentication.
 Click on the GitHub link, and you are then redirected to GitHub for authentication.
@@ -183,7 +270,7 @@ NOTE: The selection for the _Category_ field is not relevant but it's a required
 The next page presented is "Product Setup". Click the "Get Started" button for the *Facebook Login* product.
 The next page presented is "Product Setup". Click the "Get Started" button for the *Facebook Login* product.
 In the left sidebar, under _Products -> Facebook Login_, select _Settings_.
 In the left sidebar, under _Products -> Facebook Login_, select _Settings_.
 
 
-For the field *Valid OAuth redirect URIs*, enter `http://localhost:8080/login/oauth2/code/facebook` then click _Save Changes_.
+For the field *Valid OAuth redirect URIs*, enter `http://127.0.0.1:8080/login/oauth2/code/facebook` then click _Save Changes_.
 
 
 The OAuth redirect URI is the path in the application that the end-user's user-agent is redirected back to after they have authenticated with Facebook
 The OAuth redirect URI is the path in the application that the end-user's user-agent is redirected back to after they have authenticated with Facebook
 and have granted access to the application on the _Authorize application_ page.
 and have granted access to the application on the _Authorize application_ page.
@@ -224,7 +311,7 @@ spring:
 [[facebook-boot-application]]
 [[facebook-boot-application]]
 === Boot up the application
 === Boot up the application
 
 
-Launch the Spring Boot 2.0 sample and go to `http://localhost:8080`.
+Launch the Spring Boot 2.0 sample and go to `http://127.0.0.1:8080`.
 You are then redirected to the default _auto-generated_ login page, which displays a link for Facebook.
 You are then redirected to the default _auto-generated_ login page, which displays a link for Facebook.
 
 
 Click on the Facebook link, and you are then redirected to Facebook for authentication.
 Click on the Facebook link, and you are then redirected to Facebook for authentication.
@@ -259,7 +346,7 @@ From the "Add Application" page, select the "Create New App" button and enter th
 
 
 Select the _Create_ button.
 Select the _Create_ button.
 On the "General Settings" page, enter the Application Name (for example, "Spring Security Okta Login") and then select the _Next_ button.
 On the "General Settings" page, enter the Application Name (for example, "Spring Security Okta Login") and then select the _Next_ button.
-On the "Configure OpenID Connect" page, enter `http://localhost:8080/login/oauth2/code/okta` for the field *Redirect URIs* and then select _Finish_.
+On the "Configure OpenID Connect" page, enter `http://127.0.0.1:8080/login/oauth2/code/okta` for the field *Redirect URIs* and then select _Finish_.
 
 
 The redirect URI is the path in the application that the end-user's user-agent is redirected back to after they have authenticated with Okta
 The redirect URI is the path in the application that the end-user's user-agent is redirected back to after they have authenticated with Okta
 and have granted access to the application on the _Authorize application_ page.
 and have granted access to the application on the _Authorize application_ page.
@@ -315,7 +402,7 @@ As well, replace `https://your-subdomain.oktapreview.com` in `authorization-uri`
 [[okta-boot-application]]
 [[okta-boot-application]]
 === Boot up the application
 === Boot up the application
 
 
-Launch the Spring Boot 2.0 sample and go to `http://localhost:8080`.
+Launch the Spring Boot 2.0 sample and go to `http://127.0.0.1:8080`.
 You are then redirected to the default _auto-generated_ login page, which displays a link for Okta.
 You are then redirected to the default _auto-generated_ login page, which displays a link for Okta.
 
 
 Click on the Okta link, and you are then redirected to Okta for authentication.
 Click on the Okta link, and you are then redirected to Okta for authentication.

+ 65 - 0
reactive/webflux/java/oauth2/login/src/main/java/example/LoopbackIpRedirectWebFilter.java

@@ -0,0 +1,65 @@
+/*
+ * Copyright 2021 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.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package example;
+
+import reactor.core.publisher.Mono;
+
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
+import org.springframework.http.HttpStatus;
+import org.springframework.stereotype.Component;
+import org.springframework.web.server.ServerWebExchange;
+import org.springframework.web.server.WebFilter;
+import org.springframework.web.server.WebFilterChain;
+import org.springframework.web.util.UriComponents;
+import org.springframework.web.util.UriComponentsBuilder;
+
+/**
+ * This filter ensures that the loopback IP <code>127.0.0.1</code> is used to access the
+ * application so that the sample works correctly, due to the fact that redirect URIs with
+ * "localhost" are rejected by the Spring Authorization Server, because the OAuth 2.1
+ * draft specification states:
+ *
+ * <pre>
+ *     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.
+ * </pre>
+ *
+ * @author Steve Riesenberg
+ * @see <a href=
+ * "https://tools.ietf.org/html/draft-ietf-oauth-v2-1-01#section-9.7.1">Loopback Redirect
+ * Considerations in Native Apps</a>
+ */
+@Component
+@Order(Ordered.HIGHEST_PRECEDENCE)
+public class LoopbackIpRedirectWebFilter implements WebFilter {
+
+	@Override
+	public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
+		String host = exchange.getRequest().getURI().getHost();
+		if (host != null && host.equals("localhost")) {
+			UriComponents uri = UriComponentsBuilder.fromHttpRequest(exchange.getRequest()).host("127.0.0.1").build();
+			exchange.getResponse().setStatusCode(HttpStatus.PERMANENT_REDIRECT);
+			exchange.getResponse().getHeaders().setLocation(uri.toUri());
+			return Mono.empty();
+		}
+		return chain.filter(exchange);
+	}
+
+}

+ 13 - 0
reactive/webflux/java/oauth2/login/src/main/resources/application.yml

@@ -15,6 +15,15 @@ spring:
     oauth2:
     oauth2:
       client:
       client:
         registration:
         registration:
+          login-client:
+            provider: spring
+            client-id: login-client
+            client-secret: openid-connect
+            client-authentication-method: client_secret_basic
+            authorization-grant-type: authorization_code
+            redirect-uri: http://127.0.0.1:8080/login/oauth2/code/login-client
+            scope: openid,profile
+            client-name: Spring
           google:
           google:
             client-id: your-app-client-id
             client-id: your-app-client-id
             client-secret: your-app-client-secret
             client-secret: your-app-client-secret
@@ -28,6 +37,10 @@ spring:
             client-id: your-app-client-id
             client-id: your-app-client-id
             client-secret: your-app-client-secret
             client-secret: your-app-client-secret
         provider:
         provider:
+          spring:
+            authorization-uri: http://localhost:9000/oauth2/authorize
+            token-uri: http://localhost:9000/oauth2/token
+            jwk-set-uri: http://localhost:9000/oauth2/jwks
           okta:
           okta:
             authorization-uri: https://your-subdomain.oktapreview.com/oauth2/v1/authorize
             authorization-uri: https://your-subdomain.oktapreview.com/oauth2/v1/authorize
             token-uri: https://your-subdomain.oktapreview.com/oauth2/v1/token
             token-uri: https://your-subdomain.oktapreview.com/oauth2/v1/token

+ 6 - 46
servlet/spring-boot/java/oauth2/authorization-server/README.adoc

@@ -1,6 +1,6 @@
 = OAuth 2.0 Authorization Server Sample
 = OAuth 2.0 Authorization Server Sample
 
 
-This sample demonstrates Authorization Server with the `client_credentials` grant type. This authorization server is configured to generate JWT tokens signed with the `RS256` algorithm.
+This sample demonstrates Authorization Server with the `authorization_code` and `client_credentials` grant types, as well as OpenID Connect 1.0. This authorization server is configured to generate JWT tokens signed with the `RS256` algorithm.
 
 
 * <<running-the-tests, Running the tests>>
 * <<running-the-tests, Running the tests>>
 * <<running-the-app, Running the app>>
 * <<running-the-app, Running the app>>
@@ -19,29 +19,11 @@ Or import the project into your IDE and run `OAuth2AuthorizationServerApplicatio
 
 
 === What is it doing?
 === What is it doing?
 
 
-The tests are making requests to the token endpoint with the `client_credentials` grant type using the `client_secret_basic` authentication method, and subsequently verifying them using the token introspection endpoint.
+The tests are making requests to the token endpoint with the `client_credentials` grant type using the `client_secret_basic` authentication method, and subsequently verifying the access token from the response using the token introspection endpoint.
 
 
-The introspection endpoint response is used to verify the token (decode the JWT in this case), returning the payload including the requested scope:
+The introspection endpoint response is used to verify the token (decode the JWT in this case), returning the payload including the requested scope.
 
 
-```json
-{
-    "active": true,
-    "aud": [
-        "messaging-client"
-    ],
-    "client_id": "messaging-client",
-    "exp": 1627070941,
-    "iat": 1627070641,
-    "iss": "http://localhost:9000",
-    "jti": "987599e3-1048-4fe8-89df-ad113aef2d6c",
-    "nbf": 1627070641,
-    "scope": "message:read",
-    "sub": "messaging-client",
-    "token_type": "Bearer"
-}
-```
-
-Note that Spring Security does not require the token introspection endpoint when configured to use the Bearer scheme with JWTs, this is simply used for demonstration purposes.
+NOTE: Spring Security does not require the token introspection endpoint when configured to use the Bearer scheme with JWTs, this is simply used for demonstration purposes.
 
 
 [[running-the-app]]
 [[running-the-app]]
 == Running the app
 == Running the app
@@ -106,31 +88,9 @@ Which will return something like the following:
 [[testing-with-a-resource-server]]
 [[testing-with-a-resource-server]]
 == Testing with a resource server
 == Testing with a resource server
 
 
-This sample can be used in conjunction with a resource server, such as the https://github.com/spring-projects/spring-security-samples/tree/main/servlet/spring-boot/java/oauth2/resource-server/hello-security[resource-server sample] in this project.
-
-To change the sample to point to this authorization server, simply find this property in that project's `application.yml`:
-
-```yaml
-spring:
-  security:
-    oauth2:
-      resourceserver:
-        jwt:
-          jwk-set-uri: ${mockwebserver.url}/.well-known/jwks.json
-```
-
-And change the property to:
-
-```yaml
-spring:
-  security:
-    oauth2:
-      resourceserver:
-        jwt:
-          jwk-set-uri: http://localhost:9000/oauth2/jwks
-```
+This sample can be used in conjunction with a resource server, such as the https://github.com/spring-projects/spring-security-samples/tree/main/servlet/spring-boot/java/oauth2/resource-server/hello-security[resource-server sample] in this project which is pre-configured to work with this authorization server sample out of the box.
 
 
-And then you can run that app similarly to the authorization server:
+You can run that app similarly to the authorization server:
 
 
 ```bash
 ```bash
 ./gradlew bootRun
 ./gradlew bootRun

+ 11 - 0
servlet/spring-boot/java/oauth2/authorization-server/src/integTest/java/example/OAuth2AuthorizationServerApplicationITests.java

@@ -106,6 +106,17 @@ public class OAuth2AuthorizationServerApplicationITests {
 		// @formatter:on
 		// @formatter:on
 	}
 	}
 
 
+	@Test
+	void performTokenRequestWhenGrantTypeNotRegisteredThenBadRequest() throws Exception {
+		// @formatter:off
+		this.mockMvc.perform(post("/oauth2/token")
+				.param("grant_type", "client_credentials")
+				.with(basicAuth("login-client", "openid-connect")))
+				.andExpect(status().isBadRequest())
+				.andExpect(jsonPath("$.error").value("unauthorized_client"));
+		// @formatter:on
+	}
+
 	@Test
 	@Test
 	void performIntrospectionRequestWhenValidTokenThenOk() throws Exception {
 	void performIntrospectionRequestWhenValidTokenThenOk() throws Exception {
 		// @formatter:off
 		// @formatter:off

+ 0 - 48
servlet/spring-boot/java/oauth2/authorization-server/src/main/java/example/Jwks.java

@@ -1,48 +0,0 @@
-/*
- * Copyright 2021 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.
- * You may obtain a copy of the License at
- *
- *      https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package example;
-
-import java.security.KeyPair;
-import java.security.interfaces.RSAPrivateKey;
-import java.security.interfaces.RSAPublicKey;
-import java.util.UUID;
-
-import com.nimbusds.jose.jwk.RSAKey;
-
-/**
- * Utils for generating JWKs.
- *
- * @author Joe Grandja
- */
-final class Jwks {
-
-	private Jwks() {
-	}
-
-	static RSAKey generateRsa() {
-		KeyPair keyPair = KeyGeneratorUtils.generateRsaKey();
-		RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
-		RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
-		// @formatter:off
-		return new RSAKey.Builder(publicKey)
-				.privateKey(privateKey)
-				.keyID(UUID.randomUUID().toString())
-				.build();
-		// @formatter:on
-	}
-
-}

+ 0 - 44
servlet/spring-boot/java/oauth2/authorization-server/src/main/java/example/KeyGeneratorUtils.java

@@ -1,44 +0,0 @@
-/*
- * Copyright 2021 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.
- * You may obtain a copy of the License at
- *
- *      https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package example;
-
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-
-/**
- * Utils for generating keys.
- *
- * @author Joe Grandja
- */
-final class KeyGeneratorUtils {
-
-	private KeyGeneratorUtils() {
-	}
-
-	static KeyPair generateRsaKey() {
-		KeyPair keyPair;
-		try {
-			KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
-			keyPairGenerator.initialize(2048);
-			keyPair = keyPairGenerator.generateKeyPair();
-		}
-		catch (Exception ex) {
-			throw new IllegalStateException(ex);
-		}
-		return keyPair;
-	}
-
-}

+ 75 - 31
servlet/spring-boot/java/oauth2/authorization-server/src/main/java/example/OAuth2AuthorizationServerSecurityConfiguration.java

@@ -16,28 +16,32 @@
 
 
 package example;
 package example;
 
 
-import java.util.HashSet;
-import java.util.Set;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
 import java.util.UUID;
 import java.util.UUID;
 
 
-import com.nimbusds.jose.JWSAlgorithm;
 import com.nimbusds.jose.jwk.JWKSet;
 import com.nimbusds.jose.jwk.JWKSet;
 import com.nimbusds.jose.jwk.RSAKey;
 import com.nimbusds.jose.jwk.RSAKey;
 import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
 import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
 import com.nimbusds.jose.jwk.source.JWKSource;
 import com.nimbusds.jose.jwk.source.JWKSource;
-import com.nimbusds.jose.proc.JWSKeySelector;
-import com.nimbusds.jose.proc.JWSVerificationKeySelector;
 import com.nimbusds.jose.proc.SecurityContext;
 import com.nimbusds.jose.proc.SecurityContext;
-import com.nimbusds.jwt.proc.ConfigurableJWTProcessor;
-import com.nimbusds.jwt.proc.DefaultJWTProcessor;
 
 
+import org.springframework.beans.factory.config.BeanDefinition;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Role;
+import org.springframework.core.annotation.Order;
+import org.springframework.security.config.Customizer;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
-import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
 import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
 import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
-import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
 import org.springframework.security.oauth2.core.AuthorizationGrantType;
 import org.springframework.security.oauth2.core.AuthorizationGrantType;
 import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
 import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
+import org.springframework.security.oauth2.core.oidc.OidcScopes;
 import org.springframework.security.oauth2.jwt.JwtDecoder;
 import org.springframework.security.oauth2.jwt.JwtDecoder;
 import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
 import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
 import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository;
 import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository;
@@ -45,6 +49,7 @@ import org.springframework.security.oauth2.server.authorization.client.Registere
 import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
 import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
 import org.springframework.security.oauth2.server.authorization.config.ClientSettings;
 import org.springframework.security.oauth2.server.authorization.config.ClientSettings;
 import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
 import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
+import org.springframework.security.provisioning.InMemoryUserDetailsManager;
 import org.springframework.security.web.SecurityFilterChain;
 import org.springframework.security.web.SecurityFilterChain;
 
 
 /**
 /**
@@ -52,19 +57,23 @@ import org.springframework.security.web.SecurityFilterChain;
  *
  *
  * @author Steve Riesenberg
  * @author Steve Riesenberg
  */
  */
-@EnableWebSecurity
+@Configuration
 public class OAuth2AuthorizationServerSecurityConfiguration {
 public class OAuth2AuthorizationServerSecurityConfiguration {
 
 
 	@Bean
 	@Bean
+	@Order(1)
 	public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
 	public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
 		OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
 		OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
+		return http.formLogin(Customizer.withDefaults()).build();
+	}
 
 
+	@Bean
+	@Order(2)
+	public SecurityFilterChain standardSecurityFilterChain(HttpSecurity http) throws Exception {
 		// @formatter:off
 		// @formatter:off
 		http
 		http
-			.sessionManagement((sessionManagement) ->
-				sessionManagement
-					.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
-			);
+			.authorizeRequests((requests) -> requests.anyRequest().authenticated())
+			.formLogin(Customizer.withDefaults());
 		// @formatter:on
 		// @formatter:on
 
 
 		return http.build();
 		return http.build();
@@ -73,6 +82,18 @@ public class OAuth2AuthorizationServerSecurityConfiguration {
 	@Bean
 	@Bean
 	public RegisteredClientRepository registeredClientRepository() {
 	public RegisteredClientRepository registeredClientRepository() {
 		// @formatter:off
 		// @formatter:off
+		RegisteredClient loginClient = RegisteredClient.withId(UUID.randomUUID().toString())
+				.clientId("login-client")
+				.clientSecret("{noop}openid-connect")
+				.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
+				.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
+				.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
+				.redirectUri("http://127.0.0.1:8080/login/oauth2/code/login-client")
+				.redirectUri("http://127.0.0.1:8080/authorized")
+				.scope(OidcScopes.OPENID)
+				.scope(OidcScopes.PROFILE)
+				.clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
+				.build();
 		RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
 		RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
 				.clientId("messaging-client")
 				.clientId("messaging-client")
 				.clientSecret("{noop}secret")
 				.clientSecret("{noop}secret")
@@ -80,34 +101,29 @@ public class OAuth2AuthorizationServerSecurityConfiguration {
 				.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
 				.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
 				.scope("message:read")
 				.scope("message:read")
 				.scope("message:write")
 				.scope("message:write")
-				.clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
 				.build();
 				.build();
 		// @formatter:on
 		// @formatter:on
 
 
-		return new InMemoryRegisteredClientRepository(registeredClient);
+		return new InMemoryRegisteredClientRepository(loginClient, registeredClient);
 	}
 	}
 
 
 	@Bean
 	@Bean
-	public JWKSource<SecurityContext> jwkSource() {
-		RSAKey rsaKey = Jwks.generateRsa();
+	public JWKSource<SecurityContext> jwkSource(KeyPair keyPair) {
+		RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
+		RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
+		// @formatter:off
+		RSAKey rsaKey = new RSAKey.Builder(publicKey)
+				.privateKey(privateKey)
+				.keyID(UUID.randomUUID().toString())
+				.build();
+		// @formatter:on
 		JWKSet jwkSet = new JWKSet(rsaKey);
 		JWKSet jwkSet = new JWKSet(rsaKey);
 		return new ImmutableJWKSet<>(jwkSet);
 		return new ImmutableJWKSet<>(jwkSet);
 	}
 	}
 
 
 	@Bean
 	@Bean
-	public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
-		Set<JWSAlgorithm> jwsAlgs = new HashSet<>();
-		jwsAlgs.addAll(JWSAlgorithm.Family.RSA);
-		jwsAlgs.addAll(JWSAlgorithm.Family.EC);
-		jwsAlgs.addAll(JWSAlgorithm.Family.HMAC_SHA);
-		ConfigurableJWTProcessor<SecurityContext> jwtProcessor = new DefaultJWTProcessor<>();
-		JWSKeySelector<SecurityContext> jwsKeySelector = new JWSVerificationKeySelector<>(jwsAlgs, jwkSource);
-		jwtProcessor.setJWSKeySelector(jwsKeySelector);
-		// Override the default Nimbus claims set verifier as NimbusJwtDecoder handles it
-		// instead
-		jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> {
-		});
-		return new NimbusJwtDecoder(jwtProcessor);
+	public JwtDecoder jwtDecoder(KeyPair keyPair) {
+		return NimbusJwtDecoder.withPublicKey((RSAPublicKey) keyPair.getPublic()).build();
 	}
 	}
 
 
 	@Bean
 	@Bean
@@ -115,4 +131,32 @@ public class OAuth2AuthorizationServerSecurityConfiguration {
 		return ProviderSettings.builder().issuer("http://localhost:9000").build();
 		return ProviderSettings.builder().issuer("http://localhost:9000").build();
 	}
 	}
 
 
+	@Bean
+	public UserDetailsService userDetailsService() {
+		// @formatter:off
+		UserDetails userDetails = User.withDefaultPasswordEncoder()
+				.username("user")
+				.password("password")
+				.roles("USER")
+				.build();
+		// @formatter:on
+
+		return new InMemoryUserDetailsManager(userDetails);
+	}
+
+	@Bean
+	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
+	KeyPair generateRsaKey() {
+		KeyPair keyPair;
+		try {
+			KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
+			keyPairGenerator.initialize(2048);
+			keyPair = keyPairGenerator.generateKeyPair();
+		}
+		catch (Exception ex) {
+			throw new IllegalStateException(ex);
+		}
+		return keyPair;
+	}
+
 }
 }

+ 98 - 9
servlet/spring-boot/java/oauth2/login/README.adoc

@@ -1,15 +1,104 @@
 = OAuth 2.0 Login Sample
 = OAuth 2.0 Login Sample
 
 
 This guide provides instructions on setting up the sample application with OAuth 2.0 Login using an OAuth 2.0 Provider or OpenID Connect 1.0 Provider.
 This guide provides instructions on setting up the sample application with OAuth 2.0 Login using an OAuth 2.0 Provider or OpenID Connect 1.0 Provider.
-The sample application uses Spring Boot 2.0.0.M6 and the `spring-security-oauth2-client` module which is new in Spring Security 5.0.
+The sample application uses Spring Boot 2.5 and the `spring-security-oauth2-client` module which is new in Spring Security 5.0.
 
 
 The following sections provide detailed steps for setting up OAuth 2.0 Login for these Providers:
 The following sections provide detailed steps for setting up OAuth 2.0 Login for these Providers:
 
 
+* <<spring-login, Spring Authorization Server>>
 * <<google-login, Google>>
 * <<google-login, Google>>
 * <<github-login, GitHub>>
 * <<github-login, GitHub>>
 * <<facebook-login, Facebook>>
 * <<facebook-login, Facebook>>
 * <<okta-login, Okta>>
 * <<okta-login, Okta>>
 
 
+[[spring-login]]
+== Login with Spring Authorization Server
+
+This section shows how to configure the sample application using Spring Authorization Server as the Authentication Provider and covers the following topics:
+
+* <<spring-initial-setup,Initial setup>>
+* <<spring-redirect-uri,Setting the redirect URI>>
+* <<spring-application-config,Configure application.yml>>
+* <<spring-boot-application,Boot up the application>>
+
+[[spring-initial-setup]]
+=== Initial setup
+
+The sample application is pre-configured to work out of the box with Spring Authorization Server, which runs locally on port `9000`. See the https://github.com/spring-projects/spring-security-samples/tree/main/servlet/spring-boot/java/oauth2/authorization-server[authorization-server sample] to run the authorization server used in this section.
+
+NOTE: https://github.com/spring-projects-external/spring-authorization-server[Spring Authorization Server] supports the https://openid.net/connect/[OpenID Connect 1.0] specification.
+
+[[spring-redirect-uri]]
+=== Setting the redirect URI
+
+The redirect URI is the path in the application that the end-user's user-agent is redirected back to after they have authenticated with Spring Authorization Server
+and have granted access to the OAuth Client on the Consent page.
+
+The default redirect URI is `http://127.0.0.1:8080/login/oauth2/code/login-client`. No special setup is required to use the sample locally.
+
+TIP: The default redirect URI template is `{baseUrl}/login/oauth2/code/{registrationId}`.
+The *_registrationId_* is a unique identifier for the `ClientRegistration`.
+
+IMPORTANT: If the application is running behind a proxy server, it is recommended to check https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#appendix-proxy-server[Proxy Server Configuration] to ensure the application is correctly configured.
+Also, see the supported https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#oauth2Client-auth-code-redirect-uri[`URI` template variables] for `redirect-uri`.
+
+[[spring-application-config]]
+=== Configure application.yml
+
+If you wish to customize the OAuth Client to work with a non-local deployment of Spring Authorization Server, you need to configure the application to use the OAuth Client for the _authentication flow_. To do so:
+
+. Go to `application.yml` and set the following configuration:
++
+[source,yaml]
+----
+spring:
+  security:
+    oauth2:
+      client:
+        registration:	<1>
+          login-client:		<2>
+            provider: spring	<3>
+            client-id: login-client
+            client-secret: openid-connect
+            client-authentication-method: client_secret_basic
+            authorization-grant-type: authorization_code
+            redirect-uri: http://127.0.0.1:8080/login/oauth2/code/login-client
+            scope: openid,profile	<4>
+            client-name: Spring
+        provider:	<5>
+          spring:
+            authorization-uri: http://localhost:9000/oauth2/authorize
+            token-uri: http://localhost:9000/oauth2/token
+            jwk-set-uri: http://localhost:9000/oauth2/jwks
+----
++
+.OAuth Client properties
+====
+<1> `spring.security.oauth2.client.registration` is the base property prefix for OAuth Client properties.
+<2> Following the base property prefix is the ID for the `ClientRegistration`, such as login-client.
+<3> The `provider` property specifies which provider configuration is used by this `ClientRegistration`.
+<4> The `openid` scope is required by Spring Authorization Server to perform https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth[authentication using OpenID Connect 1.0].
+<5> `spring.security.oauth2.client.provider` is the base property prefix for OAuth Provider properties.
+====
+
+. Replace the values in the `client-id` and `client-secret` property with the OAuth 2.0 credentials for your Spring Authorization Server. As well, replace `http://localhost:9000` in `authorization-uri`, `token-uri` and `jwk-set-uri` with the actual domain of your authorization server.
+
+[[spring-boot-application]]
+=== Boot up the application
+
+Launch the Spring Boot 2.0 sample and go to `http://127.0.0.1:8080`.
+You are then redirected to the default _auto-generated_ login page, which displays a link for Spring.
+
+Click on the Spring link, and you are then redirected to the Spring Authorization Server for authentication.
+
+After authenticating with your credentials (`user` and `password` by default), the next page presented to you is the Consent screen.
+The Consent screen asks you to either allow or deny access to the OAuth Client. Select "profile" and
+click *Submit Consent* to authorize the OAuth Client to access your basic profile information.
+
+At this point, the OAuth Client retrieves your basic profile information via the https://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken[ID Token] and establishes an authenticated session.
+
+NOTE: Spring Authorization Server does not currently support the https://openid.net/specs/openid-connect-core-1_0.html#UserInfo[UserInfo Endpoint], which is optional in OpenID Connect 1.0. See https://github.com/spring-projects-experimental/spring-authorization-server/issues/176[#176] fo more information.
+
 [[google-login]]
 [[google-login]]
 == Login with Google
 == Login with Google
 
 
@@ -38,7 +127,7 @@ After completing the "Obtain OAuth 2.0 credentials" instructions, you should hav
 The redirect URI is the path in the application that the end-user's user-agent is redirected back to after they have authenticated with Google
 The redirect URI is the path in the application that the end-user's user-agent is redirected back to after they have authenticated with Google
 and have granted access to the OAuth Client _(created in the previous step)_ on the Consent page.
 and have granted access to the OAuth Client _(created in the previous step)_ on the Consent page.
 
 
-In the "Set a redirect URI" sub-section, ensure that the *Authorized redirect URIs* field is set to `http://localhost:8080/login/oauth2/code/google`.
+In the "Set a redirect URI" sub-section, ensure that the *Authorized redirect URIs* field is set to `http://127.0.0.1:8080/login/oauth2/code/google`.
 
 
 TIP: The default redirect URI template is `{baseUrl}/login/oauth2/code/{registrationId}`.
 TIP: The default redirect URI template is `{baseUrl}/login/oauth2/code/{registrationId}`.
  The *_registrationId_* is a unique identifier for the `ClientRegistration`.
  The *_registrationId_* is a unique identifier for the `ClientRegistration`.
@@ -76,7 +165,7 @@ spring:
 [[google-boot-application]]
 [[google-boot-application]]
 === Boot up the application
 === Boot up the application
 
 
-Launch the Spring Boot 2.0 sample and go to `http://localhost:8080`.
+Launch the Spring Boot 2.0 sample and go to `http://127.0.0.1:8080`.
 You are then redirected to the default _auto-generated_ login page, which displays a link for Google.
 You are then redirected to the default _auto-generated_ login page, which displays a link for Google.
 
 
 Click on the Google link, and you are then redirected to Google for authentication.
 Click on the Google link, and you are then redirected to Google for authentication.
@@ -102,7 +191,7 @@ This section shows how to configure the sample application using GitHub as the A
 
 
 To use GitHub's OAuth 2.0 authentication system for login, you must https://github.com/settings/applications/new[Register a new OAuth application].
 To use GitHub's OAuth 2.0 authentication system for login, you must https://github.com/settings/applications/new[Register a new OAuth application].
 
 
-When registering the OAuth application, ensure the *Authorization callback URL* is set to `http://localhost:8080/login/oauth2/code/github`.
+When registering the OAuth application, ensure the *Authorization callback URL* is set to `http://127.0.0.1:8080/login/oauth2/code/github`.
 
 
 The Authorization callback URL (redirect URI) is the path in the application that the end-user's user-agent is redirected back to after they have authenticated with GitHub
 The Authorization callback URL (redirect URI) is the path in the application that the end-user's user-agent is redirected back to after they have authenticated with GitHub
 and have granted access to the OAuth application on the _Authorize application_ page.
 and have granted access to the OAuth application on the _Authorize application_ page.
@@ -143,7 +232,7 @@ spring:
 [[github-boot-application]]
 [[github-boot-application]]
 === Boot up the application
 === Boot up the application
 
 
-Launch the Spring Boot 2.0 sample and go to `http://localhost:8080`.
+Launch the Spring Boot 2.0 sample and go to `http://127.0.0.1:8080`.
 You are then redirected to the default _auto-generated_ login page, which displays a link for GitHub.
 You are then redirected to the default _auto-generated_ login page, which displays a link for GitHub.
 
 
 Click on the GitHub link, and you are then redirected to GitHub for authentication.
 Click on the GitHub link, and you are then redirected to GitHub for authentication.
@@ -180,7 +269,7 @@ NOTE: The selection for the _Category_ field is not relevant but it's a required
 The next page presented is "Product Setup". Click the "Get Started" button for the *Facebook Login* product.
 The next page presented is "Product Setup". Click the "Get Started" button for the *Facebook Login* product.
 In the left sidebar, under _Products -> Facebook Login_, select _Settings_.
 In the left sidebar, under _Products -> Facebook Login_, select _Settings_.
 
 
-For the field *Valid OAuth redirect URIs*, enter `http://localhost:8080/login/oauth2/code/facebook` then click _Save Changes_.
+For the field *Valid OAuth redirect URIs*, enter `http://127.0.0.1:8080/login/oauth2/code/facebook` then click _Save Changes_.
 
 
 The OAuth redirect URI is the path in the application that the end-user's user-agent is redirected back to after they have authenticated with Facebook
 The OAuth redirect URI is the path in the application that the end-user's user-agent is redirected back to after they have authenticated with Facebook
 and have granted access to the application on the _Authorize application_ page.
 and have granted access to the application on the _Authorize application_ page.
@@ -221,7 +310,7 @@ spring:
 [[facebook-boot-application]]
 [[facebook-boot-application]]
 === Boot up the application
 === Boot up the application
 
 
-Launch the Spring Boot 2.0 sample and go to `http://localhost:8080`.
+Launch the Spring Boot 2.0 sample and go to `http://127.0.0.1:8080`.
 You are then redirected to the default _auto-generated_ login page, which displays a link for Facebook.
 You are then redirected to the default _auto-generated_ login page, which displays a link for Facebook.
 
 
 Click on the Facebook link, and you are then redirected to Facebook for authentication.
 Click on the Facebook link, and you are then redirected to Facebook for authentication.
@@ -256,7 +345,7 @@ From the "Add Application" page, select the "Create New App" button and enter th
 
 
 Select the _Create_ button.
 Select the _Create_ button.
 On the "General Settings" page, enter the Application Name (for example, "Spring Security Okta Login") and then select the _Next_ button.
 On the "General Settings" page, enter the Application Name (for example, "Spring Security Okta Login") and then select the _Next_ button.
-On the "Configure OpenID Connect" page, enter `http://localhost:8080/login/oauth2/code/okta` for the field *Redirect URIs* and then select _Finish_.
+On the "Configure OpenID Connect" page, enter `http://127.0.0.1:8080/login/oauth2/code/okta` for the field *Redirect URIs* and then select _Finish_.
 
 
 The redirect URI is the path in the application that the end-user's user-agent is redirected back to after they have authenticated with Okta
 The redirect URI is the path in the application that the end-user's user-agent is redirected back to after they have authenticated with Okta
 and have granted access to the application on the _Authorize application_ page.
 and have granted access to the application on the _Authorize application_ page.
@@ -312,7 +401,7 @@ As well, replace `https://your-subdomain.oktapreview.com` in `authorization-uri`
 [[okta-boot-application]]
 [[okta-boot-application]]
 === Boot up the application
 === Boot up the application
 
 
-Launch the Spring Boot 2.0 sample and go to `http://localhost:8080`.
+Launch the Spring Boot 2.0 sample and go to `http://127.0.0.1:8080`.
 You are then redirected to the default _auto-generated_ login page, which displays a link for Okta.
 You are then redirected to the default _auto-generated_ login page, which displays a link for Okta.
 
 
 Click on the Okta link, and you are then redirected to Okta for authentication.
 Click on the Okta link, and you are then redirected to Okta for authentication.

+ 7 - 3
servlet/spring-boot/java/oauth2/login/src/integTest/java/example/OAuth2LoginApplicationTests.java

@@ -267,7 +267,7 @@ public class OAuth2LoginApplicationTests {
 	private void assertLoginPage(HtmlPage page) {
 	private void assertLoginPage(HtmlPage page) {
 		assertThat(page.getTitleText()).isEqualTo("Please sign in");
 		assertThat(page.getTitleText()).isEqualTo("Please sign in");
 
 
-		int expectedClients = 4;
+		int expectedClients = 5;
 
 
 		List<HtmlAnchor> clientAnchorElements = page.getAnchors();
 		List<HtmlAnchor> clientAnchorElements = page.getAnchors();
 		assertThat(clientAnchorElements.size()).isEqualTo(expectedClients);
 		assertThat(clientAnchorElements.size()).isEqualTo(expectedClients);
@@ -277,19 +277,23 @@ public class OAuth2LoginApplicationTests {
 		ClientRegistration facebookClientRegistration = this.clientRegistrationRepository
 		ClientRegistration facebookClientRegistration = this.clientRegistrationRepository
 				.findByRegistrationId("facebook");
 				.findByRegistrationId("facebook");
 		ClientRegistration oktaClientRegistration = this.clientRegistrationRepository.findByRegistrationId("okta");
 		ClientRegistration oktaClientRegistration = this.clientRegistrationRepository.findByRegistrationId("okta");
+		ClientRegistration springClientRegistration = this.clientRegistrationRepository
+				.findByRegistrationId("login-client");
 
 
 		String baseAuthorizeUri = AUTHORIZATION_BASE_URI + "/";
 		String baseAuthorizeUri = AUTHORIZATION_BASE_URI + "/";
 		String googleClientAuthorizeUri = baseAuthorizeUri + googleClientRegistration.getRegistrationId();
 		String googleClientAuthorizeUri = baseAuthorizeUri + googleClientRegistration.getRegistrationId();
 		String githubClientAuthorizeUri = baseAuthorizeUri + githubClientRegistration.getRegistrationId();
 		String githubClientAuthorizeUri = baseAuthorizeUri + githubClientRegistration.getRegistrationId();
 		String facebookClientAuthorizeUri = baseAuthorizeUri + facebookClientRegistration.getRegistrationId();
 		String facebookClientAuthorizeUri = baseAuthorizeUri + facebookClientRegistration.getRegistrationId();
 		String oktaClientAuthorizeUri = baseAuthorizeUri + oktaClientRegistration.getRegistrationId();
 		String oktaClientAuthorizeUri = baseAuthorizeUri + oktaClientRegistration.getRegistrationId();
+		String springClientAuthorizeUri = baseAuthorizeUri + springClientRegistration.getRegistrationId();
 
 
 		for (int i = 0; i < expectedClients; i++) {
 		for (int i = 0; i < expectedClients; i++) {
 			assertThat(clientAnchorElements.get(i).getAttribute("href")).isIn(googleClientAuthorizeUri,
 			assertThat(clientAnchorElements.get(i).getAttribute("href")).isIn(googleClientAuthorizeUri,
-					githubClientAuthorizeUri, facebookClientAuthorizeUri, oktaClientAuthorizeUri);
+					githubClientAuthorizeUri, facebookClientAuthorizeUri, oktaClientAuthorizeUri,
+					springClientAuthorizeUri);
 			assertThat(clientAnchorElements.get(i).asText()).isIn(googleClientRegistration.getClientName(),
 			assertThat(clientAnchorElements.get(i).asText()).isIn(googleClientRegistration.getClientName(),
 					githubClientRegistration.getClientName(), facebookClientRegistration.getClientName(),
 					githubClientRegistration.getClientName(), facebookClientRegistration.getClientName(),
-					oktaClientRegistration.getClientName());
+					oktaClientRegistration.getClientName(), springClientRegistration.getClientName());
 		}
 		}
 	}
 	}
 
 

+ 68 - 0
servlet/spring-boot/java/oauth2/login/src/main/java/example/filter/LoopbackIpRedirectFilter.java

@@ -0,0 +1,68 @@
+/*
+ * Copyright 2021 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.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package example.filter;
+
+import java.io.IOException;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
+import org.springframework.http.server.ServletServerHttpRequest;
+import org.springframework.stereotype.Component;
+import org.springframework.web.filter.OncePerRequestFilter;
+import org.springframework.web.util.UriComponents;
+import org.springframework.web.util.UriComponentsBuilder;
+
+/**
+ * This filter ensures that the loopback IP <code>127.0.0.1</code> is used to access the
+ * application so that the sample works correctly, due to the fact that redirect URIs with
+ * "localhost" are rejected by the Spring Authorization Server, because the OAuth 2.1
+ * draft specification states:
+ *
+ * <pre>
+ *     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.
+ * </pre>
+ *
+ * @author Steve Riesenberg
+ * @see <a href=
+ * "https://tools.ietf.org/html/draft-ietf-oauth-v2-1-01#section-9.7.1">Loopback Redirect
+ * Considerations in Native Apps</a>
+ */
+@Component
+@Order(Ordered.HIGHEST_PRECEDENCE)
+public class LoopbackIpRedirectFilter extends OncePerRequestFilter {
+
+	@Override
+	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
+			throws ServletException, IOException {
+		if (request.getServerName().equals("localhost") && request.getHeader("host") != null) {
+			UriComponents uri = UriComponentsBuilder.fromHttpRequest(new ServletServerHttpRequest(request))
+					.host("127.0.0.1").build();
+			response.sendRedirect(uri.toUriString());
+			return;
+		}
+		filterChain.doFilter(request, response);
+	}
+
+}

+ 13 - 0
servlet/spring-boot/java/oauth2/login/src/main/resources/application.yml

@@ -15,6 +15,15 @@ spring:
     oauth2:
     oauth2:
       client:
       client:
         registration:
         registration:
+          login-client:
+            provider: spring
+            client-id: login-client
+            client-secret: openid-connect
+            client-authentication-method: client_secret_basic
+            authorization-grant-type: authorization_code
+            redirect-uri: http://127.0.0.1:8080/login/oauth2/code/login-client
+            scope: openid,profile
+            client-name: Spring
           google:
           google:
             client-id: your-app-client-id
             client-id: your-app-client-id
             client-secret: your-app-client-secret
             client-secret: your-app-client-secret
@@ -28,6 +37,10 @@ spring:
             client-id: your-app-client-id
             client-id: your-app-client-id
             client-secret: your-app-client-secret
             client-secret: your-app-client-secret
         provider:
         provider:
+          spring:
+            authorization-uri: http://localhost:9000/oauth2/authorize
+            token-uri: http://localhost:9000/oauth2/token
+            jwk-set-uri: http://localhost:9000/oauth2/jwks
           okta:
           okta:
             authorization-uri: https://your-subdomain.oktapreview.com/oauth2/v1/authorize
             authorization-uri: https://your-subdomain.oktapreview.com/oauth2/v1/authorize
             token-uri: https://your-subdomain.oktapreview.com/oauth2/v1/token
             token-uri: https://your-subdomain.oktapreview.com/oauth2/v1/token