Add support for OAuth 2.0 Login
Fixes gh-3907
This commit is contained in:
@@ -44,6 +44,13 @@
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-dependencies</artifactId>
|
||||
<version>1.5.0.BUILD-SNAPSHOT</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
<dependencies>
|
||||
|
||||
@@ -44,6 +44,13 @@
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-dependencies</artifactId>
|
||||
<version>1.5.0.BUILD-SNAPSHOT</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
<dependencies>
|
||||
|
||||
@@ -0,0 +1,342 @@
|
||||
= OAuth 2.0 Login Sample
|
||||
Joe Grandja
|
||||
:toc:
|
||||
:security-site-url: https://projects.spring.io/spring-security/
|
||||
|
||||
[.lead]
|
||||
This guide will walk you through the steps for setting up the sample application with OAuth 2.0 Login using an external _OAuth 2.0_ or _OpenID Connect 1.0_ Provider.
|
||||
The sample application is built with *Spring Boot 1.5* and the *spring-security-oauth2-client* module that is new in {security-site-url}[Spring Security 5.0].
|
||||
|
||||
The following sections outline detailed steps for setting up OAuth 2.0 Login with these Providers:
|
||||
|
||||
* <<google-login, Google>>
|
||||
* <<github-login, GitHub>>
|
||||
* <<facebook-login, Facebook>>
|
||||
* <<okta-login, Okta>>
|
||||
|
||||
NOTE: The _"authentication flow"_ is realized using the *Authorization Code Grant*, as specified in the https://tools.ietf.org/html/rfc6749#section-4.1[OAuth 2.0 Authorization Framework].
|
||||
|
||||
[[sample-app-content]]
|
||||
== Sample application content
|
||||
|
||||
The sample application contains the following package structure and artifacts:
|
||||
|
||||
*org.springframework.security.samples*
|
||||
|
||||
[circle]
|
||||
* _OAuth2LoginApplication_ - the main class for the _Spring application_.
|
||||
** *user*
|
||||
*** _GitHubOAuth2User_ - a custom _UserInfo_ type for <<github-login, GitHub Login>>.
|
||||
** *web*
|
||||
*** _MainController_ - the root controller that displays user information after a successful login.
|
||||
|
||||
*org.springframework.boot.autoconfigure.security.oauth2.client*
|
||||
|
||||
[circle]
|
||||
* <<client-registration-auto-configuration-class, _ClientRegistrationAutoConfiguration_>> - a Spring Boot auto-configuration class
|
||||
that automatically registers a _ClientRegistrationRepository_ bean in the _ApplicationContext_.
|
||||
* <<oauth2-login-auto-configuration-class, _OAuth2LoginAutoConfiguration_>> - a Spring Boot auto-configuration class that automatically enables OAuth 2.0 Login.
|
||||
|
||||
WARNING: The Spring Boot auto-configuration classes (and dependent resources) will eventually _live_ in the *Spring Boot Security Starter*.
|
||||
|
||||
NOTE: See <<oauth2-login-auto-configuration, OAuth 2.0 Login auto-configuration>> for a detailed overview of the auto-configuration classes.
|
||||
|
||||
[[google-login]]
|
||||
== Setting up *_Login with Google_*
|
||||
|
||||
The goal for this section of the guide is to setup login using Google as the _Authentication Provider_.
|
||||
|
||||
NOTE: https://developers.google.com/identity/protocols/OpenIDConnect[Google's OAuth 2.0 implementation] for authentication conforms to the
|
||||
http://openid.net/connect/[OpenID Connect] specification and is http://openid.net/certification/[OpenID Certified].
|
||||
|
||||
[[google-login-register-credentials]]
|
||||
=== Register OAuth 2.0 credentials
|
||||
|
||||
In order to use Google's OAuth 2.0 authentication system for login, you must set up a project in the *Google API Console* to obtain OAuth 2.0 credentials.
|
||||
|
||||
Follow the instructions on the https://developers.google.com/identity/protocols/OpenIDConnect[OpenID Connect] page starting in the section *_"Setting up OAuth 2.0"_*.
|
||||
|
||||
After completing the sub-section, *_"Obtain OAuth 2.0 credentials"_*, you should have created a new *OAuth Client* with credentials consisting of a *Client ID* and *Client Secret*.
|
||||
|
||||
[[google-login-redirect-uri]]
|
||||
=== Setting the redirect URI
|
||||
|
||||
The redirect URI is the path in the sample 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 from the <<google-login-register-credentials, previous step>>)_ on the *Consent screen* page.
|
||||
|
||||
For the sub-section, *_"Set a redirect URI"_*, ensure the *Authorised redirect URIs* is set to *http://localhost:8080/oauth2/authorize/code/google*
|
||||
|
||||
TIP: The default redirect URI is *_"{scheme}://{serverName}:{serverPort}/oauth2/authorize/code/{clientAlias}"_*.
|
||||
See <<oauth2-client-properties, OAuth client properties>> for more details on this default.
|
||||
|
||||
[[google-login-configure-application-yml]]
|
||||
=== Configuring application.yml
|
||||
|
||||
Now that we have created a new OAuth Client with Google, we need to configure the sample application to use this OAuth Client for the _authentication flow_.
|
||||
|
||||
Go to *_src/main/resources_* and edit *application.yml*. Add the following configuration:
|
||||
|
||||
[source,yaml]
|
||||
----
|
||||
security:
|
||||
oauth2:
|
||||
client:
|
||||
google:
|
||||
client-id: ${client-id}
|
||||
client-secret: ${client-secret}
|
||||
----
|
||||
|
||||
Replace *${client-id}* and *${client-secret}* with the OAuth 2.0 credentials created in the previous section <<google-login-register-credentials, Register OAuth 2.0 credentials>>.
|
||||
|
||||
[TIP]
|
||||
.OAuth client properties
|
||||
====
|
||||
. *security.oauth2.client* is the *_base property prefix_* for OAuth client properties.
|
||||
. Just below the *_base property prefix_* is the *_client property key_*, for example *security.oauth2.client.google*.
|
||||
. At the base of the *_client property key_* are the properties for specifying the configuration for an OAuth Client.
|
||||
A list of these properties are detailed in <<oauth2-client-properties, OAuth client properties>>.
|
||||
====
|
||||
|
||||
[[google-login-run-sample]]
|
||||
=== Running the sample
|
||||
|
||||
Launch the Spring Boot application by running *_org.springframework.security.samples.OAuth2LoginApplication_*.
|
||||
|
||||
After the application successfully starts up, go to http://localhost:8080. You will be redirected to http://localhost:8080/login, which will display an _auto-generated login page_ with an anchor link for *Google*.
|
||||
|
||||
Click through on the Google link and you'll be redirected to Google for authentication.
|
||||
|
||||
After you authenticate using your Google credentials, the next page presented to you will be the *Consent screen*.
|
||||
The Consent screen will ask you to either *_Allow_* or *_Deny_* access to the OAuth Client you created in the previous step <<google-login-register-credentials, Register OAuth 2.0 credentials>>.
|
||||
Click *_Allow_* to authorize the OAuth Client to access your _email address_ and _basic profile_ information.
|
||||
|
||||
At this point, the OAuth Client will retrieve your email address and basic profile information from the http://openid.net/specs/openid-connect-core-1_0.html#UserInfo[*UserInfo Endpoint*] and establish an _authenticated session_.
|
||||
The home page will then be displayed showing the user attributes retrieved from the *UserInfo Endpoint*, for example, name, email, profile, sub, etc.
|
||||
|
||||
[[oauth2-login-auto-configuration]]
|
||||
== OAuth 2.0 Login auto-configuration
|
||||
|
||||
As you worked through this guide and setup OAuth 2.0 Login with one of the Providers,
|
||||
we hope you noticed the ease in configuration and setup required in getting the sample up and running?
|
||||
And you may be asking, how does this all work? Thanks to some Spring Boot auto-configuration _magic_,
|
||||
we were able to automatically register the OAuth Client(s) configured in the `Environment`,
|
||||
as well, provide a minimal security configuration for OAuth 2.0 Login for these registered OAuth Client(s).
|
||||
|
||||
The following provides an overview of the Spring Boot auto-configuration classes:
|
||||
|
||||
[[client-registration-auto-configuration-class]]
|
||||
*_org.springframework.boot.autoconfigure.security.oauth2.client.ClientRegistrationAutoConfiguration_*::
|
||||
`ClientRegistrationAutoConfiguration` is responsible for registering a `ClientRegistrationRepository` _bean_ with the `ApplicationContext`.
|
||||
The `ClientRegistrationRepository` is composed of one or more `ClientRegistration` instances, which are created from the OAuth client properties
|
||||
configured in the `Environment` that are prefixed with `security.oauth2.client.[client-alias]`, for example, `security.oauth2.client.google`.
|
||||
|
||||
NOTE: `ClientRegistrationAutoConfiguration` also loads a _resource_ named *oauth2-clients-defaults.yml*,
|
||||
which provides a set of default client property values for a number of _well-known_ Providers.
|
||||
More on this in the later section <<oauth2-default-client-properties, Default client property values>>.
|
||||
|
||||
[[oauth2-login-auto-configuration-class]]
|
||||
*_org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2LoginAutoConfiguration_*::
|
||||
`OAuth2LoginAutoConfiguration` is responsible for enabling OAuth 2.0 Login,
|
||||
only if there is a `ClientRegistrationRepository` _bean_ available in the `ApplicationContext`.
|
||||
|
||||
WARNING: The auto-configuration classes (and dependent resources) will eventually _live_ in the *Spring Boot Security Starter*.
|
||||
|
||||
[[oauth2-client-properties]]
|
||||
=== OAuth client properties
|
||||
|
||||
The following specifies the common set of properties available for configuring an OAuth Client.
|
||||
|
||||
[TIP]
|
||||
====
|
||||
- *security.oauth2.client* is the *_base property prefix_* for OAuth client properties.
|
||||
- Just below the *_base property prefix_* is the *_client property key_*, for example *security.oauth2.client.google*.
|
||||
- At the base of the *_client property key_* are the properties for specifying the configuration for an OAuth Client.
|
||||
====
|
||||
|
||||
- *client-authentication-method* - the method used to authenticate the _Client_ with the _Provider_. Supported values are *header* and *form*.
|
||||
- *authorized-grant-type* - the OAuth 2.0 Authorization Framework defines the https://tools.ietf.org/html/rfc6749#section-1.3.1[Authorization Code] grant type,
|
||||
which is used to realize the _"authentication flow"_. Currently, this is the only supported grant type.
|
||||
- *redirect-uri* - this is the client's _registered_ redirect URI that the _Authorization Server_ redirects the end-user's user-agent
|
||||
to after the end-user has authenticated and authorized access for the client.
|
||||
|
||||
NOTE: The default redirect URI is _"{scheme}://{serverName}:{serverPort}/oauth2/authorize/code/{clientAlias}"_, which leverages *URI template variables*.
|
||||
|
||||
- *scopes* - a comma-delimited string of scope(s) requested during the _Authorization Request_ flow, for example: _openid, email, profile_
|
||||
|
||||
NOTE: _OpenID Connect 1.0_ defines these http://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims[standard scopes]: _profile, email, address, phone_
|
||||
|
||||
NOTE: Non-standard scopes may be defined by a standard _OAuth 2.0 Provider_. Please consult the Provider's OAuth API documentation to learn which scopes are supported.
|
||||
|
||||
- *authorization-uri* - the URI used by the client to redirect the end-user's user-agent to the _Authorization Server_ in order to obtain authorization from the end-user (the _Resource Owner_).
|
||||
- *token-uri* - the URI used by the client when exchanging an _Authorization Grant_ (for example, Authorization Code) for an _Access Token_ at the _Authorization Server_.
|
||||
- *user-info-uri* - the URI used by the client to access the protected resource *UserInfo Endpoint*, in order to obtain attributes of the end-user.
|
||||
- *user-info-converter* - the `Converter` implementation class used to convert the *UserInfo Response* to a `UserInfo` (_OpenID Connect 1.0 Provider_) or `OAuth2User` instance (_Standard OAuth 2.0 Provider_).
|
||||
|
||||
TIP: The `Converter` implementation class for an _OpenID Connect 1.0 Provider_ is *org.springframework.security.oauth2.client.user.converter.UserInfoConverter*
|
||||
and for a standard _OAuth 2.0 Provider_ it's *org.springframework.security.oauth2.client.user.converter.OAuth2UserConverter*.
|
||||
|
||||
- *user-info-name-attribute-key* - the _key_ used to retrieve the *Name* of the end-user from the `Map` of available attributes in `UserInfo` or `OAuth2User`.
|
||||
|
||||
NOTE: _OpenID Connect 1.0_ defines the http://openid.net/specs/openid-connect-core-1_0.html#StandardClaims[*"name"* Claim], which is the end-user's full name and is the default used for `UserInfo`.
|
||||
|
||||
IMPORTANT: Standard _OAuth 2.0 Provider's_ may vary the naming of their *Name* attribute. Please consult the Provider's *UserInfo* API documentation.
|
||||
This is a *_required_* property when *user-info-converter* is set to `OAuth2UserConverter`.
|
||||
|
||||
- *client-name* - this is a descriptive name used for the client. The name may be used in certain scenarios, for example, when displaying the name of the client in the _auto-generated login page_.
|
||||
- *client-alias* - an _alias_ which uniquely identifies the client. It *must be* unique within a `ClientRegistrationRepository`.
|
||||
|
||||
[[oauth2-default-client-properties]]
|
||||
=== Default client property values
|
||||
|
||||
As noted previously, <<client-registration-auto-configuration-class, `ClientRegistrationAutoConfiguration`>> loads a _resource_ named *oauth2-clients-defaults.yml*,
|
||||
which provides a set of default client property values for a number of _well-known_ Providers.
|
||||
|
||||
For example, the *authorization-uri*, *token-uri*, *user-info-uri* rarely change for a Provider and therefore it makes sense to
|
||||
provide a set of defaults in order to reduce the configuration required by the user.
|
||||
|
||||
Below are the current set of default client property values:
|
||||
|
||||
.oauth2-clients-defaults.yml
|
||||
[source,yaml]
|
||||
----
|
||||
security:
|
||||
oauth2:
|
||||
client:
|
||||
google:
|
||||
client-authentication-method: header
|
||||
authorized-grant-type: authorization_code
|
||||
redirect-uri: "{scheme}://{serverName}:{serverPort}{baseAuthorizeUri}/{clientAlias}"
|
||||
scopes: openid, email, profile
|
||||
authorization-uri: "https://accounts.google.com/o/oauth2/auth"
|
||||
token-uri: "https://accounts.google.com/o/oauth2/token"
|
||||
user-info-uri: "https://www.googleapis.com/oauth2/v3/userinfo"
|
||||
user-info-converter: "org.springframework.security.oauth2.client.user.converter.UserInfoConverter"
|
||||
client-name: Google
|
||||
client-alias: google
|
||||
github:
|
||||
client-authentication-method: header
|
||||
authorized-grant-type: authorization_code
|
||||
redirect-uri: "{scheme}://{serverName}:{serverPort}{baseAuthorizeUri}/{clientAlias}"
|
||||
scopes: user
|
||||
authorization-uri: "https://github.com/login/oauth/authorize"
|
||||
token-uri: "https://github.com/login/oauth/access_token"
|
||||
user-info-uri: "https://api.github.com/user"
|
||||
user-info-converter: "org.springframework.security.oauth2.client.user.converter.OAuth2UserConverter"
|
||||
client-name: GitHub
|
||||
client-alias: github
|
||||
facebook:
|
||||
client-authentication-method: form
|
||||
authorized-grant-type: authorization_code
|
||||
redirect-uri: "{scheme}://{serverName}:{serverPort}{baseAuthorizeUri}/{clientAlias}"
|
||||
scopes: public_profile, email
|
||||
authorization-uri: "https://www.facebook.com/v2.8/dialog/oauth"
|
||||
token-uri: "https://graph.facebook.com/v2.8/oauth/access_token"
|
||||
user-info-uri: "https://graph.facebook.com/me"
|
||||
user-info-converter: "org.springframework.security.oauth2.client.user.converter.OAuth2UserConverter"
|
||||
client-name: Facebook
|
||||
client-alias: facebook
|
||||
okta:
|
||||
client-authentication-method: header
|
||||
authorized-grant-type: authorization_code
|
||||
redirect-uri: "{scheme}://{serverName}:{serverPort}{baseAuthorizeUri}/{clientAlias}"
|
||||
scopes: openid, email, profile
|
||||
user-info-converter: "org.springframework.security.oauth2.client.user.converter.UserInfoConverter"
|
||||
client-name: Okta
|
||||
client-alias: okta
|
||||
----
|
||||
|
||||
= Appendix
|
||||
'''
|
||||
|
||||
[[configure-non-spring-boot-app]]
|
||||
== Configuring a _Non-Spring-Boot_ application
|
||||
|
||||
If you are not using Spring Boot for your application, you will not be able to leverage the auto-configuration features for OAuth 2.0 Login.
|
||||
You will be required to provide your own _security configuration_ in order to enable OAuth 2.0 Login.
|
||||
|
||||
The following sample code demonstrates a minimal security configuration for enabling OAuth 2.0 Login.
|
||||
|
||||
Assuming we have a _properties file_ named *oauth2-clients.properties* on the _classpath_ and it specifies all the _required_ properties for an OAuth Client, specifically _"Google"_:
|
||||
|
||||
.oauth2-clients.properties
|
||||
[source,properties]
|
||||
----
|
||||
security.oauth2.client.google.client-id=${client-id}
|
||||
security.oauth2.client.google.client-secret=${client-secret}
|
||||
security.oauth2.client.google.client-authentication-method=header
|
||||
security.oauth2.client.google.authorized-grant-type=authorization_code
|
||||
security.oauth2.client.google.redirect-uri=http://localhost:8080/oauth2/authorize/code/google
|
||||
security.oauth2.client.google.scopes=openid,email,profile
|
||||
security.oauth2.client.google.authorization-uri=https://accounts.google.com/o/oauth2/auth
|
||||
security.oauth2.client.google.token-uri=https://accounts.google.com/o/oauth2/token
|
||||
security.oauth2.client.google.user-info-uri=https://www.googleapis.com/oauth2/v3/userinfo
|
||||
security.oauth2.client.google.user-info-converter=org.springframework.security.oauth2.client.user.converter.UserInfoConverter
|
||||
security.oauth2.client.google.client-name=Google
|
||||
security.oauth2.client.google.client-alias=google
|
||||
----
|
||||
|
||||
The following _security configuration_ will enable OAuth 2.0 Login using _"Google"_ as the _Authentication Provider_:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@EnableWebSecurity
|
||||
@PropertySource("classpath:oauth2-clients.properties")
|
||||
public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
private Environment environment;
|
||||
|
||||
public SecurityConfig(Environment environment) {
|
||||
this.environment = environment;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeRequests()
|
||||
.anyRequest().authenticated()
|
||||
.and()
|
||||
.oauth2Login()
|
||||
.clients(clientRegistrationRepository())
|
||||
.userInfoEndpoint()
|
||||
.userInfoTypeConverter(
|
||||
new UserInfoConverter(),
|
||||
new URI("https://www.googleapis.com/oauth2/v3/userinfo"));
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ClientRegistrationRepository clientRegistrationRepository() {
|
||||
List<ClientRegistration> clientRegistrations = Collections.singletonList(
|
||||
clientRegistration("security.oauth2.client.google."));
|
||||
|
||||
return new InMemoryClientRegistrationRepository(clientRegistrations);
|
||||
}
|
||||
|
||||
private ClientRegistration clientRegistration(String clientPropertyKey) {
|
||||
String clientId = this.environment.getProperty(clientPropertyKey + "client-id");
|
||||
String clientSecret = this.environment.getProperty(clientPropertyKey + "client-secret");
|
||||
ClientAuthenticationMethod clientAuthenticationMethod = ClientAuthenticationMethod.valueOf(
|
||||
this.environment.getProperty(clientPropertyKey + "client-authentication-method").toUpperCase());
|
||||
AuthorizationGrantType authorizationGrantType = AuthorizationGrantType.valueOf(
|
||||
this.environment.getProperty(clientPropertyKey + "authorized-grant-type").toUpperCase());
|
||||
String redirectUri = this.environment.getProperty(clientPropertyKey + "redirect-uri");
|
||||
String[] scopes = this.environment.getProperty(clientPropertyKey + "scopes").split(",");
|
||||
String authorizationUri = this.environment.getProperty(clientPropertyKey + "authorization-uri");
|
||||
String tokenUri = this.environment.getProperty(clientPropertyKey + "token-uri");
|
||||
String userInfoUri = this.environment.getProperty(clientPropertyKey + "user-info-uri");
|
||||
String clientName = this.environment.getProperty(clientPropertyKey + "client-name");
|
||||
String clientAlias = this.environment.getProperty(clientPropertyKey + "client-alias");
|
||||
|
||||
return new ClientRegistration.Builder(clientId)
|
||||
.clientSecret(clientSecret)
|
||||
.clientAuthenticationMethod(clientAuthenticationMethod)
|
||||
.authorizedGrantType(authorizationGrantType)
|
||||
.redirectUri(redirectUri)
|
||||
.scopes(scopes)
|
||||
.authorizationUri(authorizationUri)
|
||||
.tokenUri(tokenUri)
|
||||
.userInfoUri(userInfoUri)
|
||||
.clientName(clientName)
|
||||
.clientAlias(clientAlias)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
----
|
||||
@@ -0,0 +1,173 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-samples-boot-oauth2login</artifactId>
|
||||
<version>5.0.0.BUILD-SNAPSHOT</version>
|
||||
<name>spring-security-samples-boot-oauth2login</name>
|
||||
<description>spring-security-samples-boot-oauth2login</description>
|
||||
<url>http://spring.io/spring-security</url>
|
||||
<organization>
|
||||
<name>spring.io</name>
|
||||
<url>http://spring.io/</url>
|
||||
</organization>
|
||||
<licenses>
|
||||
<license>
|
||||
<name>The Apache Software License, Version 2.0</name>
|
||||
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
|
||||
<distribution>repo</distribution>
|
||||
</license>
|
||||
</licenses>
|
||||
<developers>
|
||||
<developer>
|
||||
<id>rwinch</id>
|
||||
<name>Rob Winch</name>
|
||||
<email>rwinch@pivotal.io</email>
|
||||
</developer>
|
||||
<developer>
|
||||
<id>jgrandja</id>
|
||||
<name>Joe Grandja</name>
|
||||
<email>jgrandja@pivotal.io</email>
|
||||
</developer>
|
||||
</developers>
|
||||
<scm>
|
||||
<connection>scm:git:git://github.com/spring-projects/spring-security</connection>
|
||||
<developerConnection>scm:git:git://github.com/spring-projects/spring-security</developerConnection>
|
||||
<url>https://github.com/spring-projects/spring-security</url>
|
||||
</scm>
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-framework-bom</artifactId>
|
||||
<version>4.3.5.RELEASE</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-dependencies</artifactId>
|
||||
<version>1.5.0.BUILD-SNAPSHOT</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-thymeleaf</artifactId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-config</artifactId>
|
||||
<version>5.0.0.BUILD-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-oauth2-client</artifactId>
|
||||
<version>5.0.0.BUILD-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-web</artifactId>
|
||||
<version>5.0.0.BUILD-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.thymeleaf.extras</groupId>
|
||||
<artifactId>thymeleaf-extras-springsecurity4</artifactId>
|
||||
<version>2.1.3.RELEASE</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-logging</groupId>
|
||||
<artifactId>commons-logging</artifactId>
|
||||
<version>1.2</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<version>1.1.2</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.12</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.sourceforge.htmlunit</groupId>
|
||||
<artifactId>htmlunit</artifactId>
|
||||
<version>2.24</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<version>3.6.2</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<version>1.10.19</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>jcl-over-slf4j</artifactId>
|
||||
<version>1.7.7</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-test</artifactId>
|
||||
<version>5.0.0.BUILD-SNAPSHOT</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>spring-snapshot</id>
|
||||
<url>https://repo.spring.io/snapshot</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,15 @@
|
||||
apply plugin: 'io.spring.convention.spring-sample-boot'
|
||||
|
||||
dependencies {
|
||||
compile project(':spring-security-config')
|
||||
compile project(':spring-security-oauth2-client')
|
||||
compile project(':spring-security-web')
|
||||
compile 'org.springframework.boot:spring-boot-starter-security'
|
||||
compile 'org.springframework.boot:spring-boot-starter-thymeleaf'
|
||||
compile 'org.springframework.boot:spring-boot-starter-web'
|
||||
compile 'org.thymeleaf.extras:thymeleaf-extras-springsecurity4'
|
||||
|
||||
testCompile project(':spring-security-test')
|
||||
testCompile 'net.sourceforge.htmlunit:htmlunit'
|
||||
testCompile 'org.springframework.boot:spring-boot-starter-test'
|
||||
}
|
||||
+405
@@ -0,0 +1,405 @@
|
||||
/*
|
||||
* Copyright 2012-2017 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
|
||||
*
|
||||
* http://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 org.springframework.security.samples;
|
||||
|
||||
import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException;
|
||||
import com.gargoylesoftware.htmlunit.WebClient;
|
||||
import com.gargoylesoftware.htmlunit.WebResponse;
|
||||
import com.gargoylesoftware.htmlunit.html.DomNodeList;
|
||||
import com.gargoylesoftware.htmlunit.html.HtmlAnchor;
|
||||
import com.gargoylesoftware.htmlunit.html.HtmlElement;
|
||||
import com.gargoylesoftware.htmlunit.html.HtmlPage;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.SpringBootConfiguration;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.http.HttpStatus;
|
||||
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.WebSecurityConfigurerAdapter;
|
||||
import org.springframework.security.oauth2.client.authentication.AuthorizationCodeAuthenticationProcessingFilter;
|
||||
import org.springframework.security.oauth2.client.authentication.AuthorizationCodeAuthenticationToken;
|
||||
import org.springframework.security.oauth2.client.authentication.AuthorizationCodeRequestRedirectFilter;
|
||||
import org.springframework.security.oauth2.client.authentication.AuthorizationGrantTokenExchanger;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.client.user.OAuth2UserService;
|
||||
import org.springframework.security.oauth2.core.AccessToken;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2Parameter;
|
||||
import org.springframework.security.oauth2.core.endpoint.ResponseType;
|
||||
import org.springframework.security.oauth2.core.endpoint.TokenResponseAttributes;
|
||||
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.web.util.UriComponents;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.net.URLDecoder;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Integration tests for the OAuth 2.0 client filters {@link AuthorizationCodeRequestRedirectFilter}
|
||||
* and {@link AuthorizationCodeAuthenticationProcessingFilter}.
|
||||
* These filters work together to realize the Authorization Code Grant flow.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest
|
||||
@AutoConfigureMockMvc
|
||||
public class OAuth2LoginApplicationTests {
|
||||
private static final String AUTHORIZATION_BASE_URI = "/oauth2/authorization/code";
|
||||
private static final String AUTHORIZE_BASE_URL = "http://localhost:8080/oauth2/authorize/code";
|
||||
|
||||
@Autowired
|
||||
private WebClient webClient;
|
||||
|
||||
@Autowired
|
||||
private ClientRegistrationRepository clientRegistrationRepository;
|
||||
|
||||
private ClientRegistration googleClientRegistration;
|
||||
private ClientRegistration githubClientRegistration;
|
||||
private ClientRegistration facebookClientRegistration;
|
||||
private ClientRegistration oktaClientRegistration;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
this.webClient.getCookieManager().clearCookies();
|
||||
this.googleClientRegistration = this.clientRegistrationRepository.getRegistrationByClientAlias("google");
|
||||
this.githubClientRegistration = this.clientRegistrationRepository.getRegistrationByClientAlias("github");
|
||||
this.facebookClientRegistration = this.clientRegistrationRepository.getRegistrationByClientAlias("facebook");
|
||||
this.oktaClientRegistration = this.clientRegistrationRepository.getRegistrationByClientAlias("okta");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestRootPageWhenNotAuthenticatedThenDisplayLoginPage() throws Exception {
|
||||
HtmlPage page = this.webClient.getPage("/");
|
||||
this.assertLoginPage(page);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestOtherPageWhenNotAuthenticatedThenDisplayLoginPage() throws Exception {
|
||||
HtmlPage page = this.webClient.getPage("/other-page");
|
||||
this.assertLoginPage(page);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestAuthorizeGitHubClientWhenLinkClickedThenStatusRedirectForAuthorization() throws Exception {
|
||||
HtmlPage page = this.webClient.getPage("/");
|
||||
|
||||
HtmlAnchor clientAnchorElement = this.getClientAnchorElement(page, this.githubClientRegistration);
|
||||
assertThat(clientAnchorElement).isNotNull();
|
||||
|
||||
WebResponse response = this.followLinkDisableRedirects(clientAnchorElement);
|
||||
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.MOVED_PERMANENTLY.value());
|
||||
|
||||
String authorizeRedirectUri = response.getResponseHeaderValue("Location");
|
||||
assertThat(authorizeRedirectUri).isNotNull();
|
||||
|
||||
UriComponents uriComponents = UriComponentsBuilder.fromUri(URI.create(authorizeRedirectUri)).build();
|
||||
|
||||
String requestUri = uriComponents.getScheme() + "://" + uriComponents.getHost() + uriComponents.getPath();
|
||||
assertThat(requestUri).isEqualTo(this.githubClientRegistration.getProviderDetails().getAuthorizationUri().toString());
|
||||
|
||||
Map<String, String> params = uriComponents.getQueryParams().toSingleValueMap();
|
||||
|
||||
assertThat(params.get(OAuth2Parameter.RESPONSE_TYPE)).isEqualTo(ResponseType.CODE.value());
|
||||
assertThat(params.get(OAuth2Parameter.CLIENT_ID)).isEqualTo(this.githubClientRegistration.getClientId());
|
||||
String redirectUri = AUTHORIZE_BASE_URL + "/" + this.githubClientRegistration.getClientAlias();
|
||||
assertThat(URLDecoder.decode(params.get(OAuth2Parameter.REDIRECT_URI), "UTF-8")).isEqualTo(redirectUri);
|
||||
assertThat(URLDecoder.decode(params.get(OAuth2Parameter.SCOPE), "UTF-8"))
|
||||
.isEqualTo(this.githubClientRegistration.getScopes().stream().collect(Collectors.joining(" ")));
|
||||
assertThat(params.get(OAuth2Parameter.STATE)).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestAuthorizeClientWhenInvalidClientThenStatusBadRequest() throws Exception {
|
||||
HtmlPage page = this.webClient.getPage("/");
|
||||
|
||||
HtmlAnchor clientAnchorElement = this.getClientAnchorElement(page, this.googleClientRegistration);
|
||||
assertThat(clientAnchorElement).isNotNull();
|
||||
clientAnchorElement.setAttribute("href", clientAnchorElement.getHrefAttribute() + "-invalid");
|
||||
|
||||
WebResponse response = null;
|
||||
try {
|
||||
clientAnchorElement.click();
|
||||
} catch (FailingHttpStatusCodeException ex) {
|
||||
response = ex.getResponse();
|
||||
}
|
||||
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST.value());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestAuthorizationCodeGrantWhenValidAuthorizationResponseThenDisplayUserInfoPage() throws Exception {
|
||||
HtmlPage page = this.webClient.getPage("/");
|
||||
|
||||
HtmlAnchor clientAnchorElement = this.getClientAnchorElement(page, this.githubClientRegistration);
|
||||
assertThat(clientAnchorElement).isNotNull();
|
||||
|
||||
WebResponse response = this.followLinkDisableRedirects(clientAnchorElement);
|
||||
|
||||
UriComponents authorizeRequestUriComponents = UriComponentsBuilder.fromUri(
|
||||
URI.create(response.getResponseHeaderValue("Location"))).build();
|
||||
|
||||
Map<String, String> params = authorizeRequestUriComponents.getQueryParams().toSingleValueMap();
|
||||
String code = "auth-code";
|
||||
String state = URLDecoder.decode(params.get(OAuth2Parameter.STATE), "UTF-8");
|
||||
String redirectUri = URLDecoder.decode(params.get(OAuth2Parameter.REDIRECT_URI), "UTF-8");
|
||||
|
||||
String authorizationResponseUri =
|
||||
UriComponentsBuilder.fromHttpUrl(redirectUri)
|
||||
.queryParam(OAuth2Parameter.CODE, code)
|
||||
.queryParam(OAuth2Parameter.STATE, state)
|
||||
.build().encode().toUriString();
|
||||
|
||||
page = this.webClient.getPage(new URL(authorizationResponseUri));
|
||||
this.assertUserInfoPage(page);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestAuthorizationCodeGrantWhenNoMatchingAuthorizationRequestThenDisplayLoginPageWithError() throws Exception {
|
||||
HtmlPage page = this.webClient.getPage("/");
|
||||
URL loginPageUrl = page.getBaseURL();
|
||||
URL loginErrorPageUrl = new URL(loginPageUrl.toString() + "?error");
|
||||
|
||||
String code = "auth-code";
|
||||
String state = "state";
|
||||
String redirectUri = AUTHORIZE_BASE_URL + "/" + this.googleClientRegistration.getClientAlias();
|
||||
|
||||
String authorizationResponseUri =
|
||||
UriComponentsBuilder.fromHttpUrl(redirectUri)
|
||||
.queryParam(OAuth2Parameter.CODE, code)
|
||||
.queryParam(OAuth2Parameter.STATE, state)
|
||||
.build().encode().toUriString();
|
||||
|
||||
// Clear session cookie will ensure the 'session-saved'
|
||||
// Authorization Request (from previous request) is not found
|
||||
this.webClient.getCookieManager().clearCookies();
|
||||
|
||||
page = this.webClient.getPage(new URL(authorizationResponseUri));
|
||||
assertThat(page.getBaseURL()).isEqualTo(loginErrorPageUrl);
|
||||
|
||||
HtmlElement errorElement = page.getBody().getFirstByXPath("p");
|
||||
assertThat(errorElement).isNotNull();
|
||||
assertThat(errorElement.asText()).contains("authorization_request_not_found");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestAuthorizationCodeGrantWhenInvalidStateParamThenDisplayLoginPageWithError() throws Exception {
|
||||
HtmlPage page = this.webClient.getPage("/");
|
||||
URL loginPageUrl = page.getBaseURL();
|
||||
URL loginErrorPageUrl = new URL(loginPageUrl.toString() + "?error");
|
||||
|
||||
HtmlAnchor clientAnchorElement = this.getClientAnchorElement(page, this.googleClientRegistration);
|
||||
assertThat(clientAnchorElement).isNotNull();
|
||||
this.followLinkDisableRedirects(clientAnchorElement);
|
||||
|
||||
String code = "auth-code";
|
||||
String state = "invalid-state";
|
||||
String redirectUri = AUTHORIZE_BASE_URL + "/" + this.githubClientRegistration.getClientAlias();
|
||||
|
||||
String authorizationResponseUri =
|
||||
UriComponentsBuilder.fromHttpUrl(redirectUri)
|
||||
.queryParam(OAuth2Parameter.CODE, code)
|
||||
.queryParam(OAuth2Parameter.STATE, state)
|
||||
.build().encode().toUriString();
|
||||
|
||||
page = this.webClient.getPage(new URL(authorizationResponseUri));
|
||||
assertThat(page.getBaseURL()).isEqualTo(loginErrorPageUrl);
|
||||
|
||||
HtmlElement errorElement = page.getBody().getFirstByXPath("p");
|
||||
assertThat(errorElement).isNotNull();
|
||||
assertThat(errorElement.asText()).contains("invalid_state_parameter");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestAuthorizationCodeGrantWhenInvalidRedirectUriThenDisplayLoginPageWithError() throws Exception {
|
||||
HtmlPage page = this.webClient.getPage("/");
|
||||
URL loginPageUrl = page.getBaseURL();
|
||||
URL loginErrorPageUrl = new URL(loginPageUrl.toString() + "?error");
|
||||
|
||||
HtmlAnchor clientAnchorElement = this.getClientAnchorElement(page, this.googleClientRegistration);
|
||||
assertThat(clientAnchorElement).isNotNull();
|
||||
|
||||
WebResponse response = this.followLinkDisableRedirects(clientAnchorElement);
|
||||
|
||||
UriComponents authorizeRequestUriComponents = UriComponentsBuilder.fromUri(
|
||||
URI.create(response.getResponseHeaderValue("Location"))).build();
|
||||
|
||||
Map<String, String> params = authorizeRequestUriComponents.getQueryParams().toSingleValueMap();
|
||||
String code = "auth-code";
|
||||
String state = URLDecoder.decode(params.get(OAuth2Parameter.STATE), "UTF-8");
|
||||
String redirectUri = URLDecoder.decode(params.get(OAuth2Parameter.REDIRECT_URI), "UTF-8");
|
||||
redirectUri += "-invalid";
|
||||
|
||||
String authorizationResponseUri =
|
||||
UriComponentsBuilder.fromHttpUrl(redirectUri)
|
||||
.queryParam(OAuth2Parameter.CODE, code)
|
||||
.queryParam(OAuth2Parameter.STATE, state)
|
||||
.build().encode().toUriString();
|
||||
|
||||
page = this.webClient.getPage(new URL(authorizationResponseUri));
|
||||
assertThat(page.getBaseURL()).isEqualTo(loginErrorPageUrl);
|
||||
|
||||
HtmlElement errorElement = page.getBody().getFirstByXPath("p");
|
||||
assertThat(errorElement).isNotNull();
|
||||
assertThat(errorElement.asText()).contains("invalid_redirect_uri_parameter");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestAuthorizationCodeGrantWhenStandardErrorCodeResponseThenDisplayLoginPageWithError() throws Exception {
|
||||
HtmlPage page = this.webClient.getPage("/");
|
||||
URL loginPageUrl = page.getBaseURL();
|
||||
URL loginErrorPageUrl = new URL(loginPageUrl.toString() + "?error");
|
||||
|
||||
String error = OAuth2Error.INVALID_CLIENT_ERROR_CODE;
|
||||
String state = "state";
|
||||
String redirectUri = AUTHORIZE_BASE_URL + "/" + this.githubClientRegistration.getClientAlias();
|
||||
|
||||
String authorizationResponseUri =
|
||||
UriComponentsBuilder.fromHttpUrl(redirectUri)
|
||||
.queryParam(OAuth2Parameter.ERROR, error)
|
||||
.queryParam(OAuth2Parameter.STATE, state)
|
||||
.build().encode().toUriString();
|
||||
|
||||
page = this.webClient.getPage(new URL(authorizationResponseUri));
|
||||
assertThat(page.getBaseURL()).isEqualTo(loginErrorPageUrl);
|
||||
|
||||
HtmlElement errorElement = page.getBody().getFirstByXPath("p");
|
||||
assertThat(errorElement).isNotNull();
|
||||
assertThat(errorElement.asText()).contains(error);
|
||||
}
|
||||
|
||||
private void assertLoginPage(HtmlPage page) throws Exception {
|
||||
assertThat(page.getTitleText()).isEqualTo("Login Page");
|
||||
|
||||
int expectedClients = 4;
|
||||
|
||||
List<HtmlAnchor> clientAnchorElements = page.getAnchors();
|
||||
assertThat(clientAnchorElements.size()).isEqualTo(expectedClients);
|
||||
|
||||
String baseAuthorizeUri = AUTHORIZATION_BASE_URI + "/";
|
||||
String googleClientAuthorizeUri = baseAuthorizeUri + this.googleClientRegistration.getClientAlias();
|
||||
String githubClientAuthorizeUri = baseAuthorizeUri + this.githubClientRegistration.getClientAlias();
|
||||
String facebookClientAuthorizeUri = baseAuthorizeUri + this.facebookClientRegistration.getClientAlias();
|
||||
String oktaClientAuthorizeUri = baseAuthorizeUri + this.oktaClientRegistration.getClientAlias();
|
||||
|
||||
for (int i=0; i<expectedClients; i++) {
|
||||
assertThat(clientAnchorElements.get(i).getAttribute("href")).isIn(
|
||||
googleClientAuthorizeUri, githubClientAuthorizeUri,
|
||||
facebookClientAuthorizeUri, oktaClientAuthorizeUri);
|
||||
assertThat(clientAnchorElements.get(i).asText()).isIn(
|
||||
this.googleClientRegistration.getClientName(),
|
||||
this.githubClientRegistration.getClientName(),
|
||||
this.facebookClientRegistration.getClientName(),
|
||||
this.oktaClientRegistration.getClientName());
|
||||
}
|
||||
}
|
||||
|
||||
private void assertUserInfoPage(HtmlPage page) throws Exception {
|
||||
assertThat(page.getTitleText()).isEqualTo("Spring Security - OAuth2 User Info");
|
||||
|
||||
DomNodeList<HtmlElement> divElements = page.getBody().getElementsByTagName("div");
|
||||
assertThat(divElements.get(1).asText()).contains("User: joeg@springsecurity.io");
|
||||
assertThat(divElements.get(4).asText()).contains("Name: joeg@springsecurity.io");
|
||||
}
|
||||
|
||||
private HtmlAnchor getClientAnchorElement(HtmlPage page, ClientRegistration clientRegistration) {
|
||||
Optional<HtmlAnchor> clientAnchorElement = page.getAnchors().stream()
|
||||
.filter(e -> e.asText().equals(clientRegistration.getClientName())).findFirst();
|
||||
|
||||
return (clientAnchorElement.isPresent() ? clientAnchorElement.get() : null);
|
||||
}
|
||||
|
||||
private WebResponse followLinkDisableRedirects(HtmlAnchor anchorElement) throws Exception {
|
||||
WebResponse response = null;
|
||||
try {
|
||||
// Disable the automatic redirection (which will trigger
|
||||
// an exception) so that we can capture the response
|
||||
this.webClient.getOptions().setRedirectEnabled(false);
|
||||
anchorElement.click();
|
||||
} catch (FailingHttpStatusCodeException ex) {
|
||||
response = ex.getResponse();
|
||||
this.webClient.getOptions().setRedirectEnabled(true);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
public static class SecurityTestConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
// @formatter:off
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeRequests()
|
||||
.anyRequest().authenticated()
|
||||
.and()
|
||||
.oauth2Login()
|
||||
.authorizationCodeTokenExchanger(this.mockAuthorizationCodeTokenExchanger())
|
||||
.userInfoEndpoint()
|
||||
.userInfoService(this.mockUserInfoService());
|
||||
}
|
||||
// @formatter:on
|
||||
|
||||
private AuthorizationGrantTokenExchanger<AuthorizationCodeAuthenticationToken> mockAuthorizationCodeTokenExchanger() {
|
||||
TokenResponseAttributes tokenResponse = TokenResponseAttributes.withToken("access-token-1234")
|
||||
.tokenType(AccessToken.TokenType.BEARER)
|
||||
.expiresIn(60 * 1000)
|
||||
.scopes(Collections.singleton("openid"))
|
||||
.build();
|
||||
|
||||
AuthorizationGrantTokenExchanger mock = mock(AuthorizationGrantTokenExchanger.class);
|
||||
when(mock.exchange(any())).thenReturn(tokenResponse);
|
||||
return mock;
|
||||
}
|
||||
|
||||
private OAuth2UserService mockUserInfoService() {
|
||||
Map<String, Object> attributes = new HashMap<>();
|
||||
attributes.put("id", "joeg");
|
||||
attributes.put("first-name", "Joe");
|
||||
attributes.put("last-name", "Grandja");
|
||||
attributes.put("email", "joeg@springsecurity.io");
|
||||
|
||||
DefaultOAuth2User user = new DefaultOAuth2User(attributes, "email");
|
||||
|
||||
OAuth2UserService mock = mock(OAuth2UserService.class);
|
||||
when(mock.loadUser(any())).thenReturn(user);
|
||||
return mock;
|
||||
}
|
||||
}
|
||||
|
||||
@SpringBootConfiguration
|
||||
@EnableAutoConfiguration
|
||||
@ComponentScan(basePackages = "org.springframework.security.samples.web")
|
||||
public static class SpringBootApplicationTestConfig {
|
||||
}
|
||||
}
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Copyright 2012-2017 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
|
||||
*
|
||||
* http://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 org.springframework.boot.autoconfigure.security.oauth2.client;
|
||||
|
||||
import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||
import org.springframework.boot.autoconfigure.condition.*;
|
||||
import org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration;
|
||||
import org.springframework.boot.bind.PropertySourcesBinder;
|
||||
import org.springframework.boot.bind.RelaxedPropertyResolver;
|
||||
import org.springframework.context.annotation.*;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.env.MutablePropertySources;
|
||||
import org.springframework.core.env.PropertiesPropertySource;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.type.AnnotatedTypeMetadata;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationProperties;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author Joe Grandja
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnWebApplication
|
||||
@ConditionalOnClass(ClientRegistrationRepository.class)
|
||||
@ConditionalOnMissingBean(ClientRegistrationRepository.class)
|
||||
@AutoConfigureBefore(SecurityAutoConfiguration.class)
|
||||
public class ClientRegistrationAutoConfiguration {
|
||||
private static final String CLIENT_ID_PROPERTY = "client-id";
|
||||
private static final String CLIENTS_DEFAULTS_RESOURCE = "META-INF/oauth2-clients-defaults.yml";
|
||||
static final String CLIENT_PROPERTY_PREFIX = "security.oauth2.client.";
|
||||
|
||||
@Configuration
|
||||
@Conditional(ClientPropertiesAvailableCondition.class)
|
||||
protected static class ClientRegistrationConfiguration {
|
||||
private final Environment environment;
|
||||
|
||||
protected ClientRegistrationConfiguration(Environment environment) {
|
||||
this.environment = environment;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ClientRegistrationRepository clientRegistrationRepository() {
|
||||
MutablePropertySources propertySources = ((ConfigurableEnvironment) this.environment).getPropertySources();
|
||||
Properties clientsDefaultProperties = this.getClientsDefaultProperties();
|
||||
if (clientsDefaultProperties != null) {
|
||||
propertySources.addLast(new PropertiesPropertySource("oauth2ClientsDefaults", clientsDefaultProperties));
|
||||
}
|
||||
PropertySourcesBinder binder = new PropertySourcesBinder(propertySources);
|
||||
RelaxedPropertyResolver resolver = new RelaxedPropertyResolver(this.environment, CLIENT_PROPERTY_PREFIX);
|
||||
|
||||
List<ClientRegistration> clientRegistrations = new ArrayList<>();
|
||||
|
||||
Set<String> clientPropertyKeys = resolveClientPropertyKeys(this.environment);
|
||||
for (String clientPropertyKey : clientPropertyKeys) {
|
||||
if (!resolver.containsProperty(clientPropertyKey + "." + CLIENT_ID_PROPERTY)) {
|
||||
continue;
|
||||
}
|
||||
ClientRegistrationProperties clientRegistrationProperties = new ClientRegistrationProperties();
|
||||
binder.bindTo(CLIENT_PROPERTY_PREFIX + clientPropertyKey, clientRegistrationProperties);
|
||||
ClientRegistration clientRegistration = new ClientRegistration.Builder(clientRegistrationProperties).build();
|
||||
clientRegistrations.add(clientRegistration);
|
||||
}
|
||||
|
||||
return new InMemoryClientRegistrationRepository(clientRegistrations);
|
||||
}
|
||||
|
||||
private Properties getClientsDefaultProperties() {
|
||||
ClassPathResource clientsDefaultsResource = new ClassPathResource(CLIENTS_DEFAULTS_RESOURCE);
|
||||
if (!clientsDefaultsResource.exists()) {
|
||||
return null;
|
||||
}
|
||||
YamlPropertiesFactoryBean yamlPropertiesFactory = new YamlPropertiesFactoryBean();
|
||||
yamlPropertiesFactory.setResources(clientsDefaultsResource);
|
||||
return yamlPropertiesFactory.getObject();
|
||||
}
|
||||
}
|
||||
|
||||
static Set<String> resolveClientPropertyKeys(Environment environment) {
|
||||
Set<String> clientPropertyKeys = new LinkedHashSet<>();
|
||||
RelaxedPropertyResolver resolver = new RelaxedPropertyResolver(environment, CLIENT_PROPERTY_PREFIX);
|
||||
resolver.getSubProperties("").keySet().forEach(key -> {
|
||||
int endIndex = key.indexOf('.');
|
||||
if (endIndex != -1) {
|
||||
clientPropertyKeys.add(key.substring(0, endIndex));
|
||||
}
|
||||
});
|
||||
return clientPropertyKeys;
|
||||
}
|
||||
|
||||
private static class ClientPropertiesAvailableCondition extends SpringBootCondition implements ConfigurationCondition {
|
||||
|
||||
@Override
|
||||
public ConfigurationCondition.ConfigurationPhase getConfigurationPhase() {
|
||||
return ConfigurationPhase.PARSE_CONFIGURATION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
|
||||
ConditionMessage.Builder message = ConditionMessage.forCondition("OAuth2 Client Properties");
|
||||
Set<String> clientPropertyKeys = resolveClientPropertyKeys(context.getEnvironment());
|
||||
if (!CollectionUtils.isEmpty(clientPropertyKeys)) {
|
||||
return ConditionOutcome.match(message.foundExactly("OAuth2 Client(s) -> " +
|
||||
clientPropertyKeys.stream().collect(Collectors.joining(", "))));
|
||||
}
|
||||
return ConditionOutcome.noMatch(message.notAvailable("OAuth2 Client(s)"));
|
||||
}
|
||||
}
|
||||
}
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright 2012-2017 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
|
||||
*
|
||||
* http://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 org.springframework.boot.autoconfigure.security.oauth2.client;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||
import org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
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.WebSecurityConfiguration;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
import org.springframework.security.config.annotation.web.configurers.oauth2.client.OAuth2LoginConfigurer;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.client.user.converter.AbstractOAuth2UserConverter;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.net.URI;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.springframework.boot.autoconfigure.security.oauth2.client.ClientRegistrationAutoConfiguration.CLIENT_PROPERTY_PREFIX;
|
||||
import static org.springframework.boot.autoconfigure.security.oauth2.client.ClientRegistrationAutoConfiguration.resolveClientPropertyKeys;
|
||||
|
||||
/**
|
||||
* @author Joe Grandja
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnWebApplication
|
||||
@ConditionalOnClass(EnableWebSecurity.class)
|
||||
@ConditionalOnMissingBean(WebSecurityConfiguration.class)
|
||||
@ConditionalOnBean(ClientRegistrationRepository.class)
|
||||
@AutoConfigureBefore(SecurityAutoConfiguration.class)
|
||||
@AutoConfigureAfter(ClientRegistrationAutoConfiguration.class)
|
||||
public class OAuth2LoginAutoConfiguration {
|
||||
private static final String USER_INFO_URI_PROPERTY = "user-info-uri";
|
||||
private static final String USER_INFO_CONVERTER_PROPERTY = "user-info-converter";
|
||||
private static final String USER_INFO_NAME_ATTR_KEY_PROPERTY = "user-info-name-attribute-key";
|
||||
|
||||
@EnableWebSecurity
|
||||
protected static class OAuth2LoginSecurityConfiguration extends WebSecurityConfigurerAdapter {
|
||||
private final Environment environment;
|
||||
|
||||
protected OAuth2LoginSecurityConfiguration(Environment environment) {
|
||||
this.environment = environment;
|
||||
}
|
||||
|
||||
// @formatter:off
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeRequests()
|
||||
.antMatchers("/favicon.ico").permitAll()
|
||||
.anyRequest().authenticated()
|
||||
.and()
|
||||
.oauth2Login();
|
||||
|
||||
this.registerUserInfoTypeConverters(http.oauth2Login());
|
||||
}
|
||||
// @formatter:on
|
||||
|
||||
private void registerUserInfoTypeConverters(OAuth2LoginConfigurer<HttpSecurity> oauth2LoginConfigurer) throws Exception {
|
||||
Set<String> clientPropertyKeys = resolveClientPropertyKeys(this.environment);
|
||||
for (String clientPropertyKey : clientPropertyKeys) {
|
||||
String fullClientPropertyKey = CLIENT_PROPERTY_PREFIX + clientPropertyKey + ".";
|
||||
String userInfoUriValue = this.environment.getProperty(fullClientPropertyKey + USER_INFO_URI_PROPERTY);
|
||||
String userInfoConverterTypeValue = this.environment.getProperty(fullClientPropertyKey + USER_INFO_CONVERTER_PROPERTY);
|
||||
if (userInfoUriValue != null && userInfoConverterTypeValue != null) {
|
||||
Class<? extends Converter> userInfoConverterType = ClassUtils.resolveClassName(
|
||||
userInfoConverterTypeValue, this.getClass().getClassLoader()).asSubclass(Converter.class);
|
||||
Converter<ClientHttpResponse, ? extends OAuth2User> userInfoConverter = null;
|
||||
if (AbstractOAuth2UserConverter.class.isAssignableFrom(userInfoConverterType)) {
|
||||
Constructor<? extends Converter> oauth2UserConverterConstructor = ClassUtils.getConstructorIfAvailable(userInfoConverterType, String.class);
|
||||
if (oauth2UserConverterConstructor != null) {
|
||||
String userInfoNameAttributeKey = this.environment.getProperty(fullClientPropertyKey + USER_INFO_NAME_ATTR_KEY_PROPERTY);
|
||||
userInfoConverter = (Converter<ClientHttpResponse, ? extends OAuth2User>)oauth2UserConverterConstructor.newInstance(userInfoNameAttributeKey);
|
||||
}
|
||||
}
|
||||
if (userInfoConverter == null) {
|
||||
userInfoConverter = (Converter<ClientHttpResponse, ? extends OAuth2User>)userInfoConverterType.newInstance();
|
||||
}
|
||||
oauth2LoginConfigurer.userInfoEndpoint().userInfoTypeConverter(userInfoConverter, new URI(userInfoUriValue));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2012-2017 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
|
||||
*
|
||||
* http://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 org.springframework.security.samples;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
/**
|
||||
* @author Joe Grandja
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class OAuth2LoginApplication {
|
||||
|
||||
public OAuth2LoginApplication() {
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(OAuth2LoginApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright 2012-2017 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
|
||||
*
|
||||
* http://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 org.springframework.security.samples.user;
|
||||
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author Joe Grandja
|
||||
*/
|
||||
public class GitHubOAuth2User implements OAuth2User {
|
||||
private String id;
|
||||
private String name;
|
||||
private String login;
|
||||
private String email;
|
||||
|
||||
public GitHubOAuth2User() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getAttributes() {
|
||||
Map<String, Object> attributes = new HashMap<>();
|
||||
attributes.put("id", this.getId());
|
||||
attributes.put("name", this.getName());
|
||||
attributes.put("login", this.getLogin());
|
||||
attributes.put("email", this.getEmail());
|
||||
return attributes;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getLogin() {
|
||||
return this.login;
|
||||
}
|
||||
|
||||
public void setLogin(String login) {
|
||||
this.login = login;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return this.email;
|
||||
}
|
||||
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2012-2017 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
|
||||
*
|
||||
* http://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 org.springframework.security.samples.web;
|
||||
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
/**
|
||||
* @author Joe Grandja
|
||||
*/
|
||||
@Controller
|
||||
public class MainController {
|
||||
|
||||
@RequestMapping("/")
|
||||
public String index(Model model, @AuthenticationPrincipal OAuth2User user) {
|
||||
model.addAttribute("userName", user.getName());
|
||||
model.addAttribute("userAttributes", user.getAttributes());
|
||||
return "index";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
security:
|
||||
oauth2:
|
||||
client:
|
||||
google:
|
||||
client-authentication-method: header
|
||||
authorized-grant-type: authorization_code
|
||||
redirect-uri: "{scheme}://{serverName}:{serverPort}{baseAuthorizeUri}/{clientAlias}"
|
||||
scopes: openid, email, profile
|
||||
authorization-uri: "https://accounts.google.com/o/oauth2/auth"
|
||||
token-uri: "https://accounts.google.com/o/oauth2/token"
|
||||
user-info-uri: "https://www.googleapis.com/oauth2/v3/userinfo"
|
||||
user-info-converter: "org.springframework.security.oauth2.client.user.converter.UserInfoConverter"
|
||||
client-name: Google
|
||||
client-alias: google
|
||||
github:
|
||||
client-authentication-method: header
|
||||
authorized-grant-type: authorization_code
|
||||
redirect-uri: "{scheme}://{serverName}:{serverPort}{baseAuthorizeUri}/{clientAlias}"
|
||||
scopes: user
|
||||
authorization-uri: "https://github.com/login/oauth/authorize"
|
||||
token-uri: "https://github.com/login/oauth/access_token"
|
||||
user-info-uri: "https://api.github.com/user"
|
||||
user-info-converter: "org.springframework.security.oauth2.client.user.converter.OAuth2UserConverter"
|
||||
client-name: GitHub
|
||||
client-alias: github
|
||||
facebook:
|
||||
client-authentication-method: form
|
||||
authorized-grant-type: authorization_code
|
||||
redirect-uri: "{scheme}://{serverName}:{serverPort}{baseAuthorizeUri}/{clientAlias}"
|
||||
scopes: public_profile, email
|
||||
authorization-uri: "https://www.facebook.com/v2.8/dialog/oauth"
|
||||
token-uri: "https://graph.facebook.com/v2.8/oauth/access_token"
|
||||
user-info-uri: "https://graph.facebook.com/me"
|
||||
user-info-converter: "org.springframework.security.oauth2.client.user.converter.OAuth2UserConverter"
|
||||
client-name: Facebook
|
||||
client-alias: facebook
|
||||
okta:
|
||||
client-authentication-method: header
|
||||
authorized-grant-type: authorization_code
|
||||
redirect-uri: "{scheme}://{serverName}:{serverPort}{baseAuthorizeUri}/{clientAlias}"
|
||||
scopes: openid, email, profile
|
||||
user-info-converter: "org.springframework.security.oauth2.client.user.converter.UserInfoConverter"
|
||||
client-name: Okta
|
||||
client-alias: okta
|
||||
@@ -0,0 +1,4 @@
|
||||
# Spring Boot Auto Configurations
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
org.springframework.boot.autoconfigure.security.oauth2.client.ClientRegistrationAutoConfiguration,\
|
||||
org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2LoginAutoConfiguration
|
||||
@@ -0,0 +1,34 @@
|
||||
server:
|
||||
port: 8080
|
||||
|
||||
logging:
|
||||
level:
|
||||
root: INFO
|
||||
org.springframework.web: INFO
|
||||
org.springframework.security: INFO
|
||||
# org.springframework.boot.autoconfigure: DEBUG
|
||||
|
||||
spring:
|
||||
thymeleaf:
|
||||
cache: false
|
||||
|
||||
security:
|
||||
oauth2:
|
||||
client:
|
||||
google:
|
||||
client-id: your-app-client-id
|
||||
client-secret: your-app-client-secret
|
||||
github:
|
||||
client-id: your-app-client-id
|
||||
client-secret: your-app-client-secret
|
||||
user-info-name-attribute-key: "name"
|
||||
facebook:
|
||||
client-id: your-app-client-id
|
||||
client-secret: your-app-client-secret
|
||||
user-info-name-attribute-key: "name"
|
||||
okta:
|
||||
client-id: your-app-client-id
|
||||
client-secret: your-app-client-secret
|
||||
authorization-uri: https://your-subdomain.oktapreview.com/oauth2/v1/authorize
|
||||
token-uri: https://your-subdomain.oktapreview.com/oauth2/v1/token
|
||||
user-info-uri: https://your-subdomain.oktapreview.com/oauth2/v1/userinfo
|
||||
@@ -0,0 +1,33 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
|
||||
<head>
|
||||
<title>Spring Security - OAuth2 User Info</title>
|
||||
<meta charset="utf-8" />
|
||||
</head>
|
||||
<body>
|
||||
<div style="float: right" th:fragment="logout" sec:authorize="isAuthenticated()">
|
||||
<div style="float:left">
|
||||
<span style="font-weight:bold">User: </span><span sec:authentication="name"></span>
|
||||
</div>
|
||||
<div style="float:none"> </div>
|
||||
<div style="float:right">
|
||||
<form action="#" th:action="@{/logout}" method="post">
|
||||
<input type="submit" value="Logout" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<h1>OAuth2 User Info</h1>
|
||||
<div>
|
||||
<span style="font-weight:bold">Name: </span><span th:text="${userName}"></span>
|
||||
</div>
|
||||
<div> </div>
|
||||
<div>
|
||||
<span style="font-weight:bold">Attributes:</span>
|
||||
<ul>
|
||||
<li th:each="userAttribute : ${userAttributes}">
|
||||
<span style="font-weight:bold" th:text="${userAttribute.key}"></span>: <span th:text="${userAttribute.value}"></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user