Update oauth login samples
Closes gh-29
This commit is contained in:
		
							parent
							
								
									6548ff0876
								
							
						
					
					
						commit
						52cc331d9c
					
				| @ -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. | ||||||
|  | |||||||
| @ -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); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | } | ||||||
| @ -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 | ||||||
|  | |||||||
| @ -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 | 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. | ||||||
| { |  | ||||||
|     "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. |  | ||||||
| 
 | 
 | ||||||
| [[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. | 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. | ||||||
| 
 | 
 | ||||||
| To change the sample to point to this authorization server, simply find this property in that project's `application.yml`: | You can run that app similarly to the authorization server: | ||||||
| 
 |  | ||||||
| ```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 |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| And then you can run that app similarly to the authorization server: |  | ||||||
| 
 | 
 | ||||||
| ```bash | ```bash | ||||||
| ./gradlew bootRun | ./gradlew bootRun | ||||||
|  | |||||||
| @ -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 | ||||||
|  | |||||||
| @ -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 |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
| @ -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; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
| @ -16,28 +16,32 @@ | |||||||
| 
 | 
 | ||||||
| package example; | package example; | ||||||
| 
 | 
 | ||||||
| import java.util.HashSet; | import java.security.KeyPair; | ||||||
| import java.util.Set; | 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) -> | 			.authorizeRequests((requests) -> requests.anyRequest().authenticated()) | ||||||
| 				sessionManagement | 			.formLogin(Customizer.withDefaults()); | ||||||
| 					.sessionCreationPolicy(SessionCreationPolicy.STATELESS) |  | ||||||
| 			); |  | ||||||
| 		// @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() { | 	public JWKSource<SecurityContext> jwkSource(KeyPair keyPair) { | ||||||
| 		RSAKey rsaKey = Jwks.generateRsa(); | 		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) { | 	public JwtDecoder jwtDecoder(KeyPair keyPair) { | ||||||
| 		Set<JWSAlgorithm> jwsAlgs = new HashSet<>(); | 		return NimbusJwtDecoder.withPublicKey((RSAPublicKey) keyPair.getPublic()).build(); | ||||||
| 		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); |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	@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; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| } | } | ||||||
|  | |||||||
| @ -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. | ||||||
|  | |||||||
| @ -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()); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -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); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | } | ||||||
| @ -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 | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user