81 changed files with 6485 additions and 50 deletions
@ -0,0 +1,123 @@
@@ -0,0 +1,123 @@
|
||||
/* |
||||
* 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.config.annotation.web.configurers.oauth2.client; |
||||
|
||||
import org.springframework.core.convert.converter.Converter; |
||||
import org.springframework.http.client.ClientHttpResponse; |
||||
import org.springframework.security.config.annotation.web.HttpSecurityBuilder; |
||||
import org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer; |
||||
import org.springframework.security.oauth2.client.authentication.AuthorizationCodeAuthenticationProcessingFilter; |
||||
import org.springframework.security.oauth2.client.authentication.AuthorizationCodeAuthenticationProvider; |
||||
import org.springframework.security.oauth2.client.authentication.AuthorizationCodeAuthenticationToken; |
||||
import org.springframework.security.oauth2.client.authentication.AuthorizationGrantTokenExchanger; |
||||
import org.springframework.security.oauth2.client.authentication.nimbus.NimbusAuthorizationCodeTokenExchanger; |
||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; |
||||
import org.springframework.security.oauth2.client.user.OAuth2UserService; |
||||
import org.springframework.security.oauth2.client.user.nimbus.NimbusOAuth2UserService; |
||||
import org.springframework.security.oauth2.core.user.OAuth2User; |
||||
import org.springframework.security.web.util.matcher.RequestMatcher; |
||||
import org.springframework.util.Assert; |
||||
|
||||
import java.net.URI; |
||||
import java.util.HashMap; |
||||
import java.util.Map; |
||||
|
||||
/** |
||||
* @author Joe Grandja |
||||
*/ |
||||
final class AuthorizationCodeAuthenticationFilterConfigurer<H extends HttpSecurityBuilder<H>> extends |
||||
AbstractAuthenticationFilterConfigurer<H, AuthorizationCodeAuthenticationFilterConfigurer<H>, AuthorizationCodeAuthenticationProcessingFilter> { |
||||
|
||||
private AuthorizationGrantTokenExchanger<AuthorizationCodeAuthenticationToken> authorizationCodeTokenExchanger; |
||||
private OAuth2UserService userInfoService; |
||||
private Map<URI, Converter<ClientHttpResponse, ? extends OAuth2User>> userInfoTypeConverters = new HashMap<>(); |
||||
|
||||
|
||||
AuthorizationCodeAuthenticationFilterConfigurer() { |
||||
super(new AuthorizationCodeAuthenticationProcessingFilter(), null); |
||||
} |
||||
|
||||
AuthorizationCodeAuthenticationFilterConfigurer<H> clientRegistrationRepository(ClientRegistrationRepository clientRegistrationRepository) { |
||||
Assert.notNull(clientRegistrationRepository, "clientRegistrationRepository cannot be null"); |
||||
Assert.notEmpty(clientRegistrationRepository.getRegistrations(), "clientRegistrationRepository cannot be empty"); |
||||
this.getBuilder().setSharedObject(ClientRegistrationRepository.class, clientRegistrationRepository); |
||||
return this; |
||||
} |
||||
|
||||
AuthorizationCodeAuthenticationFilterConfigurer<H> authorizationCodeTokenExchanger( |
||||
AuthorizationGrantTokenExchanger<AuthorizationCodeAuthenticationToken> authorizationCodeTokenExchanger) { |
||||
|
||||
Assert.notNull(authorizationCodeTokenExchanger, "authorizationCodeTokenExchanger cannot be null"); |
||||
this.authorizationCodeTokenExchanger = authorizationCodeTokenExchanger; |
||||
return this; |
||||
} |
||||
|
||||
AuthorizationCodeAuthenticationFilterConfigurer<H> userInfoService(OAuth2UserService userInfoService) { |
||||
Assert.notNull(userInfoService, "userInfoService cannot be null"); |
||||
this.userInfoService = userInfoService; |
||||
return this; |
||||
} |
||||
|
||||
AuthorizationCodeAuthenticationFilterConfigurer<H> userInfoTypeConverter(Converter<ClientHttpResponse, ? extends OAuth2User> userInfoConverter, URI userInfoUri) { |
||||
Assert.notNull(userInfoConverter, "userInfoConverter cannot be null"); |
||||
Assert.notNull(userInfoUri, "userInfoUri cannot be null"); |
||||
this.userInfoTypeConverters.put(userInfoUri, userInfoConverter); |
||||
return this; |
||||
} |
||||
|
||||
String getLoginUrl() { |
||||
return super.getLoginPage(); |
||||
} |
||||
|
||||
String getLoginFailureUrl() { |
||||
return super.getFailureUrl(); |
||||
} |
||||
|
||||
@Override |
||||
public void init(H http) throws Exception { |
||||
AuthorizationCodeAuthenticationProvider authenticationProvider = new AuthorizationCodeAuthenticationProvider( |
||||
this.getAuthorizationCodeTokenExchanger(), this.getUserInfoService()); |
||||
authenticationProvider = this.postProcess(authenticationProvider); |
||||
http.authenticationProvider(authenticationProvider); |
||||
super.init(http); |
||||
} |
||||
|
||||
@Override |
||||
public void configure(H http) throws Exception { |
||||
AuthorizationCodeAuthenticationProcessingFilter authFilter = this.getAuthenticationFilter(); |
||||
authFilter.setClientRegistrationRepository(OAuth2LoginConfigurer.getClientRegistrationRepository(this.getBuilder())); |
||||
super.configure(http); |
||||
} |
||||
|
||||
@Override |
||||
protected RequestMatcher createLoginProcessingUrlMatcher(String loginProcessingUrl) { |
||||
return this.getAuthenticationFilter().getAuthorizeRequestMatcher(); |
||||
} |
||||
|
||||
private AuthorizationGrantTokenExchanger<AuthorizationCodeAuthenticationToken> getAuthorizationCodeTokenExchanger() { |
||||
if (this.authorizationCodeTokenExchanger == null) { |
||||
this.authorizationCodeTokenExchanger = new NimbusAuthorizationCodeTokenExchanger(); |
||||
} |
||||
return this.authorizationCodeTokenExchanger; |
||||
} |
||||
|
||||
private OAuth2UserService getUserInfoService() { |
||||
if (this.userInfoService == null) { |
||||
this.userInfoService = new NimbusOAuth2UserService(this.userInfoTypeConverters); |
||||
} |
||||
return this.userInfoService; |
||||
} |
||||
} |
||||
@ -0,0 +1,61 @@
@@ -0,0 +1,61 @@
|
||||
/* |
||||
* 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.config.annotation.web.configurers.oauth2.client; |
||||
|
||||
import org.springframework.security.config.annotation.web.HttpSecurityBuilder; |
||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; |
||||
import org.springframework.security.oauth2.client.authentication.AuthorizationCodeRequestRedirectFilter; |
||||
import org.springframework.security.oauth2.client.authentication.AuthorizationRequestUriBuilder; |
||||
import org.springframework.security.oauth2.client.authentication.DefaultAuthorizationRequestUriBuilder; |
||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; |
||||
import org.springframework.util.Assert; |
||||
|
||||
/** |
||||
* @author Joe Grandja |
||||
*/ |
||||
final class AuthorizationCodeRequestRedirectFilterConfigurer<B extends HttpSecurityBuilder<B>> extends |
||||
AbstractHttpConfigurer<AuthorizationCodeRequestRedirectFilterConfigurer<B>, B> { |
||||
|
||||
private AuthorizationRequestUriBuilder authorizationRequestBuilder; |
||||
|
||||
AuthorizationCodeRequestRedirectFilterConfigurer<B> clientRegistrationRepository(ClientRegistrationRepository clientRegistrationRepository) { |
||||
Assert.notNull(clientRegistrationRepository, "clientRegistrationRepository cannot be null"); |
||||
Assert.notEmpty(clientRegistrationRepository.getRegistrations(), "clientRegistrationRepository cannot be empty"); |
||||
this.getBuilder().setSharedObject(ClientRegistrationRepository.class, clientRegistrationRepository); |
||||
return this; |
||||
} |
||||
|
||||
AuthorizationCodeRequestRedirectFilterConfigurer<B> authorizationRequestBuilder(AuthorizationRequestUriBuilder authorizationRequestBuilder) { |
||||
Assert.notNull(authorizationRequestBuilder, "authorizationRequestBuilder cannot be null"); |
||||
this.authorizationRequestBuilder = authorizationRequestBuilder; |
||||
return this; |
||||
} |
||||
|
||||
@Override |
||||
public void configure(B http) throws Exception { |
||||
AuthorizationCodeRequestRedirectFilter filter = new AuthorizationCodeRequestRedirectFilter( |
||||
OAuth2LoginConfigurer.getClientRegistrationRepository(this.getBuilder()), |
||||
this.getAuthorizationRequestBuilder()); |
||||
http.addFilter(this.postProcess(filter)); |
||||
} |
||||
|
||||
private AuthorizationRequestUriBuilder getAuthorizationRequestBuilder() { |
||||
if (this.authorizationRequestBuilder == null) { |
||||
this.authorizationRequestBuilder = new DefaultAuthorizationRequestUriBuilder(); |
||||
} |
||||
return this.authorizationRequestBuilder; |
||||
} |
||||
} |
||||
@ -0,0 +1,154 @@
@@ -0,0 +1,154 @@
|
||||
/* |
||||
* 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.config.annotation.web.configurers.oauth2.client; |
||||
|
||||
import org.springframework.context.ApplicationContext; |
||||
import org.springframework.core.convert.converter.Converter; |
||||
import org.springframework.http.client.ClientHttpResponse; |
||||
import org.springframework.security.config.annotation.web.HttpSecurityBuilder; |
||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; |
||||
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.authentication.AuthorizationRequestUriBuilder; |
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration; |
||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; |
||||
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository; |
||||
import org.springframework.security.oauth2.client.user.OAuth2UserService; |
||||
import org.springframework.security.oauth2.core.user.OAuth2User; |
||||
import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter; |
||||
import org.springframework.util.Assert; |
||||
import org.springframework.util.CollectionUtils; |
||||
|
||||
import java.net.URI; |
||||
import java.util.Arrays; |
||||
import java.util.Map; |
||||
import java.util.stream.Collectors; |
||||
|
||||
/** |
||||
* @author Joe Grandja |
||||
*/ |
||||
public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>> extends |
||||
AbstractHttpConfigurer<OAuth2LoginConfigurer<B>, B> { |
||||
|
||||
private final AuthorizationCodeRequestRedirectFilterConfigurer<B> authorizationCodeRequestRedirectFilterConfigurer; |
||||
private final AuthorizationCodeAuthenticationFilterConfigurer<B> authorizationCodeAuthenticationFilterConfigurer; |
||||
private final UserInfoEndpointConfig userInfoEndpointConfig; |
||||
|
||||
public OAuth2LoginConfigurer() { |
||||
this.authorizationCodeRequestRedirectFilterConfigurer = new AuthorizationCodeRequestRedirectFilterConfigurer<>(); |
||||
this.authorizationCodeAuthenticationFilterConfigurer = new AuthorizationCodeAuthenticationFilterConfigurer<>(); |
||||
this.userInfoEndpointConfig = new UserInfoEndpointConfig(); |
||||
} |
||||
|
||||
public OAuth2LoginConfigurer<B> clients(ClientRegistration... clientRegistrations) { |
||||
Assert.notEmpty(clientRegistrations, "clientRegistrations cannot be empty"); |
||||
return clients(new InMemoryClientRegistrationRepository(Arrays.asList(clientRegistrations))); |
||||
} |
||||
|
||||
public OAuth2LoginConfigurer<B> clients(ClientRegistrationRepository clientRegistrationRepository) { |
||||
Assert.notNull(clientRegistrationRepository, "clientRegistrationRepository cannot be null"); |
||||
Assert.notEmpty(clientRegistrationRepository.getRegistrations(), "clientRegistrationRepository cannot be empty"); |
||||
this.getBuilder().setSharedObject(ClientRegistrationRepository.class, clientRegistrationRepository); |
||||
return this; |
||||
} |
||||
|
||||
public OAuth2LoginConfigurer<B> authorizationRequestBuilder(AuthorizationRequestUriBuilder authorizationRequestBuilder) { |
||||
Assert.notNull(authorizationRequestBuilder, "authorizationRequestBuilder cannot be null"); |
||||
this.authorizationCodeRequestRedirectFilterConfigurer.authorizationRequestBuilder(authorizationRequestBuilder); |
||||
return this; |
||||
} |
||||
|
||||
public OAuth2LoginConfigurer<B> authorizationCodeTokenExchanger( |
||||
AuthorizationGrantTokenExchanger<AuthorizationCodeAuthenticationToken> authorizationCodeTokenExchanger) { |
||||
|
||||
Assert.notNull(authorizationCodeTokenExchanger, "authorizationCodeTokenExchanger cannot be null"); |
||||
this.authorizationCodeAuthenticationFilterConfigurer.authorizationCodeTokenExchanger(authorizationCodeTokenExchanger); |
||||
return this; |
||||
} |
||||
|
||||
public UserInfoEndpointConfig userInfoEndpoint() { |
||||
return this.userInfoEndpointConfig; |
||||
} |
||||
|
||||
public class UserInfoEndpointConfig { |
||||
|
||||
private UserInfoEndpointConfig() { |
||||
} |
||||
|
||||
public OAuth2LoginConfigurer<B> userInfoService(OAuth2UserService userInfoService) { |
||||
Assert.notNull(userInfoService, "userInfoService cannot be null"); |
||||
OAuth2LoginConfigurer.this.authorizationCodeAuthenticationFilterConfigurer.userInfoService(userInfoService); |
||||
return this.and(); |
||||
} |
||||
|
||||
public OAuth2LoginConfigurer<B> userInfoTypeConverter(Converter<ClientHttpResponse, ? extends OAuth2User> userInfoConverter, URI userInfoUri) { |
||||
Assert.notNull(userInfoConverter, "userInfoConverter cannot be null"); |
||||
Assert.notNull(userInfoUri, "userInfoUri cannot be null"); |
||||
OAuth2LoginConfigurer.this.authorizationCodeAuthenticationFilterConfigurer.userInfoTypeConverter(userInfoConverter, userInfoUri); |
||||
return this.and(); |
||||
} |
||||
|
||||
public OAuth2LoginConfigurer<B> and() { |
||||
return OAuth2LoginConfigurer.this; |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void init(B http) throws Exception { |
||||
this.authorizationCodeRequestRedirectFilterConfigurer.setBuilder(http); |
||||
this.authorizationCodeAuthenticationFilterConfigurer.setBuilder(http); |
||||
|
||||
this.authorizationCodeRequestRedirectFilterConfigurer.init(http); |
||||
this.authorizationCodeAuthenticationFilterConfigurer.init(http); |
||||
this.initDefaultLoginFilter(http); |
||||
} |
||||
|
||||
@Override |
||||
public void configure(B http) throws Exception { |
||||
this.authorizationCodeRequestRedirectFilterConfigurer.configure(http); |
||||
this.authorizationCodeAuthenticationFilterConfigurer.configure(http); |
||||
} |
||||
|
||||
static <B extends HttpSecurityBuilder<B>> ClientRegistrationRepository getClientRegistrationRepository(B http) { |
||||
ClientRegistrationRepository clientRegistrationRepository = http.getSharedObject(ClientRegistrationRepository.class); |
||||
if (clientRegistrationRepository == null) { |
||||
clientRegistrationRepository = getDefaultClientRegistrationRepository(http); |
||||
http.setSharedObject(ClientRegistrationRepository.class, clientRegistrationRepository); |
||||
} |
||||
return clientRegistrationRepository; |
||||
} |
||||
|
||||
private static <B extends HttpSecurityBuilder<B>> ClientRegistrationRepository getDefaultClientRegistrationRepository(B http) { |
||||
return http.getSharedObject(ApplicationContext.class).getBean(ClientRegistrationRepository.class); |
||||
} |
||||
|
||||
private void initDefaultLoginFilter(B http) { |
||||
DefaultLoginPageGeneratingFilter loginPageGeneratingFilter = http.getSharedObject(DefaultLoginPageGeneratingFilter.class); |
||||
if (loginPageGeneratingFilter != null && !this.authorizationCodeAuthenticationFilterConfigurer.isCustomLoginPage()) { |
||||
ClientRegistrationRepository clientRegistrationRepository = getClientRegistrationRepository(this.getBuilder()); |
||||
if (!CollectionUtils.isEmpty(clientRegistrationRepository.getRegistrations())) { |
||||
Map<String, String> oauth2AuthenticationUrlToClientName = clientRegistrationRepository.getRegistrations().stream() |
||||
.collect(Collectors.toMap(e -> AuthorizationCodeRequestRedirectFilter.AUTHORIZATION_BASE_URI + "/" + e.getClientAlias(), |
||||
e -> e.getClientName())); |
||||
loginPageGeneratingFilter.setOauth2LoginEnabled(true); |
||||
loginPageGeneratingFilter.setOauth2AuthenticationUrlToClientName(oauth2AuthenticationUrlToClientName); |
||||
loginPageGeneratingFilter.setLoginPageUrl(this.authorizationCodeAuthenticationFilterConfigurer.getLoginUrl()); |
||||
loginPageGeneratingFilter.setFailureUrl(this.authorizationCodeAuthenticationFilterConfigurer.getLoginFailureUrl()); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,156 @@
@@ -0,0 +1,156 @@
|
||||
<?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-oauth2-client</artifactId> |
||||
<version>5.0.0.BUILD-SNAPSHOT</version> |
||||
<name>spring-security-oauth2-client</name> |
||||
<description>spring-security-oauth2-client</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> |
||||
</dependencies> |
||||
</dependencyManagement> |
||||
<dependencies> |
||||
<dependency> |
||||
<groupId>com.nimbusds</groupId> |
||||
<artifactId>oauth2-oidc-sdk</artifactId> |
||||
<version>5.21</version> |
||||
<scope>compile</scope> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>org.springframework.security</groupId> |
||||
<artifactId>spring-security-core</artifactId> |
||||
<version>5.0.0.BUILD-SNAPSHOT</version> |
||||
<scope>compile</scope> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>org.springframework.security</groupId> |
||||
<artifactId>spring-security-oauth2-core</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.springframework</groupId> |
||||
<artifactId>spring-core</artifactId> |
||||
<scope>compile</scope> |
||||
<exclusions> |
||||
<exclusion> |
||||
<artifactId>commons-logging</artifactId> |
||||
<groupId>commons-logging</groupId> |
||||
</exclusion> |
||||
</exclusions> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>org.springframework</groupId> |
||||
<artifactId>spring-web</artifactId> |
||||
<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>javax.servlet</groupId> |
||||
<artifactId>javax.servlet-api</artifactId> |
||||
<version>3.1.0</version> |
||||
<scope>provided</scope> |
||||
</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>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</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,12 @@
@@ -0,0 +1,12 @@
|
||||
apply plugin: 'io.spring.convention.spring-module' |
||||
|
||||
dependencies { |
||||
compile project(':spring-security-core') |
||||
compile project(':spring-security-oauth2-core') |
||||
compile project(':spring-security-web') |
||||
compile springCoreDependency |
||||
compile 'com.nimbusds:oauth2-oidc-sdk' |
||||
compile 'org.springframework:spring-web' |
||||
|
||||
provided 'javax.servlet:javax.servlet-api' |
||||
} |
||||
@ -0,0 +1,225 @@
@@ -0,0 +1,225 @@
|
||||
/* |
||||
* 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.oauth2.client.authentication; |
||||
|
||||
import org.springframework.security.authentication.AuthenticationManager; |
||||
import org.springframework.security.core.Authentication; |
||||
import org.springframework.security.core.AuthenticationException; |
||||
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.client.web.converter.AuthorizationCodeAuthorizationResponseAttributesConverter; |
||||
import org.springframework.security.oauth2.client.web.converter.ErrorResponseAttributesConverter; |
||||
import org.springframework.security.oauth2.core.AccessToken; |
||||
import org.springframework.security.oauth2.core.OAuth2Error; |
||||
import org.springframework.security.oauth2.core.endpoint.*; |
||||
import org.springframework.security.oauth2.core.user.OAuth2User; |
||||
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; |
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher; |
||||
import org.springframework.security.web.util.matcher.RequestMatcher; |
||||
import org.springframework.util.Assert; |
||||
import org.springframework.util.CollectionUtils; |
||||
|
||||
import javax.servlet.ServletException; |
||||
import javax.servlet.http.HttpServletRequest; |
||||
import javax.servlet.http.HttpServletResponse; |
||||
import java.io.IOException; |
||||
|
||||
import static org.springframework.security.oauth2.client.authentication.AuthorizationCodeRequestRedirectFilter.isDefaultRedirectUri; |
||||
|
||||
/** |
||||
* An implementation of an {@link AbstractAuthenticationProcessingFilter} that handles |
||||
* the processing of an <i>OAuth 2.0 Authorization Response</i> for the authorization code grant flow. |
||||
* |
||||
* <p> |
||||
* This <code>Filter</code> processes the <i>Authorization Response</i> in the following step sequence: |
||||
* |
||||
* <ol> |
||||
* <li> |
||||
* Assuming the resource owner (end-user) has granted access to the client, the authorization server will append the |
||||
* {@link OAuth2Parameter#CODE} and {@link OAuth2Parameter#STATE} (if provided in the <i>Authorization Request</i>) parameters |
||||
* to the {@link OAuth2Parameter#REDIRECT_URI} (provided in the <i>Authorization Request</i>) |
||||
* and redirect the end-user's user-agent back to this <code>Filter</code> (the client). |
||||
* </li> |
||||
* <li> |
||||
* This <code>Filter</code> will then create an {@link AuthorizationCodeAuthenticationToken} with |
||||
* the {@link OAuth2Parameter#CODE} received in the previous step and pass it to |
||||
* {@link AuthorizationCodeAuthenticationProvider#authenticate(Authentication)} (indirectly via {@link AuthenticationManager}). |
||||
* The {@link AuthorizationCodeAuthenticationProvider} will use an {@link AuthorizationGrantTokenExchanger} to make a request |
||||
* to the authorization server's <i>Token Endpoint</i> for exchanging the {@link OAuth2Parameter#CODE} for an {@link AccessToken}. |
||||
* </li> |
||||
* <li> |
||||
* Upon receiving the <i>Access Token Request</i>, the authorization server will authenticate the client, |
||||
* verify the {@link OAuth2Parameter#CODE}, and ensure that the {@link OAuth2Parameter#REDIRECT_URI} |
||||
* received matches the <code>URI</code> originally provided in the <i>Authorization Request</i>. |
||||
* If the request is valid, the authorization server will respond back with a {@link TokenResponseAttributes}. |
||||
* </li> |
||||
* <li> |
||||
* The {@link AuthorizationCodeAuthenticationProvider} will then create a new {@link OAuth2AuthenticationToken} |
||||
* associating the {@link AccessToken} from the {@link TokenResponseAttributes} and pass it to |
||||
* {@link OAuth2UserService#loadUser(OAuth2AuthenticationToken)}. The {@link OAuth2UserService} will make a request |
||||
* to the authorization server's <i>UserInfo Endpoint</i> (using the {@link AccessToken}) |
||||
* to obtain the end-user's (resource owner) attributes and return it in the form of an {@link OAuth2User}. |
||||
* </li> |
||||
* <li> |
||||
* The {@link AuthorizationCodeAuthenticationProvider} will create another new {@link OAuth2AuthenticationToken} |
||||
* but this time associating the {@link AccessToken} and {@link OAuth2User} returned from the {@link OAuth2UserService}. |
||||
* Finally, the {@link OAuth2AuthenticationToken} is returned to the {@link AuthenticationManager} |
||||
* and then back to this <code>Filter</code> at which point the session is considered <i>"authenticated"</i>. |
||||
* </li> |
||||
* </ol> |
||||
* |
||||
* <p> |
||||
* <b>NOTE:</b> Steps 4-5 are <b>not</b> part of the authorization code grant flow and instead are |
||||
* <i>"authentication flow"</i> steps that are required in order to authenticate the end-user with the system. |
||||
* |
||||
* @author Joe Grandja |
||||
* @since 5.0 |
||||
* @see AbstractAuthenticationProcessingFilter |
||||
* @see AuthorizationCodeAuthenticationToken |
||||
* @see AuthorizationCodeAuthenticationProvider |
||||
* @see AuthorizationGrantTokenExchanger |
||||
* @see AuthorizationCodeAuthorizationResponseAttributes |
||||
* @see AuthorizationRequestAttributes |
||||
* @see AuthorizationRequestRepository |
||||
* @see AuthorizationCodeRequestRedirectFilter |
||||
* @see ClientRegistration |
||||
* @see ClientRegistrationRepository |
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1">Section 4.1 Authorization Code Grant Flow</a> |
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.2">Section 4.1.2 Authorization Response</a> |
||||
*/ |
||||
public class AuthorizationCodeAuthenticationProcessingFilter extends AbstractAuthenticationProcessingFilter { |
||||
public static final String AUTHORIZE_BASE_URI = "/oauth2/authorize/code"; |
||||
private static final String CLIENT_ALIAS_VARIABLE_NAME = "clientAlias"; |
||||
private static final String AUTHORIZE_URI = AUTHORIZE_BASE_URI + "/{" + CLIENT_ALIAS_VARIABLE_NAME + "}"; |
||||
private static final String AUTHORIZATION_REQUEST_NOT_FOUND_ERROR_CODE = "authorization_request_not_found"; |
||||
private static final String INVALID_STATE_PARAMETER_ERROR_CODE = "invalid_state_parameter"; |
||||
private static final String INVALID_REDIRECT_URI_PARAMETER_ERROR_CODE = "invalid_redirect_uri_parameter"; |
||||
private final ErrorResponseAttributesConverter errorResponseConverter = new ErrorResponseAttributesConverter(); |
||||
private final AuthorizationCodeAuthorizationResponseAttributesConverter authorizationCodeResponseConverter = |
||||
new AuthorizationCodeAuthorizationResponseAttributesConverter(); |
||||
private final RequestMatcher authorizeRequestMatcher = new AntPathRequestMatcher(AUTHORIZE_URI); |
||||
private ClientRegistrationRepository clientRegistrationRepository; |
||||
private AuthorizationRequestRepository authorizationRequestRepository = new HttpSessionAuthorizationRequestRepository(); |
||||
|
||||
public AuthorizationCodeAuthenticationProcessingFilter() { |
||||
super(AUTHORIZE_URI); |
||||
} |
||||
|
||||
@Override |
||||
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) |
||||
throws AuthenticationException, IOException, ServletException { |
||||
|
||||
ErrorResponseAttributes authorizationError = this.errorResponseConverter.convert(request); |
||||
if (authorizationError != null) { |
||||
OAuth2Error oauth2Error = new OAuth2Error(authorizationError.getErrorCode(), |
||||
authorizationError.getDescription(), authorizationError.getUri()); |
||||
this.getAuthorizationRequestRepository().removeAuthorizationRequest(request); |
||||
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); |
||||
} |
||||
|
||||
AuthorizationRequestAttributes matchingAuthorizationRequest = this.resolveAuthorizationRequest(request); |
||||
|
||||
ClientRegistration clientRegistration = this.getClientRegistrationRepository().getRegistrationByClientId( |
||||
matchingAuthorizationRequest.getClientId()); |
||||
|
||||
// If clientRegistration.redirectUri is the default one (with Uri template variables)
|
||||
// then use matchingAuthorizationRequest.redirectUri instead
|
||||
if (isDefaultRedirectUri(clientRegistration)) { |
||||
clientRegistration = new ClientRegistrationBuilderWithUriOverrides( |
||||
clientRegistration, matchingAuthorizationRequest.getRedirectUri()).build(); |
||||
} |
||||
|
||||
AuthorizationCodeAuthorizationResponseAttributes authorizationCodeResponseAttributes = |
||||
this.authorizationCodeResponseConverter.convert(request); |
||||
|
||||
AuthorizationCodeAuthenticationToken authRequest = new AuthorizationCodeAuthenticationToken( |
||||
authorizationCodeResponseAttributes.getCode(), clientRegistration); |
||||
|
||||
authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request)); |
||||
|
||||
Authentication authenticated = this.getAuthenticationManager().authenticate(authRequest); |
||||
|
||||
return authenticated; |
||||
} |
||||
|
||||
public RequestMatcher getAuthorizeRequestMatcher() { |
||||
return this.authorizeRequestMatcher; |
||||
} |
||||
|
||||
protected ClientRegistrationRepository getClientRegistrationRepository() { |
||||
return this.clientRegistrationRepository; |
||||
} |
||||
|
||||
public final void setClientRegistrationRepository(ClientRegistrationRepository clientRegistrationRepository) { |
||||
Assert.notNull(clientRegistrationRepository, "clientRegistrationRepository cannot be null"); |
||||
Assert.notEmpty(clientRegistrationRepository.getRegistrations(), "clientRegistrationRepository cannot be empty"); |
||||
this.clientRegistrationRepository = clientRegistrationRepository; |
||||
} |
||||
|
||||
protected AuthorizationRequestRepository getAuthorizationRequestRepository() { |
||||
return this.authorizationRequestRepository; |
||||
} |
||||
|
||||
public final void setAuthorizationRequestRepository(AuthorizationRequestRepository authorizationRequestRepository) { |
||||
Assert.notNull(authorizationRequestRepository, "authorizationRequestRepository cannot be null"); |
||||
this.authorizationRequestRepository = authorizationRequestRepository; |
||||
} |
||||
|
||||
private AuthorizationRequestAttributes resolveAuthorizationRequest(HttpServletRequest request) { |
||||
AuthorizationRequestAttributes authorizationRequest = |
||||
this.getAuthorizationRequestRepository().loadAuthorizationRequest(request); |
||||
if (authorizationRequest == null) { |
||||
OAuth2Error oauth2Error = new OAuth2Error(AUTHORIZATION_REQUEST_NOT_FOUND_ERROR_CODE); |
||||
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); |
||||
} |
||||
this.getAuthorizationRequestRepository().removeAuthorizationRequest(request); |
||||
this.assertMatchingAuthorizationRequest(request, authorizationRequest); |
||||
return authorizationRequest; |
||||
} |
||||
|
||||
private void assertMatchingAuthorizationRequest(HttpServletRequest request, AuthorizationRequestAttributes authorizationRequest) { |
||||
String state = request.getParameter(OAuth2Parameter.STATE); |
||||
if (!authorizationRequest.getState().equals(state)) { |
||||
OAuth2Error oauth2Error = new OAuth2Error(INVALID_STATE_PARAMETER_ERROR_CODE); |
||||
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); |
||||
} |
||||
|
||||
if (!request.getRequestURL().toString().equals(authorizationRequest.getRedirectUri())) { |
||||
OAuth2Error oauth2Error = new OAuth2Error(INVALID_REDIRECT_URI_PARAMETER_ERROR_CODE); |
||||
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); |
||||
} |
||||
} |
||||
|
||||
private static class ClientRegistrationBuilderWithUriOverrides extends ClientRegistration.Builder { |
||||
|
||||
private ClientRegistrationBuilderWithUriOverrides(ClientRegistration clientRegistration, String redirectUri) { |
||||
super(clientRegistration.getClientId()); |
||||
this.clientSecret(clientRegistration.getClientSecret()); |
||||
this.clientAuthenticationMethod(clientRegistration.getClientAuthenticationMethod()); |
||||
this.authorizedGrantType(clientRegistration.getAuthorizedGrantType()); |
||||
this.redirectUri(redirectUri); |
||||
if (!CollectionUtils.isEmpty(clientRegistration.getScopes())) { |
||||
this.scopes(clientRegistration.getScopes().stream().toArray(String[]::new)); |
||||
} |
||||
this.authorizationUri(clientRegistration.getProviderDetails().getAuthorizationUri()); |
||||
this.tokenUri(clientRegistration.getProviderDetails().getTokenUri()); |
||||
this.userInfoUri(clientRegistration.getProviderDetails().getUserInfoUri()); |
||||
this.clientName(clientRegistration.getClientName()); |
||||
this.clientAlias(clientRegistration.getClientAlias()); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,120 @@
@@ -0,0 +1,120 @@
|
||||
/* |
||||
* 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.oauth2.client.authentication; |
||||
|
||||
import org.springframework.security.authentication.AuthenticationManager; |
||||
import org.springframework.security.authentication.AuthenticationProvider; |
||||
import org.springframework.security.core.Authentication; |
||||
import org.springframework.security.core.AuthenticationException; |
||||
import org.springframework.security.core.GrantedAuthority; |
||||
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; |
||||
import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper; |
||||
import org.springframework.security.oauth2.client.user.OAuth2UserService; |
||||
import org.springframework.security.oauth2.core.AccessToken; |
||||
import org.springframework.security.oauth2.core.endpoint.TokenResponseAttributes; |
||||
import org.springframework.security.oauth2.core.user.OAuth2User; |
||||
import org.springframework.util.Assert; |
||||
|
||||
import java.util.Collection; |
||||
|
||||
/** |
||||
* An implementation of an {@link AuthenticationProvider} that is responsible for authenticating |
||||
* an <i>authorization code</i> credential with the authorization server's <i>Token Endpoint</i> |
||||
* and if valid, exchanging it for an <i>access token</i> credential. |
||||
* Additionally, it will also obtain the end-user's (resource owner) attributes from the <i>UserInfo Endpoint</i> |
||||
* (using the <i>access token</i>) and create a <code>Principal</code> in the form of an {@link OAuth2User} |
||||
* associating it with the returned {@link OAuth2AuthenticationToken}. |
||||
* |
||||
* <p> |
||||
* The {@link AuthorizationCodeAuthenticationProvider} uses an {@link AuthorizationGrantTokenExchanger} |
||||
* to make a request to the authorization server's <i>Token Endpoint</i> |
||||
* to verify the {@link AuthorizationCodeAuthenticationToken#getAuthorizationCode()}. |
||||
* If the request is valid, the authorization server will respond back with a {@link TokenResponseAttributes}. |
||||
* |
||||
* <p> |
||||
* It will then create a {@link OAuth2AuthenticationToken} associating the {@link AccessToken} |
||||
* from the {@link TokenResponseAttributes} and pass it to {@link OAuth2UserService#loadUser(OAuth2AuthenticationToken)} |
||||
* to obtain the end-user's (resource owner) attributes in the form of an {@link OAuth2User}. |
||||
* |
||||
* <p> |
||||
* Finally, it will create another {@link OAuth2AuthenticationToken}, this time associating |
||||
* the {@link AccessToken} and {@link OAuth2User} and return it to the {@link AuthenticationManager}, |
||||
* at which point the {@link OAuth2AuthenticationToken} is considered <i>"authenticated"</i>. |
||||
* |
||||
* @author Joe Grandja |
||||
* @since 5.0 |
||||
* @see AuthorizationCodeAuthenticationToken |
||||
* @see AuthorizationGrantTokenExchanger |
||||
* @see TokenResponseAttributes |
||||
* @see AccessToken |
||||
* @see OAuth2UserService |
||||
* @see OAuth2User |
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1">Section 4.1 Authorization Code Grant Flow</a> |
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.3">Section 4.1.3 Access Token Request</a> |
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.4">Section 4.1.4 Access Token Response</a> |
||||
*/ |
||||
public class AuthorizationCodeAuthenticationProvider implements AuthenticationProvider { |
||||
private final AuthorizationGrantTokenExchanger<AuthorizationCodeAuthenticationToken> authorizationCodeTokenExchanger; |
||||
private final OAuth2UserService userInfoService; |
||||
private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper(); |
||||
|
||||
public AuthorizationCodeAuthenticationProvider( |
||||
AuthorizationGrantTokenExchanger<AuthorizationCodeAuthenticationToken> authorizationCodeTokenExchanger, |
||||
OAuth2UserService userInfoService) { |
||||
|
||||
Assert.notNull(authorizationCodeTokenExchanger, "authorizationCodeTokenExchanger cannot be null"); |
||||
Assert.notNull(userInfoService, "userInfoService cannot be null"); |
||||
this.authorizationCodeTokenExchanger = authorizationCodeTokenExchanger; |
||||
this.userInfoService = userInfoService; |
||||
} |
||||
|
||||
@Override |
||||
public Authentication authenticate(Authentication authentication) throws AuthenticationException { |
||||
AuthorizationCodeAuthenticationToken authorizationCodeAuthentication = |
||||
(AuthorizationCodeAuthenticationToken) authentication; |
||||
|
||||
TokenResponseAttributes tokenResponse = |
||||
this.authorizationCodeTokenExchanger.exchange(authorizationCodeAuthentication); |
||||
|
||||
AccessToken accessToken = new AccessToken(tokenResponse.getTokenType(), |
||||
tokenResponse.getTokenValue(), tokenResponse.getIssuedAt(), |
||||
tokenResponse.getExpiresAt(), tokenResponse.getScopes()); |
||||
OAuth2AuthenticationToken accessTokenAuthentication = new OAuth2AuthenticationToken( |
||||
authorizationCodeAuthentication.getClientRegistration(), accessToken); |
||||
accessTokenAuthentication.setDetails(authorizationCodeAuthentication.getDetails()); |
||||
|
||||
OAuth2User user = this.userInfoService.loadUser(accessTokenAuthentication); |
||||
|
||||
Collection<? extends GrantedAuthority> authorities = |
||||
this.authoritiesMapper.mapAuthorities(user.getAuthorities()); |
||||
|
||||
OAuth2AuthenticationToken authenticationResult = new OAuth2AuthenticationToken(user, authorities, |
||||
accessTokenAuthentication.getClientRegistration(), accessTokenAuthentication.getAccessToken()); |
||||
authenticationResult.setDetails(accessTokenAuthentication.getDetails()); |
||||
|
||||
return authenticationResult; |
||||
} |
||||
|
||||
@Override |
||||
public boolean supports(Class<?> authentication) { |
||||
return AuthorizationCodeAuthenticationToken.class.isAssignableFrom(authentication); |
||||
} |
||||
|
||||
public final void setAuthoritiesMapper(GrantedAuthoritiesMapper authoritiesMapper) { |
||||
Assert.notNull(authoritiesMapper, "authoritiesMapper cannot be null"); |
||||
this.authoritiesMapper = authoritiesMapper; |
||||
} |
||||
} |
||||
@ -0,0 +1,63 @@
@@ -0,0 +1,63 @@
|
||||
/* |
||||
* 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.oauth2.client.authentication; |
||||
|
||||
import org.springframework.security.core.authority.AuthorityUtils; |
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration; |
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType; |
||||
import org.springframework.util.Assert; |
||||
|
||||
/** |
||||
* An implementation of an {@link AuthorizationGrantAuthenticationToken} that holds |
||||
* an <i>authorization code grant</i> credential for a specific client identified in {@link #getClientRegistration()}. |
||||
* |
||||
* @author Joe Grandja |
||||
* @since 5.0 |
||||
* @see AuthorizationGrantAuthenticationToken |
||||
* @see ClientRegistration |
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-1.3.1">Section 1.3.1 Authorization Code Grant</a> |
||||
*/ |
||||
public class AuthorizationCodeAuthenticationToken extends AuthorizationGrantAuthenticationToken { |
||||
private final String authorizationCode; |
||||
private final ClientRegistration clientRegistration; |
||||
|
||||
public AuthorizationCodeAuthenticationToken(String authorizationCode, ClientRegistration clientRegistration) { |
||||
super(AuthorizationGrantType.AUTHORIZATION_CODE, AuthorityUtils.NO_AUTHORITIES); |
||||
Assert.hasText(authorizationCode, "authorizationCode cannot be empty"); |
||||
Assert.notNull(clientRegistration, "clientRegistration cannot be null"); |
||||
this.authorizationCode = authorizationCode; |
||||
this.clientRegistration = clientRegistration; |
||||
this.setAuthenticated(false); |
||||
} |
||||
|
||||
@Override |
||||
public Object getPrincipal() { |
||||
return null; |
||||
} |
||||
|
||||
@Override |
||||
public Object getCredentials() { |
||||
return this.getAuthorizationCode(); |
||||
} |
||||
|
||||
public String getAuthorizationCode() { |
||||
return this.authorizationCode; |
||||
} |
||||
|
||||
public ClientRegistration getClientRegistration() { |
||||
return this.clientRegistration; |
||||
} |
||||
} |
||||
@ -0,0 +1,159 @@
@@ -0,0 +1,159 @@
|
||||
/* |
||||
* 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.oauth2.client.authentication; |
||||
|
||||
import org.springframework.security.crypto.keygen.StringKeyGenerator; |
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration; |
||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; |
||||
import org.springframework.security.oauth2.core.endpoint.AuthorizationRequestAttributes; |
||||
import org.springframework.security.web.DefaultRedirectStrategy; |
||||
import org.springframework.security.web.RedirectStrategy; |
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher; |
||||
import org.springframework.util.Assert; |
||||
import org.springframework.web.filter.OncePerRequestFilter; |
||||
import org.springframework.web.util.UriComponentsBuilder; |
||||
|
||||
import javax.servlet.FilterChain; |
||||
import javax.servlet.ServletException; |
||||
import javax.servlet.http.HttpServletRequest; |
||||
import javax.servlet.http.HttpServletResponse; |
||||
import java.io.IOException; |
||||
import java.net.URI; |
||||
|
||||
import static org.springframework.security.oauth2.client.authentication.AuthorizationCodeAuthenticationProcessingFilter.AUTHORIZE_BASE_URI; |
||||
|
||||
/** |
||||
* This <code>Filter</code> initiates the authorization code grant flow by redirecting |
||||
* the end-user's user-agent to the authorization server's <i>Authorization Endpoint</i>. |
||||
* |
||||
* <p> |
||||
* It uses an {@link AuthorizationRequestUriBuilder} to build the <i>OAuth 2.0 Authorization Request</i>, |
||||
* which is used as the redirect <code>URI</code> to the <i>Authorization Endpoint</i>. |
||||
* The redirect <code>URI</code> will include the client identifier, requested scope(s), state, response type, and a redirection URI |
||||
* which the authorization server will send the user-agent back to (handled by {@link AuthorizationCodeAuthenticationProcessingFilter}) |
||||
* once access is granted (or denied) by the end-user (resource owner). |
||||
* |
||||
* @author Joe Grandja |
||||
* @since 5.0 |
||||
* @see AuthorizationRequestAttributes |
||||
* @see AuthorizationRequestRepository |
||||
* @see AuthorizationRequestUriBuilder |
||||
* @see ClientRegistration |
||||
* @see ClientRegistrationRepository |
||||
* @see AuthorizationCodeAuthenticationProcessingFilter |
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1">Section 4.1 Authorization Code Grant Flow</a> |
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.1">Section 4.1.1 Authorization Request</a> |
||||
*/ |
||||
public class AuthorizationCodeRequestRedirectFilter extends OncePerRequestFilter { |
||||
public static final String AUTHORIZATION_BASE_URI = "/oauth2/authorization/code"; |
||||
private static final String CLIENT_ALIAS_VARIABLE_NAME = "clientAlias"; |
||||
private static final String AUTHORIZATION_URI = AUTHORIZATION_BASE_URI + "/{" + CLIENT_ALIAS_VARIABLE_NAME + "}"; |
||||
private static final String DEFAULT_REDIRECT_URI_TEMPLATE = "{scheme}://{serverName}:{serverPort}{baseAuthorizeUri}/{clientAlias}"; |
||||
private final AntPathRequestMatcher authorizationRequestMatcher; |
||||
private final ClientRegistrationRepository clientRegistrationRepository; |
||||
private final AuthorizationRequestUriBuilder authorizationUriBuilder; |
||||
private final RedirectStrategy authorizationRedirectStrategy = new DefaultRedirectStrategy(); |
||||
private final StringKeyGenerator stateGenerator = new DefaultStateGenerator(); |
||||
private AuthorizationRequestRepository authorizationRequestRepository = new HttpSessionAuthorizationRequestRepository(); |
||||
|
||||
public AuthorizationCodeRequestRedirectFilter(ClientRegistrationRepository clientRegistrationRepository, |
||||
AuthorizationRequestUriBuilder authorizationUriBuilder) { |
||||
|
||||
Assert.notNull(clientRegistrationRepository, "clientRegistrationRepository cannot be null"); |
||||
Assert.notNull(authorizationUriBuilder, "authorizationUriBuilder cannot be null"); |
||||
this.authorizationRequestMatcher = new AntPathRequestMatcher(AUTHORIZATION_URI); |
||||
this.clientRegistrationRepository = clientRegistrationRepository; |
||||
this.authorizationUriBuilder = authorizationUriBuilder; |
||||
} |
||||
|
||||
public final void setAuthorizationRequestRepository(AuthorizationRequestRepository authorizationRequestRepository) { |
||||
Assert.notNull(authorizationRequestRepository, "authorizationRequestRepository cannot be null"); |
||||
this.authorizationRequestRepository = authorizationRequestRepository; |
||||
} |
||||
|
||||
@Override |
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) |
||||
throws ServletException, IOException { |
||||
|
||||
if (this.requiresAuthorization(request, response)) { |
||||
try { |
||||
this.sendRedirectForAuthorization(request, response); |
||||
} catch (Exception failed) { |
||||
this.unsuccessfulAuthorization(request, response, failed); |
||||
} |
||||
return; |
||||
} |
||||
|
||||
filterChain.doFilter(request, response); |
||||
} |
||||
|
||||
protected boolean requiresAuthorization(HttpServletRequest request, HttpServletResponse response) { |
||||
return this.authorizationRequestMatcher.matches(request); |
||||
} |
||||
|
||||
protected void sendRedirectForAuthorization(HttpServletRequest request, HttpServletResponse response) |
||||
throws IOException, ServletException { |
||||
|
||||
String clientAlias = this.authorizationRequestMatcher |
||||
.extractUriTemplateVariables(request).get(CLIENT_ALIAS_VARIABLE_NAME); |
||||
ClientRegistration clientRegistration = this.clientRegistrationRepository.getRegistrationByClientAlias(clientAlias); |
||||
if (clientRegistration == null) { |
||||
throw new IllegalArgumentException("Invalid Client Identifier (Alias): " + clientAlias); |
||||
} |
||||
|
||||
String redirectUriStr; |
||||
if (isDefaultRedirectUri(clientRegistration)) { |
||||
redirectUriStr = this.expandDefaultRedirectUri(request, clientRegistration); |
||||
} else { |
||||
redirectUriStr = clientRegistration.getRedirectUri(); |
||||
} |
||||
|
||||
AuthorizationRequestAttributes authorizationRequestAttributes = |
||||
AuthorizationRequestAttributes.withAuthorizationCode() |
||||
.clientId(clientRegistration.getClientId()) |
||||
.authorizeUri(clientRegistration.getProviderDetails().getAuthorizationUri()) |
||||
.redirectUri(redirectUriStr) |
||||
.scopes(clientRegistration.getScopes()) |
||||
.state(this.stateGenerator.generateKey()) |
||||
.build(); |
||||
|
||||
this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequestAttributes, request); |
||||
|
||||
URI redirectUri = this.authorizationUriBuilder.build(authorizationRequestAttributes); |
||||
this.authorizationRedirectStrategy.sendRedirect(request, response, redirectUri.toString()); |
||||
} |
||||
|
||||
protected void unsuccessfulAuthorization(HttpServletRequest request, HttpServletResponse response, |
||||
Exception failed) throws IOException, ServletException { |
||||
|
||||
if (logger.isDebugEnabled()) { |
||||
logger.debug("Authorization Request failed: " + failed.toString(), failed); |
||||
} |
||||
response.sendError(HttpServletResponse.SC_BAD_REQUEST, failed.getMessage()); |
||||
} |
||||
|
||||
static boolean isDefaultRedirectUri(ClientRegistration clientRegistration) { |
||||
return DEFAULT_REDIRECT_URI_TEMPLATE.equals(clientRegistration.getRedirectUri()); |
||||
} |
||||
|
||||
private String expandDefaultRedirectUri(HttpServletRequest request, ClientRegistration clientRegistration) { |
||||
return UriComponentsBuilder.fromUriString(DEFAULT_REDIRECT_URI_TEMPLATE) |
||||
.buildAndExpand(request.getScheme(), request.getServerName(), request.getServerPort(), |
||||
AUTHORIZE_BASE_URI, clientRegistration.getClientAlias()) |
||||
.encode() |
||||
.toUriString(); |
||||
} |
||||
} |
||||
@ -0,0 +1,50 @@
@@ -0,0 +1,50 @@
|
||||
/* |
||||
* 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.oauth2.client.authentication; |
||||
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken; |
||||
import org.springframework.security.core.GrantedAuthority; |
||||
import org.springframework.security.core.SpringSecurityCoreVersion; |
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType; |
||||
import org.springframework.util.Assert; |
||||
|
||||
import java.util.Collection; |
||||
|
||||
/** |
||||
* Base implementation of an {@link AbstractAuthenticationToken} that holds |
||||
* an <i>authorization grant</i> credential for a specific {@link AuthorizationGrantType}. |
||||
* |
||||
* @author Joe Grandja |
||||
* @since 5.0 |
||||
* @see AuthorizationGrantType |
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-1.3">Section 1.3 Authorization Grant</a> |
||||
*/ |
||||
public abstract class AuthorizationGrantAuthenticationToken extends AbstractAuthenticationToken { |
||||
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID; |
||||
private final AuthorizationGrantType authorizationGrantType; |
||||
|
||||
protected AuthorizationGrantAuthenticationToken(AuthorizationGrantType authorizationGrantType, |
||||
Collection<? extends GrantedAuthority> authorities) { |
||||
|
||||
super(authorities); |
||||
Assert.notNull(authorizationGrantType, "authorizationGrantType cannot be null"); |
||||
this.authorizationGrantType = authorizationGrantType; |
||||
} |
||||
|
||||
public AuthorizationGrantType getGrantType() { |
||||
return this.authorizationGrantType; |
||||
} |
||||
} |
||||
@ -0,0 +1,40 @@
@@ -0,0 +1,40 @@
|
||||
/* |
||||
* 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.oauth2.client.authentication; |
||||
|
||||
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType; |
||||
import org.springframework.security.oauth2.core.endpoint.TokenResponseAttributes; |
||||
|
||||
/** |
||||
* Implementations of this interface are responsible for <i>"exchanging"</i> |
||||
* an <i>authorization grant</i> credential (for example, an authorization code) for an |
||||
* <i>access token</i> credential at the authorization server's <i>Token Endpoint</i>. |
||||
* |
||||
* @author Joe Grandja |
||||
* @since 5.0 |
||||
* @see AuthorizationGrantType |
||||
* @see AuthorizationGrantAuthenticationToken |
||||
* @see TokenResponseAttributes |
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-1.3">Section 1.3 Authorization Grant</a> |
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.3">Section 4.1.3 Access Token Request (Authorization Code Grant)</a> |
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.4">Section 4.1.4 Access Token Response (Authorization Code Grant)</a> |
||||
*/ |
||||
public interface AuthorizationGrantTokenExchanger<T extends AuthorizationGrantAuthenticationToken> { |
||||
|
||||
TokenResponseAttributes exchange(T authorizationGrantAuthentication) throws OAuth2AuthenticationException; |
||||
|
||||
} |
||||
@ -0,0 +1,45 @@
@@ -0,0 +1,45 @@
|
||||
/* |
||||
* 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.oauth2.client.authentication; |
||||
|
||||
import org.springframework.security.oauth2.core.endpoint.AuthorizationRequestAttributes; |
||||
|
||||
import javax.servlet.http.HttpServletRequest; |
||||
|
||||
/** |
||||
* Implementations of this interface are responsible for the persistence |
||||
* of {@link AuthorizationRequestAttributes} between requests. |
||||
* |
||||
* <p> |
||||
* Used by the {@link AuthorizationCodeRequestRedirectFilter} for persisting the <i>Authorization Request</i> |
||||
* before it initiates the authorization code grant flow. |
||||
* As well, used by the {@link AuthorizationCodeAuthenticationProcessingFilter} when resolving |
||||
* the associated <i>Authorization Request</i> during the handling of the <i>Authorization Response</i>. |
||||
* |
||||
* @author Joe Grandja |
||||
* @since 5.0 |
||||
* @see AuthorizationRequestAttributes |
||||
* @see HttpSessionAuthorizationRequestRepository |
||||
*/ |
||||
public interface AuthorizationRequestRepository { |
||||
|
||||
AuthorizationRequestAttributes loadAuthorizationRequest(HttpServletRequest request); |
||||
|
||||
void saveAuthorizationRequest(AuthorizationRequestAttributes authorizationRequest, HttpServletRequest request); |
||||
|
||||
AuthorizationRequestAttributes removeAuthorizationRequest(HttpServletRequest request); |
||||
|
||||
} |
||||
@ -0,0 +1,31 @@
@@ -0,0 +1,31 @@
|
||||
/* |
||||
* 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.oauth2.client.authentication; |
||||
|
||||
|
||||
import org.springframework.security.oauth2.core.endpoint.AuthorizationRequestAttributes; |
||||
|
||||
import java.net.URI; |
||||
|
||||
/** |
||||
* |
||||
* @author Joe Grandja |
||||
* @since 5.0 |
||||
*/ |
||||
public interface AuthorizationRequestUriBuilder { |
||||
|
||||
URI build(AuthorizationRequestAttributes authorizationRequestAttributes); |
||||
} |
||||
@ -0,0 +1,49 @@
@@ -0,0 +1,49 @@
|
||||
/* |
||||
* 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.oauth2.client.authentication; |
||||
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2Parameter; |
||||
import org.springframework.security.oauth2.core.endpoint.ResponseType; |
||||
import org.springframework.security.oauth2.core.endpoint.AuthorizationRequestAttributes; |
||||
import org.springframework.web.util.UriComponentsBuilder; |
||||
|
||||
import java.net.URI; |
||||
import java.util.stream.Collectors; |
||||
|
||||
/** |
||||
* |
||||
* @author Joe Grandja |
||||
* @since 5.0 |
||||
*/ |
||||
public class DefaultAuthorizationRequestUriBuilder implements AuthorizationRequestUriBuilder { |
||||
|
||||
@Override |
||||
public URI build(AuthorizationRequestAttributes authorizationRequestAttributes) { |
||||
UriComponentsBuilder uriBuilder = UriComponentsBuilder |
||||
.fromUriString(authorizationRequestAttributes.getAuthorizeUri()) |
||||
.queryParam(OAuth2Parameter.RESPONSE_TYPE, ResponseType.CODE.value()); |
||||
if (authorizationRequestAttributes.getRedirectUri() != null) { |
||||
uriBuilder.queryParam(OAuth2Parameter.REDIRECT_URI, authorizationRequestAttributes.getRedirectUri()); |
||||
} |
||||
uriBuilder |
||||
.queryParam(OAuth2Parameter.CLIENT_ID, authorizationRequestAttributes.getClientId()) |
||||
.queryParam(OAuth2Parameter.SCOPE, |
||||
authorizationRequestAttributes.getScopes().stream().collect(Collectors.joining(" "))) |
||||
.queryParam(OAuth2Parameter.STATE, authorizationRequestAttributes.getState()); |
||||
|
||||
return uriBuilder.build().encode().toUri(); |
||||
} |
||||
} |
||||
@ -0,0 +1,54 @@
@@ -0,0 +1,54 @@
|
||||
/* |
||||
* 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.oauth2.client.authentication; |
||||
|
||||
import org.springframework.security.crypto.codec.Base64; |
||||
import org.springframework.security.crypto.keygen.BytesKeyGenerator; |
||||
import org.springframework.security.crypto.keygen.KeyGenerators; |
||||
import org.springframework.security.crypto.keygen.StringKeyGenerator; |
||||
import org.springframework.util.Assert; |
||||
|
||||
/** |
||||
* The default implementation for generating the |
||||
* {@link org.springframework.security.oauth2.core.endpoint.OAuth2Parameter#STATE} parameter |
||||
* used in the <i>Authorization Request</i> and correlated in the <i>Authorization Response</i> (or <i>Error Response</i>). |
||||
* |
||||
* <p> |
||||
* <b>NOTE:</b> The value of the <i>state</i> parameter is an opaque <code>String</code> |
||||
* used by the client to prevent cross-site request forgery, as described in |
||||
* <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-10.12">Section 10.12</a> of the specification. |
||||
* |
||||
* @author Joe Grandja |
||||
* @since 5.0 |
||||
*/ |
||||
public class DefaultStateGenerator implements StringKeyGenerator { |
||||
private static final int DEFAULT_BYTE_LENGTH = 32; |
||||
private final BytesKeyGenerator keyGenerator; |
||||
|
||||
public DefaultStateGenerator() { |
||||
this(DEFAULT_BYTE_LENGTH); |
||||
} |
||||
|
||||
public DefaultStateGenerator(int byteLength) { |
||||
Assert.isTrue(byteLength > 0, "byteLength must be greater than 0"); |
||||
this.keyGenerator = KeyGenerators.secureRandom(byteLength); |
||||
} |
||||
|
||||
@Override |
||||
public String generateKey() { |
||||
return new String(Base64.encode(keyGenerator.generateKey())); |
||||
} |
||||
} |
||||
@ -0,0 +1,63 @@
@@ -0,0 +1,63 @@
|
||||
/* |
||||
* 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.oauth2.client.authentication; |
||||
|
||||
import org.springframework.security.oauth2.core.endpoint.AuthorizationRequestAttributes; |
||||
|
||||
import javax.servlet.http.HttpServletRequest; |
||||
import javax.servlet.http.HttpSession; |
||||
|
||||
/** |
||||
* An implementation of an {@link AuthorizationRequestRepository} that stores |
||||
* {@link AuthorizationRequestAttributes} in the {@link HttpSession}. |
||||
* |
||||
* @author Joe Grandja |
||||
* @since 5.0 |
||||
* @see AuthorizationRequestAttributes |
||||
*/ |
||||
public final class HttpSessionAuthorizationRequestRepository implements AuthorizationRequestRepository { |
||||
private static final String DEFAULT_AUTHORIZATION_REQUEST_ATTR_NAME = |
||||
HttpSessionAuthorizationRequestRepository.class.getName() + ".AUTHORIZATION_REQUEST"; |
||||
private String sessionAttributeName = DEFAULT_AUTHORIZATION_REQUEST_ATTR_NAME; |
||||
|
||||
@Override |
||||
public AuthorizationRequestAttributes loadAuthorizationRequest(HttpServletRequest request) { |
||||
AuthorizationRequestAttributes authorizationRequest = null; |
||||
HttpSession session = request.getSession(false); |
||||
if (session != null) { |
||||
authorizationRequest = (AuthorizationRequestAttributes) session.getAttribute(this.sessionAttributeName); |
||||
} |
||||
return authorizationRequest; |
||||
} |
||||
|
||||
@Override |
||||
public void saveAuthorizationRequest(AuthorizationRequestAttributes authorizationRequest, HttpServletRequest request) { |
||||
if (authorizationRequest == null) { |
||||
this.removeAuthorizationRequest(request); |
||||
return; |
||||
} |
||||
request.getSession().setAttribute(this.sessionAttributeName, authorizationRequest); |
||||
} |
||||
|
||||
@Override |
||||
public AuthorizationRequestAttributes removeAuthorizationRequest(HttpServletRequest request) { |
||||
AuthorizationRequestAttributes authorizationRequest = this.loadAuthorizationRequest(request); |
||||
if (authorizationRequest != null) { |
||||
request.getSession().removeAttribute(this.sessionAttributeName); |
||||
} |
||||
return authorizationRequest; |
||||
} |
||||
} |
||||
@ -0,0 +1,66 @@
@@ -0,0 +1,66 @@
|
||||
/* |
||||
* 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.oauth2.client.authentication; |
||||
|
||||
import org.springframework.security.core.Authentication; |
||||
import org.springframework.security.core.AuthenticationException; |
||||
import org.springframework.security.oauth2.core.OAuth2Error; |
||||
import org.springframework.util.Assert; |
||||
|
||||
/** |
||||
* This exception is thrown for all <i>OAuth 2.0</i> related {@link Authentication} errors. |
||||
* |
||||
* <p> |
||||
* There are a number of scenarios where an error may occur, for example: |
||||
* <ul> |
||||
* <li>The authorization request or token request is missing a required parameter</li> |
||||
* <li>Missing or invalid client identifier</li> |
||||
* <li>Invalid or mismatching redirection URI</li> |
||||
* <li>The requested scope is invalid, unknown, or malformed</li> |
||||
* <li>The resource owner or authorization server denied the access request</li> |
||||
* <li>Client authentication failed</li> |
||||
* <li>The provided authorization grant (authorization code, resource owner credentials) is invalid, expired, or revoked</li> |
||||
* </ul> |
||||
* |
||||
* @author Joe Grandja |
||||
* @since 5.0 |
||||
*/ |
||||
public class OAuth2AuthenticationException extends AuthenticationException { |
||||
private OAuth2Error errorObject; |
||||
|
||||
public OAuth2AuthenticationException(OAuth2Error errorObject, Throwable cause) { |
||||
this(errorObject, cause.getMessage(), cause); |
||||
} |
||||
|
||||
public OAuth2AuthenticationException(OAuth2Error errorObject, String message) { |
||||
super(message); |
||||
this.setErrorObject(errorObject); |
||||
} |
||||
|
||||
public OAuth2AuthenticationException(OAuth2Error errorObject, String message, Throwable cause) { |
||||
super(message, cause); |
||||
this.setErrorObject(errorObject); |
||||
} |
||||
|
||||
public OAuth2Error getErrorObject() { |
||||
return errorObject; |
||||
} |
||||
|
||||
private void setErrorObject(OAuth2Error errorObject) { |
||||
Assert.notNull(errorObject, "OAuth2 Error object cannot be null"); |
||||
this.errorObject = errorObject; |
||||
} |
||||
} |
||||
@ -0,0 +1,87 @@
@@ -0,0 +1,87 @@
|
||||
/* |
||||
* 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.oauth2.client.authentication; |
||||
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken; |
||||
import org.springframework.security.core.Authentication; |
||||
import org.springframework.security.core.GrantedAuthority; |
||||
import org.springframework.security.core.SpringSecurityCoreVersion; |
||||
import org.springframework.security.core.authority.AuthorityUtils; |
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration; |
||||
import org.springframework.security.oauth2.client.user.OAuth2UserService; |
||||
import org.springframework.security.oauth2.core.AccessToken; |
||||
import org.springframework.security.oauth2.core.user.OAuth2User; |
||||
import org.springframework.util.Assert; |
||||
|
||||
import java.util.Collection; |
||||
|
||||
/** |
||||
* An implementation of an {@link AbstractAuthenticationToken} |
||||
* that represents an <i>OAuth 2.0</i> {@link Authentication}. |
||||
* |
||||
* <p> |
||||
* It associates an {@link OAuth2User}, {@link ClientRegistration} and an {@link AccessToken}. |
||||
* This <code>Authentication</code> is considered <i>"authenticated"</i> if the {@link OAuth2User} |
||||
* is provided in the respective constructor. This typically happens after the {@link OAuth2UserService} |
||||
* retrieves the end-user's (resource owner) attributes from the <i>UserInfo Endpoint</i>. |
||||
* |
||||
* @author Joe Grandja |
||||
* @since 5.0 |
||||
* @see OAuth2User |
||||
* @see ClientRegistration |
||||
* @see AccessToken |
||||
*/ |
||||
public class OAuth2AuthenticationToken extends AbstractAuthenticationToken { |
||||
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID; |
||||
private final OAuth2User principal; |
||||
private final ClientRegistration clientRegistration; |
||||
private final AccessToken accessToken; |
||||
|
||||
public OAuth2AuthenticationToken(ClientRegistration clientRegistration, AccessToken accessToken) { |
||||
this(null, AuthorityUtils.NO_AUTHORITIES, clientRegistration, accessToken); |
||||
} |
||||
|
||||
public OAuth2AuthenticationToken(OAuth2User principal, Collection<? extends GrantedAuthority> authorities, |
||||
ClientRegistration clientRegistration, AccessToken accessToken) { |
||||
|
||||
super(authorities); |
||||
Assert.notNull(clientRegistration, "clientRegistration cannot be null"); |
||||
Assert.notNull(accessToken, "accessToken cannot be null"); |
||||
this.principal = principal; |
||||
this.clientRegistration = clientRegistration; |
||||
this.accessToken = accessToken; |
||||
this.setAuthenticated(principal != null); |
||||
} |
||||
|
||||
@Override |
||||
public Object getPrincipal() { |
||||
return this.principal; |
||||
} |
||||
|
||||
@Override |
||||
public Object getCredentials() { |
||||
// Credentials are never exposed (by the Provider) for an OAuth2 User
|
||||
return ""; |
||||
} |
||||
|
||||
public ClientRegistration getClientRegistration() { |
||||
return this.clientRegistration; |
||||
} |
||||
|
||||
public AccessToken getAccessToken() { |
||||
return this.accessToken; |
||||
} |
||||
} |
||||
@ -0,0 +1,142 @@
@@ -0,0 +1,142 @@
|
||||
/* |
||||
* 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.oauth2.client.authentication.nimbus; |
||||
|
||||
|
||||
import com.nimbusds.oauth2.sdk.*; |
||||
import com.nimbusds.oauth2.sdk.auth.ClientAuthentication; |
||||
import com.nimbusds.oauth2.sdk.auth.ClientSecretBasic; |
||||
import com.nimbusds.oauth2.sdk.auth.ClientSecretPost; |
||||
import com.nimbusds.oauth2.sdk.auth.Secret; |
||||
import com.nimbusds.oauth2.sdk.http.HTTPRequest; |
||||
import com.nimbusds.oauth2.sdk.id.ClientID; |
||||
import org.springframework.http.MediaType; |
||||
import org.springframework.security.authentication.AuthenticationServiceException; |
||||
import org.springframework.security.oauth2.client.authentication.AuthorizationCodeAuthenticationToken; |
||||
import org.springframework.security.oauth2.client.authentication.AuthorizationGrantTokenExchanger; |
||||
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationException; |
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration; |
||||
import org.springframework.security.oauth2.core.AccessToken; |
||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod; |
||||
import org.springframework.security.oauth2.core.OAuth2Error; |
||||
import org.springframework.security.oauth2.core.endpoint.TokenResponseAttributes; |
||||
import org.springframework.util.CollectionUtils; |
||||
|
||||
import java.io.IOException; |
||||
import java.net.URI; |
||||
import java.util.Collections; |
||||
import java.util.HashSet; |
||||
import java.util.Map; |
||||
import java.util.Set; |
||||
import java.util.stream.Collectors; |
||||
|
||||
/** |
||||
* An implementation of an {@link AuthorizationGrantTokenExchanger} that <i>"exchanges"</i> |
||||
* an <i>authorization code</i> credential for an <i>access token</i> credential |
||||
* at the authorization server's <i>Token Endpoint</i>. |
||||
* |
||||
* <p> |
||||
* <b>NOTE:</b> This implementation uses the <b>Nimbus OAuth 2.0 SDK</b> internally. |
||||
* |
||||
* @author Joe Grandja |
||||
* @since 5.0 |
||||
* @see AuthorizationCodeAuthenticationToken |
||||
* @see TokenResponseAttributes |
||||
* @see <a target="_blank" href="https://connect2id.com/products/nimbus-oauth-openid-connect-sdk">Nimbus OAuth 2.0 SDK</a> |
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.3">Section 4.1.3 Access Token Request (Authorization Code Grant)</a> |
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.4">Section 4.1.4 Access Token Response (Authorization Code Grant)</a> |
||||
*/ |
||||
public class NimbusAuthorizationCodeTokenExchanger implements AuthorizationGrantTokenExchanger<AuthorizationCodeAuthenticationToken> { |
||||
private static final String INVALID_TOKEN_RESPONSE_ERROR_CODE = "invalid_token_response"; |
||||
|
||||
@Override |
||||
public TokenResponseAttributes exchange(AuthorizationCodeAuthenticationToken authorizationCodeAuthenticationToken) |
||||
throws OAuth2AuthenticationException { |
||||
|
||||
ClientRegistration clientRegistration = authorizationCodeAuthenticationToken.getClientRegistration(); |
||||
|
||||
// Build the authorization code grant request for the token endpoint
|
||||
AuthorizationCode authorizationCode = new AuthorizationCode(authorizationCodeAuthenticationToken.getAuthorizationCode()); |
||||
URI redirectUri = this.toURI(clientRegistration.getRedirectUri()); |
||||
AuthorizationGrant authorizationCodeGrant = new AuthorizationCodeGrant(authorizationCode, redirectUri); |
||||
URI tokenUri = this.toURI(clientRegistration.getProviderDetails().getTokenUri()); |
||||
|
||||
// Set the credentials to authenticate the client at the token endpoint
|
||||
ClientID clientId = new ClientID(clientRegistration.getClientId()); |
||||
Secret clientSecret = new Secret(clientRegistration.getClientSecret()); |
||||
ClientAuthentication clientAuthentication; |
||||
if (ClientAuthenticationMethod.FORM.equals(clientRegistration.getClientAuthenticationMethod())) { |
||||
clientAuthentication = new ClientSecretPost(clientId, clientSecret); |
||||
} else { |
||||
clientAuthentication = new ClientSecretBasic(clientId, clientSecret); |
||||
} |
||||
|
||||
TokenResponse tokenResponse; |
||||
try { |
||||
// Send the Access Token request
|
||||
TokenRequest tokenRequest = new TokenRequest(tokenUri, clientAuthentication, authorizationCodeGrant); |
||||
HTTPRequest httpRequest = tokenRequest.toHTTPRequest(); |
||||
httpRequest.setAccept(MediaType.APPLICATION_JSON_VALUE); |
||||
tokenResponse = TokenResponse.parse(httpRequest.send()); |
||||
} catch (ParseException pe) { |
||||
// This error occurs if the Access Token Response is not well-formed,
|
||||
// for example, a required attribute is missing
|
||||
throw new OAuth2AuthenticationException(new OAuth2Error(INVALID_TOKEN_RESPONSE_ERROR_CODE), pe); |
||||
} catch (IOException ioe) { |
||||
// This error occurs when there is a network-related issue
|
||||
throw new AuthenticationServiceException("An error occurred while sending the Access Token Request: " + |
||||
ioe.getMessage(), ioe); |
||||
} |
||||
|
||||
if (!tokenResponse.indicatesSuccess()) { |
||||
TokenErrorResponse tokenErrorResponse = (TokenErrorResponse) tokenResponse; |
||||
ErrorObject errorObject = tokenErrorResponse.getErrorObject(); |
||||
OAuth2Error oauth2Error = new OAuth2Error(errorObject.getCode(), errorObject.getDescription(), |
||||
(errorObject.getURI() != null ? errorObject.getURI().toString() : null)); |
||||
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); |
||||
} |
||||
|
||||
AccessTokenResponse accessTokenResponse = (AccessTokenResponse) tokenResponse; |
||||
|
||||
String accessToken = accessTokenResponse.getTokens().getAccessToken().getValue(); |
||||
AccessToken.TokenType accessTokenType = null; |
||||
if (AccessToken.TokenType.BEARER.value().equals(accessTokenResponse.getTokens().getAccessToken().getType().getValue())) { |
||||
accessTokenType = AccessToken.TokenType.BEARER; |
||||
} |
||||
long expiresIn = accessTokenResponse.getTokens().getAccessToken().getLifetime(); |
||||
Set<String> scopes = Collections.emptySet(); |
||||
if (!CollectionUtils.isEmpty(accessTokenResponse.getTokens().getAccessToken().getScope())) { |
||||
scopes = new HashSet<>(accessTokenResponse.getTokens().getAccessToken().getScope().toStringList()); |
||||
} |
||||
Map<String, Object> additionalParameters = accessTokenResponse.getCustomParameters().entrySet().stream() |
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); |
||||
|
||||
return TokenResponseAttributes.withToken(accessToken) |
||||
.tokenType(accessTokenType) |
||||
.expiresIn(expiresIn) |
||||
.scopes(scopes) |
||||
.additionalParameters(additionalParameters) |
||||
.build(); |
||||
} |
||||
|
||||
private URI toURI(String uriStr) { |
||||
try { |
||||
return new URI(uriStr); |
||||
} catch (Exception ex) { |
||||
throw new IllegalArgumentException("An error occurred parsing URI: " + uriStr, ex); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,279 @@
@@ -0,0 +1,279 @@
|
||||
/* |
||||
* 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.oauth2.client.registration; |
||||
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType; |
||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod; |
||||
import org.springframework.util.Assert; |
||||
import org.springframework.util.CollectionUtils; |
||||
|
||||
import java.util.Arrays; |
||||
import java.util.Collections; |
||||
import java.util.LinkedHashSet; |
||||
import java.util.Set; |
||||
|
||||
|
||||
/** |
||||
* |
||||
* @author Joe Grandja |
||||
* @since 5.0 |
||||
*/ |
||||
public class ClientRegistration { |
||||
private String clientId; |
||||
private String clientSecret; |
||||
private ClientAuthenticationMethod clientAuthenticationMethod = ClientAuthenticationMethod.HEADER; |
||||
private AuthorizationGrantType authorizedGrantType; |
||||
private String redirectUri; |
||||
private Set<String> scopes = Collections.emptySet(); |
||||
private ProviderDetails providerDetails = new ProviderDetails(); |
||||
private String clientName; |
||||
private String clientAlias; |
||||
|
||||
protected ClientRegistration() { |
||||
} |
||||
|
||||
public String getClientId() { |
||||
return this.clientId; |
||||
} |
||||
|
||||
protected void setClientId(String clientId) { |
||||
this.clientId = clientId; |
||||
} |
||||
|
||||
public String getClientSecret() { |
||||
return this.clientSecret; |
||||
} |
||||
|
||||
protected void setClientSecret(String clientSecret) { |
||||
this.clientSecret = clientSecret; |
||||
} |
||||
|
||||
public ClientAuthenticationMethod getClientAuthenticationMethod() { |
||||
return this.clientAuthenticationMethod; |
||||
} |
||||
|
||||
protected void setClientAuthenticationMethod(ClientAuthenticationMethod clientAuthenticationMethod) { |
||||
this.clientAuthenticationMethod = clientAuthenticationMethod; |
||||
} |
||||
|
||||
public AuthorizationGrantType getAuthorizedGrantType() { |
||||
return this.authorizedGrantType; |
||||
} |
||||
|
||||
protected void setAuthorizedGrantType(AuthorizationGrantType authorizedGrantType) { |
||||
this.authorizedGrantType = authorizedGrantType; |
||||
} |
||||
|
||||
public String getRedirectUri() { |
||||
return this.redirectUri; |
||||
} |
||||
|
||||
protected void setRedirectUri(String redirectUri) { |
||||
this.redirectUri = redirectUri; |
||||
} |
||||
|
||||
public Set<String> getScopes() { |
||||
return this.scopes; |
||||
} |
||||
|
||||
protected void setScopes(Set<String> scopes) { |
||||
this.scopes = scopes; |
||||
} |
||||
|
||||
public ProviderDetails getProviderDetails() { |
||||
return this.providerDetails; |
||||
} |
||||
|
||||
protected void setProviderDetails(ProviderDetails providerDetails) { |
||||
this.providerDetails = providerDetails; |
||||
} |
||||
|
||||
public String getClientName() { |
||||
return this.clientName; |
||||
} |
||||
|
||||
protected void setClientName(String clientName) { |
||||
this.clientName = clientName; |
||||
} |
||||
|
||||
public String getClientAlias() { |
||||
return this.clientAlias; |
||||
} |
||||
|
||||
protected void setClientAlias(String clientAlias) { |
||||
this.clientAlias = clientAlias; |
||||
} |
||||
|
||||
public class ProviderDetails { |
||||
private String authorizationUri; |
||||
private String tokenUri; |
||||
private String userInfoUri; |
||||
|
||||
protected ProviderDetails() { |
||||
} |
||||
|
||||
public String getAuthorizationUri() { |
||||
return this.authorizationUri; |
||||
} |
||||
|
||||
protected void setAuthorizationUri(String authorizationUri) { |
||||
this.authorizationUri = authorizationUri; |
||||
} |
||||
|
||||
public String getTokenUri() { |
||||
return this.tokenUri; |
||||
} |
||||
|
||||
protected void setTokenUri(String tokenUri) { |
||||
this.tokenUri = tokenUri; |
||||
} |
||||
|
||||
public String getUserInfoUri() { |
||||
return this.userInfoUri; |
||||
} |
||||
|
||||
protected void setUserInfoUri(String userInfoUri) { |
||||
this.userInfoUri = userInfoUri; |
||||
} |
||||
} |
||||
|
||||
public static class Builder { |
||||
protected String clientId; |
||||
protected String clientSecret; |
||||
protected ClientAuthenticationMethod clientAuthenticationMethod = ClientAuthenticationMethod.HEADER; |
||||
protected AuthorizationGrantType authorizedGrantType; |
||||
protected String redirectUri; |
||||
protected Set<String> scopes; |
||||
protected String authorizationUri; |
||||
protected String tokenUri; |
||||
protected String userInfoUri; |
||||
protected String clientName; |
||||
protected String clientAlias; |
||||
|
||||
public Builder(String clientId) { |
||||
this.clientId = clientId; |
||||
} |
||||
|
||||
public Builder(ClientRegistrationProperties clientRegistrationProperties) { |
||||
this(clientRegistrationProperties.getClientId()); |
||||
this.clientSecret(clientRegistrationProperties.getClientSecret()); |
||||
this.clientAuthenticationMethod(clientRegistrationProperties.getClientAuthenticationMethod()); |
||||
this.authorizedGrantType(clientRegistrationProperties.getAuthorizedGrantType()); |
||||
this.redirectUri(clientRegistrationProperties.getRedirectUri()); |
||||
if (!CollectionUtils.isEmpty(clientRegistrationProperties.getScopes())) { |
||||
this.scopes(clientRegistrationProperties.getScopes().stream().toArray(String[]::new)); |
||||
} |
||||
this.authorizationUri(clientRegistrationProperties.getAuthorizationUri()); |
||||
this.tokenUri(clientRegistrationProperties.getTokenUri()); |
||||
this.userInfoUri(clientRegistrationProperties.getUserInfoUri()); |
||||
this.clientName(clientRegistrationProperties.getClientName()); |
||||
this.clientAlias(clientRegistrationProperties.getClientAlias()); |
||||
} |
||||
|
||||
public Builder clientSecret(String clientSecret) { |
||||
this.clientSecret = clientSecret; |
||||
return this; |
||||
} |
||||
|
||||
public Builder clientAuthenticationMethod(ClientAuthenticationMethod clientAuthenticationMethod) { |
||||
this.clientAuthenticationMethod = clientAuthenticationMethod; |
||||
return this; |
||||
} |
||||
|
||||
public Builder authorizedGrantType(AuthorizationGrantType authorizedGrantType) { |
||||
this.authorizedGrantType = authorizedGrantType; |
||||
return this; |
||||
} |
||||
|
||||
public Builder redirectUri(String redirectUri) { |
||||
this.redirectUri = redirectUri; |
||||
return this; |
||||
} |
||||
|
||||
public Builder scopes(String... scopes) { |
||||
if (scopes != null && scopes.length > 0) { |
||||
this.scopes = Collections.unmodifiableSet( |
||||
new LinkedHashSet<>(Arrays.asList(scopes))); |
||||
} |
||||
return this; |
||||
} |
||||
|
||||
public Builder authorizationUri(String authorizationUri) { |
||||
this.authorizationUri = authorizationUri; |
||||
return this; |
||||
} |
||||
|
||||
public Builder tokenUri(String tokenUri) { |
||||
this.tokenUri = tokenUri; |
||||
return this; |
||||
} |
||||
|
||||
public Builder userInfoUri(String userInfoUri) { |
||||
this.userInfoUri = userInfoUri; |
||||
return this; |
||||
} |
||||
|
||||
public Builder clientName(String clientName) { |
||||
this.clientName = clientName; |
||||
return this; |
||||
} |
||||
|
||||
public Builder clientAlias(String clientAlias) { |
||||
this.clientAlias = clientAlias; |
||||
return this; |
||||
} |
||||
|
||||
public ClientRegistration build() { |
||||
this.validateClientWithAuthorizationCodeGrantType(); |
||||
ClientRegistration clientRegistration = new ClientRegistration(); |
||||
this.setProperties(clientRegistration); |
||||
return clientRegistration; |
||||
} |
||||
|
||||
protected void setProperties(ClientRegistration clientRegistration) { |
||||
clientRegistration.setClientId(this.clientId); |
||||
clientRegistration.setClientSecret(this.clientSecret); |
||||
clientRegistration.setClientAuthenticationMethod(this.clientAuthenticationMethod); |
||||
clientRegistration.setAuthorizedGrantType(this.authorizedGrantType); |
||||
clientRegistration.setRedirectUri(this.redirectUri); |
||||
clientRegistration.setScopes(this.scopes); |
||||
|
||||
ProviderDetails providerDetails = clientRegistration.new ProviderDetails(); |
||||
providerDetails.setAuthorizationUri(this.authorizationUri); |
||||
providerDetails.setTokenUri(this.tokenUri); |
||||
providerDetails.setUserInfoUri(this.userInfoUri); |
||||
clientRegistration.setProviderDetails(providerDetails); |
||||
|
||||
clientRegistration.setClientName(this.clientName); |
||||
clientRegistration.setClientAlias(this.clientAlias); |
||||
} |
||||
|
||||
protected void validateClientWithAuthorizationCodeGrantType() { |
||||
Assert.isTrue(AuthorizationGrantType.AUTHORIZATION_CODE.equals(this.authorizedGrantType), |
||||
"authorizedGrantType must be " + AuthorizationGrantType.AUTHORIZATION_CODE.value()); |
||||
Assert.hasText(this.clientId, "clientId cannot be empty"); |
||||
Assert.hasText(this.clientSecret, "clientSecret cannot be empty"); |
||||
Assert.notNull(this.clientAuthenticationMethod, "clientAuthenticationMethod cannot be null"); |
||||
Assert.hasText(this.redirectUri, "redirectUri cannot be empty"); |
||||
Assert.notEmpty(this.scopes, "scopes cannot be empty"); |
||||
Assert.hasText(this.authorizationUri, "authorizationUri cannot be empty"); |
||||
Assert.hasText(this.tokenUri, "tokenUri cannot be empty"); |
||||
Assert.hasText(this.userInfoUri, "userInfoUri cannot be empty"); |
||||
Assert.hasText(this.clientName, "clientName cannot be empty"); |
||||
Assert.hasText(this.clientAlias, "clientAlias cannot be empty"); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,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.security.oauth2.client.registration; |
||||
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType; |
||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod; |
||||
|
||||
import java.util.Set; |
||||
|
||||
/** |
||||
* |
||||
* @author Joe Grandja |
||||
* @since 5.0 |
||||
*/ |
||||
public class ClientRegistrationProperties { |
||||
private String clientId; |
||||
private String clientSecret; |
||||
private ClientAuthenticationMethod clientAuthenticationMethod = ClientAuthenticationMethod.HEADER; |
||||
private AuthorizationGrantType authorizedGrantType; |
||||
private String redirectUri; |
||||
private Set<String> scopes; |
||||
private String authorizationUri; |
||||
private String tokenUri; |
||||
private String userInfoUri; |
||||
private String clientName; |
||||
private String clientAlias; |
||||
|
||||
|
||||
public String getClientId() { |
||||
return this.clientId; |
||||
} |
||||
|
||||
public void setClientId(String clientId) { |
||||
this.clientId = clientId; |
||||
} |
||||
|
||||
public String getClientSecret() { |
||||
return this.clientSecret; |
||||
} |
||||
|
||||
public void setClientSecret(String clientSecret) { |
||||
this.clientSecret = clientSecret; |
||||
} |
||||
|
||||
public ClientAuthenticationMethod getClientAuthenticationMethod() { |
||||
return this.clientAuthenticationMethod; |
||||
} |
||||
|
||||
public void setClientAuthenticationMethod(ClientAuthenticationMethod clientAuthenticationMethod) { |
||||
this.clientAuthenticationMethod = clientAuthenticationMethod; |
||||
} |
||||
|
||||
public AuthorizationGrantType getAuthorizedGrantType() { |
||||
return this.authorizedGrantType; |
||||
} |
||||
|
||||
public void setAuthorizedGrantType(AuthorizationGrantType authorizedGrantType) { |
||||
this.authorizedGrantType = authorizedGrantType; |
||||
} |
||||
|
||||
public String getRedirectUri() { |
||||
return this.redirectUri; |
||||
} |
||||
|
||||
public void setRedirectUri(String redirectUri) { |
||||
this.redirectUri = redirectUri; |
||||
} |
||||
|
||||
public Set<String> getScopes() { |
||||
return this.scopes; |
||||
} |
||||
|
||||
public void setScopes(Set<String> scopes) { |
||||
this.scopes = scopes; |
||||
} |
||||
|
||||
public String getAuthorizationUri() { |
||||
return this.authorizationUri; |
||||
} |
||||
|
||||
public void setAuthorizationUri(String authorizationUri) { |
||||
this.authorizationUri = authorizationUri; |
||||
} |
||||
|
||||
public String getTokenUri() { |
||||
return this.tokenUri; |
||||
} |
||||
|
||||
public void setTokenUri(String tokenUri) { |
||||
this.tokenUri = tokenUri; |
||||
} |
||||
|
||||
public String getUserInfoUri() { |
||||
return this.userInfoUri; |
||||
} |
||||
|
||||
public void setUserInfoUri(String userInfoUri) { |
||||
this.userInfoUri = userInfoUri; |
||||
} |
||||
|
||||
public String getClientName() { |
||||
return this.clientName; |
||||
} |
||||
|
||||
public void setClientName(String clientName) { |
||||
this.clientName = clientName; |
||||
} |
||||
|
||||
public String getClientAlias() { |
||||
return this.clientAlias; |
||||
} |
||||
|
||||
public void setClientAlias(String clientAlias) { |
||||
this.clientAlias = clientAlias; |
||||
} |
||||
} |
||||
@ -0,0 +1,33 @@
@@ -0,0 +1,33 @@
|
||||
/* |
||||
* 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.oauth2.client.registration; |
||||
|
||||
import java.util.List; |
||||
|
||||
/** |
||||
* |
||||
* @author Joe Grandja |
||||
* @since 5.0 |
||||
*/ |
||||
public interface ClientRegistrationRepository { |
||||
|
||||
ClientRegistration getRegistrationByClientId(String clientId); |
||||
|
||||
ClientRegistration getRegistrationByClientAlias(String clientAlias); |
||||
|
||||
List<ClientRegistration> getRegistrations(); |
||||
|
||||
} |
||||
@ -0,0 +1,59 @@
@@ -0,0 +1,59 @@
|
||||
/* |
||||
* 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.oauth2.client.registration; |
||||
|
||||
import org.springframework.util.Assert; |
||||
|
||||
import java.util.Collections; |
||||
import java.util.List; |
||||
import java.util.Optional; |
||||
|
||||
/** |
||||
* |
||||
* @author Joe Grandja |
||||
* @since 5.0 |
||||
*/ |
||||
public final class InMemoryClientRegistrationRepository implements ClientRegistrationRepository { |
||||
private final List<ClientRegistration> clientRegistrations; |
||||
|
||||
public InMemoryClientRegistrationRepository(List<ClientRegistration> clientRegistrations) { |
||||
Assert.notEmpty(clientRegistrations, "clientRegistrations cannot be empty"); |
||||
this.clientRegistrations = Collections.unmodifiableList(clientRegistrations); |
||||
} |
||||
|
||||
@Override |
||||
public ClientRegistration getRegistrationByClientId(String clientId) { |
||||
Optional<ClientRegistration> clientRegistration = |
||||
this.clientRegistrations.stream() |
||||
.filter(c -> c.getClientId().equals(clientId)) |
||||
.findFirst(); |
||||
return clientRegistration.isPresent() ? clientRegistration.get() : null; |
||||
} |
||||
|
||||
@Override |
||||
public ClientRegistration getRegistrationByClientAlias(String clientAlias) { |
||||
Optional<ClientRegistration> clientRegistration = |
||||
this.clientRegistrations.stream() |
||||
.filter(c -> c.getClientAlias().equals(clientAlias)) |
||||
.findFirst(); |
||||
return clientRegistration.isPresent() ? clientRegistration.get() : null; |
||||
} |
||||
|
||||
@Override |
||||
public List<ClientRegistration> getRegistrations() { |
||||
return this.clientRegistrations; |
||||
} |
||||
} |
||||
@ -0,0 +1,42 @@
@@ -0,0 +1,42 @@
|
||||
/* |
||||
* 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.oauth2.client.user; |
||||
|
||||
import org.springframework.security.core.AuthenticatedPrincipal; |
||||
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationException; |
||||
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; |
||||
import org.springframework.security.oauth2.core.user.OAuth2User; |
||||
import org.springframework.security.oauth2.oidc.user.UserInfo; |
||||
|
||||
/** |
||||
* Implementations of this interface are responsible for obtaining |
||||
* the end-user's (resource owner) attributes from the <i>UserInfo Endpoint</i> |
||||
* using the provided {@link OAuth2AuthenticationToken#getAccessToken()} |
||||
* and returning an {@link AuthenticatedPrincipal} in the form of an {@link OAuth2User} |
||||
* (for a standard <i>OAuth 2.0 Provider</i>) or {@link UserInfo} (for an <i>OpenID Connect 1.0 Provider</i>). |
||||
* |
||||
* @author Joe Grandja |
||||
* @since 5.0 |
||||
* @see OAuth2AuthenticationToken |
||||
* @see AuthenticatedPrincipal |
||||
* @see OAuth2User |
||||
* @see UserInfo |
||||
*/ |
||||
public interface OAuth2UserService { |
||||
|
||||
OAuth2User loadUser(OAuth2AuthenticationToken token) throws OAuth2AuthenticationException; |
||||
|
||||
} |
||||
@ -0,0 +1,53 @@
@@ -0,0 +1,53 @@
|
||||
/* |
||||
* 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.oauth2.client.user.converter; |
||||
|
||||
import org.springframework.core.convert.converter.Converter; |
||||
import org.springframework.http.client.ClientHttpResponse; |
||||
import org.springframework.http.converter.HttpMessageConverter; |
||||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; |
||||
import org.springframework.security.oauth2.core.user.OAuth2User; |
||||
|
||||
import java.io.IOException; |
||||
import java.util.Map; |
||||
|
||||
/** |
||||
* |
||||
* @author Joe Grandja |
||||
* @since 5.0 |
||||
*/ |
||||
public abstract class AbstractOAuth2UserConverter<T extends OAuth2User> implements Converter<ClientHttpResponse, T> { |
||||
private final HttpMessageConverter jackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter(); |
||||
|
||||
protected AbstractOAuth2UserConverter() { |
||||
} |
||||
|
||||
@Override |
||||
public final T convert(ClientHttpResponse clientHttpResponse) { |
||||
Map<String, Object> userAttributes; |
||||
|
||||
try { |
||||
userAttributes = (Map<String, Object>) this.jackson2HttpMessageConverter.read(Map.class, clientHttpResponse); |
||||
} catch (IOException ex) { |
||||
throw new IllegalArgumentException("An error occurred reading the UserInfo response: " + ex.getMessage(), ex); |
||||
} |
||||
|
||||
return this.convert(userAttributes); |
||||
} |
||||
|
||||
protected abstract T convert(Map<String, Object> userAttributes); |
||||
|
||||
} |
||||
@ -0,0 +1,51 @@
@@ -0,0 +1,51 @@
|
||||
/* |
||||
* 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.oauth2.client.user.converter; |
||||
|
||||
import org.springframework.core.convert.converter.Converter; |
||||
import org.springframework.http.client.ClientHttpResponse; |
||||
import org.springframework.http.converter.HttpMessageConverter; |
||||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; |
||||
import org.springframework.security.oauth2.core.user.OAuth2User; |
||||
|
||||
import java.io.IOException; |
||||
|
||||
/** |
||||
* |
||||
* @author Joe Grandja |
||||
* @since 5.0 |
||||
*/ |
||||
public final class CustomOAuth2UserConverter<T extends OAuth2User> implements Converter<ClientHttpResponse, T> { |
||||
private final HttpMessageConverter jackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter(); |
||||
private final Class<T> customType; |
||||
|
||||
public CustomOAuth2UserConverter(Class<T> customType) { |
||||
this.customType = customType; |
||||
} |
||||
|
||||
@Override |
||||
public T convert(ClientHttpResponse clientHttpResponse) { |
||||
T user; |
||||
|
||||
try { |
||||
user = (T) this.jackson2HttpMessageConverter.read(this.customType, clientHttpResponse); |
||||
} catch (IOException ex) { |
||||
throw new IllegalArgumentException("An error occurred reading the UserInfo response: " + ex.getMessage(), ex); |
||||
} |
||||
|
||||
return user; |
||||
} |
||||
} |
||||
@ -0,0 +1,41 @@
@@ -0,0 +1,41 @@
|
||||
/* |
||||
* 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.oauth2.client.user.converter; |
||||
|
||||
import org.springframework.security.oauth2.core.user.DefaultOAuth2User; |
||||
import org.springframework.security.oauth2.core.user.OAuth2User; |
||||
import org.springframework.util.Assert; |
||||
|
||||
import java.util.Map; |
||||
|
||||
/** |
||||
* |
||||
* @author Joe Grandja |
||||
* @since 5.0 |
||||
*/ |
||||
public final class OAuth2UserConverter extends AbstractOAuth2UserConverter<OAuth2User> { |
||||
private final String nameAttributeKey; |
||||
|
||||
public OAuth2UserConverter(String nameAttributeKey) { |
||||
Assert.hasText(nameAttributeKey, "nameAttributeKey cannot be empty"); |
||||
this.nameAttributeKey = nameAttributeKey; |
||||
} |
||||
|
||||
@Override |
||||
protected OAuth2User convert(Map<String, Object> userAttributes) { |
||||
return new DefaultOAuth2User(userAttributes, this.nameAttributeKey); |
||||
} |
||||
} |
||||
@ -0,0 +1,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.oauth2.client.user.converter; |
||||
|
||||
import org.springframework.security.oauth2.oidc.user.DefaultUserInfo; |
||||
import org.springframework.security.oauth2.oidc.user.UserInfo; |
||||
|
||||
import java.util.Map; |
||||
|
||||
/** |
||||
* |
||||
* @author Joe Grandja |
||||
* @since 5.0 |
||||
*/ |
||||
public final class UserInfoConverter extends AbstractOAuth2UserConverter<UserInfo> { |
||||
|
||||
@Override |
||||
protected UserInfo convert(Map<String, Object> userAttributes) { |
||||
return new DefaultUserInfo(userAttributes); |
||||
} |
||||
} |
||||
@ -0,0 +1,69 @@
@@ -0,0 +1,69 @@
|
||||
/* |
||||
* 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.oauth2.client.user.nimbus; |
||||
|
||||
import com.nimbusds.oauth2.sdk.http.HTTPResponse; |
||||
import org.springframework.http.HttpHeaders; |
||||
import org.springframework.http.client.AbstractClientHttpResponse; |
||||
import org.springframework.util.Assert; |
||||
|
||||
import java.io.ByteArrayInputStream; |
||||
import java.io.IOException; |
||||
import java.io.InputStream; |
||||
import java.nio.charset.Charset; |
||||
|
||||
/** |
||||
* |
||||
* @author Joe Grandja |
||||
* @since 5.0 |
||||
*/ |
||||
final class NimbusClientHttpResponse extends AbstractClientHttpResponse { |
||||
private final HTTPResponse httpResponse; |
||||
private final HttpHeaders headers; |
||||
|
||||
NimbusClientHttpResponse(HTTPResponse httpResponse) { |
||||
Assert.notNull(httpResponse, "httpResponse cannot be null"); |
||||
this.httpResponse = httpResponse; |
||||
this.headers = new HttpHeaders(); |
||||
this.headers.setAll(httpResponse.getHeaders()); |
||||
} |
||||
|
||||
@Override |
||||
public int getRawStatusCode() throws IOException { |
||||
return this.httpResponse.getStatusCode(); |
||||
} |
||||
|
||||
@Override |
||||
public String getStatusText() throws IOException { |
||||
return String.valueOf(this.getRawStatusCode()); |
||||
} |
||||
|
||||
@Override |
||||
public void close() { |
||||
} |
||||
|
||||
@Override |
||||
public InputStream getBody() throws IOException { |
||||
InputStream inputStream = new ByteArrayInputStream( |
||||
this.httpResponse.getContent().getBytes(Charset.forName("UTF-8"))); |
||||
return inputStream; |
||||
} |
||||
|
||||
@Override |
||||
public HttpHeaders getHeaders() { |
||||
return this.headers; |
||||
} |
||||
} |
||||
@ -0,0 +1,121 @@
@@ -0,0 +1,121 @@
|
||||
/* |
||||
* 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.oauth2.client.user.nimbus; |
||||
|
||||
import com.nimbusds.oauth2.sdk.ErrorObject; |
||||
import com.nimbusds.oauth2.sdk.ParseException; |
||||
import com.nimbusds.oauth2.sdk.http.HTTPRequest; |
||||
import com.nimbusds.oauth2.sdk.http.HTTPResponse; |
||||
import com.nimbusds.oauth2.sdk.token.BearerAccessToken; |
||||
import com.nimbusds.openid.connect.sdk.UserInfoErrorResponse; |
||||
import com.nimbusds.openid.connect.sdk.UserInfoRequest; |
||||
import org.springframework.core.convert.converter.Converter; |
||||
import org.springframework.http.MediaType; |
||||
import org.springframework.http.client.ClientHttpResponse; |
||||
import org.springframework.security.authentication.AuthenticationServiceException; |
||||
import org.springframework.security.core.AuthenticatedPrincipal; |
||||
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationException; |
||||
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; |
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration; |
||||
import org.springframework.security.oauth2.client.user.OAuth2UserService; |
||||
import org.springframework.security.oauth2.core.OAuth2Error; |
||||
import org.springframework.security.oauth2.core.user.OAuth2User; |
||||
import org.springframework.security.oauth2.oidc.user.UserInfo; |
||||
import org.springframework.util.Assert; |
||||
|
||||
import java.io.IOException; |
||||
import java.net.URI; |
||||
import java.util.HashMap; |
||||
import java.util.Map; |
||||
|
||||
/** |
||||
* An implementation of an {@link OAuth2UserService} that uses the <b>Nimbus OAuth 2.0 SDK</b> internally. |
||||
* |
||||
* <p> |
||||
* This implementation uses a <code>Map</code> of <code>Converter</code>'s <i>keyed</i> by <code>URI</code>. |
||||
* The <code>URI</code> represents the <i>UserInfo Endpoint</i> address and the mapped <code>Converter</code> |
||||
* is capable of converting the <i>UserInfo Response</i> to either an |
||||
* {@link OAuth2User} (for a standard <i>OAuth 2.0 Provider</i>) or |
||||
* {@link UserInfo} (for an <i>OpenID Connect 1.0 Provider</i>). |
||||
* |
||||
* @author Joe Grandja |
||||
* @since 5.0 |
||||
* @see OAuth2AuthenticationToken |
||||
* @see AuthenticatedPrincipal |
||||
* @see OAuth2User |
||||
* @see UserInfo |
||||
* @see Converter |
||||
* @see <a target="_blank" href="https://connect2id.com/products/nimbus-oauth-openid-connect-sdk">Nimbus OAuth 2.0 SDK</a> |
||||
*/ |
||||
public class NimbusOAuth2UserService implements OAuth2UserService { |
||||
private static final String INVALID_USER_INFO_RESPONSE_ERROR_CODE = "invalid_user_info_response"; |
||||
private final Map<URI, Converter<ClientHttpResponse, ? extends OAuth2User>> userInfoTypeConverters; |
||||
|
||||
public NimbusOAuth2UserService(Map<URI, Converter<ClientHttpResponse, ? extends OAuth2User>> userInfoTypeConverters) { |
||||
Assert.notEmpty(userInfoTypeConverters, "userInfoTypeConverters cannot be empty"); |
||||
this.userInfoTypeConverters = new HashMap<>(userInfoTypeConverters); |
||||
} |
||||
|
||||
@Override |
||||
public OAuth2User loadUser(OAuth2AuthenticationToken token) throws OAuth2AuthenticationException { |
||||
OAuth2User user; |
||||
|
||||
try { |
||||
ClientRegistration clientRegistration = token.getClientRegistration(); |
||||
|
||||
URI userInfoUri; |
||||
try { |
||||
userInfoUri = new URI(clientRegistration.getProviderDetails().getUserInfoUri()); |
||||
} catch (Exception ex) { |
||||
throw new IllegalArgumentException("An error occurred parsing the userInfo URI: " + |
||||
clientRegistration.getProviderDetails().getUserInfoUri(), ex); |
||||
} |
||||
|
||||
Converter<ClientHttpResponse, ? extends OAuth2User> userInfoConverter = this.userInfoTypeConverters.get(userInfoUri); |
||||
if (userInfoConverter == null) { |
||||
throw new IllegalArgumentException("There is no available User Info converter for " + userInfoUri.toString()); |
||||
} |
||||
|
||||
BearerAccessToken accessToken = new BearerAccessToken(token.getAccessToken().getTokenValue()); |
||||
|
||||
// Request the User Info
|
||||
UserInfoRequest userInfoRequest = new UserInfoRequest(userInfoUri, accessToken); |
||||
HTTPRequest httpRequest = userInfoRequest.toHTTPRequest(); |
||||
httpRequest.setAccept(MediaType.APPLICATION_JSON_VALUE); |
||||
HTTPResponse httpResponse = httpRequest.send(); |
||||
|
||||
if (httpResponse.getStatusCode() != HTTPResponse.SC_OK) { |
||||
UserInfoErrorResponse userInfoErrorResponse = UserInfoErrorResponse.parse(httpResponse); |
||||
ErrorObject errorObject = userInfoErrorResponse.getErrorObject(); |
||||
OAuth2Error oauth2Error = new OAuth2Error(errorObject.getCode(), errorObject.getDescription(), |
||||
(errorObject.getURI() != null ? errorObject.getURI().toString() : null)); |
||||
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); |
||||
} |
||||
|
||||
user = userInfoConverter.convert(new NimbusClientHttpResponse(httpResponse)); |
||||
|
||||
} catch (ParseException ex) { |
||||
// This error occurs if the User Info Response is not well-formed or invalid
|
||||
throw new OAuth2AuthenticationException(new OAuth2Error(INVALID_USER_INFO_RESPONSE_ERROR_CODE), ex); |
||||
} catch (IOException ex) { |
||||
// This error occurs when there is a network-related issue
|
||||
throw new AuthenticationServiceException("An error occurred while sending the User Info Request: " + |
||||
ex.getMessage(), ex); |
||||
} |
||||
|
||||
return user; |
||||
} |
||||
} |
||||
@ -0,0 +1,45 @@
@@ -0,0 +1,45 @@
|
||||
/* |
||||
* 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.oauth2.client.web.converter; |
||||
|
||||
import org.springframework.core.convert.converter.Converter; |
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2Parameter; |
||||
import org.springframework.security.oauth2.core.endpoint.AuthorizationCodeAuthorizationResponseAttributes; |
||||
import org.springframework.util.Assert; |
||||
|
||||
import javax.servlet.http.HttpServletRequest; |
||||
|
||||
/** |
||||
* |
||||
* @author Joe Grandja |
||||
* @since 5.0 |
||||
*/ |
||||
public final class AuthorizationCodeAuthorizationResponseAttributesConverter implements Converter<HttpServletRequest, AuthorizationCodeAuthorizationResponseAttributes> { |
||||
|
||||
@Override |
||||
public AuthorizationCodeAuthorizationResponseAttributes convert(HttpServletRequest request) { |
||||
AuthorizationCodeAuthorizationResponseAttributes response; |
||||
|
||||
String code = request.getParameter(OAuth2Parameter.CODE); |
||||
Assert.hasText(code, OAuth2Parameter.CODE + " attribute is required"); |
||||
|
||||
String state = request.getParameter(OAuth2Parameter.STATE); |
||||
|
||||
response = new AuthorizationCodeAuthorizationResponseAttributes(code, state); |
||||
|
||||
return response; |
||||
} |
||||
} |
||||
@ -0,0 +1,53 @@
@@ -0,0 +1,53 @@
|
||||
/* |
||||
* 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.oauth2.client.web.converter; |
||||
|
||||
import org.springframework.core.convert.converter.Converter; |
||||
import org.springframework.security.oauth2.core.endpoint.ErrorResponseAttributes; |
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2Parameter; |
||||
import org.springframework.util.StringUtils; |
||||
|
||||
import javax.servlet.http.HttpServletRequest; |
||||
|
||||
/** |
||||
* |
||||
* @author Joe Grandja |
||||
* @since 5.0 |
||||
*/ |
||||
public final class ErrorResponseAttributesConverter implements Converter<HttpServletRequest, ErrorResponseAttributes> { |
||||
|
||||
@Override |
||||
public ErrorResponseAttributes convert(HttpServletRequest request) { |
||||
ErrorResponseAttributes response; |
||||
|
||||
String errorCode = request.getParameter(OAuth2Parameter.ERROR); |
||||
if (!StringUtils.hasText(errorCode)) { |
||||
return null; |
||||
} |
||||
|
||||
String description = request.getParameter(OAuth2Parameter.ERROR_DESCRIPTION); |
||||
String uri = request.getParameter(OAuth2Parameter.ERROR_URI); |
||||
String state = request.getParameter(OAuth2Parameter.STATE); |
||||
|
||||
response = ErrorResponseAttributes.withErrorCode(errorCode) |
||||
.description(description) |
||||
.uri(uri) |
||||
.state(state) |
||||
.build(); |
||||
|
||||
return response; |
||||
} |
||||
} |
||||
@ -0,0 +1,254 @@
@@ -0,0 +1,254 @@
|
||||
/* |
||||
* 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.oauth2.client.authentication; |
||||
|
||||
import org.junit.Test; |
||||
import org.mockito.ArgumentCaptor; |
||||
import org.springframework.mock.web.MockHttpServletRequest; |
||||
import org.springframework.mock.web.MockHttpServletResponse; |
||||
import org.springframework.security.authentication.AuthenticationManager; |
||||
import org.springframework.security.authentication.TestingAuthenticationToken; |
||||
import org.springframework.security.core.Authentication; |
||||
import org.springframework.security.core.AuthenticationException; |
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration; |
||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; |
||||
import org.springframework.security.oauth2.core.OAuth2Error; |
||||
import org.springframework.security.oauth2.core.endpoint.AuthorizationRequestAttributes; |
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2Parameter; |
||||
import org.springframework.security.web.authentication.AuthenticationFailureHandler; |
||||
import org.springframework.security.web.authentication.AuthenticationSuccessHandler; |
||||
|
||||
import javax.servlet.FilterChain; |
||||
import javax.servlet.http.HttpServletRequest; |
||||
import javax.servlet.http.HttpServletResponse; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.mockito.Matchers.any; |
||||
import static org.mockito.Mockito.*; |
||||
import static org.springframework.security.oauth2.client.authentication.TestUtil.*; |
||||
|
||||
/** |
||||
* Tests {@link AuthorizationCodeAuthenticationProcessingFilter}. |
||||
* |
||||
* @author Joe Grandja |
||||
*/ |
||||
public class AuthorizationCodeAuthenticationProcessingFilterTests { |
||||
|
||||
@Test |
||||
public void doFilterWhenNotAuthorizationCodeResponseThenContinueChain() throws Exception { |
||||
ClientRegistration clientRegistration = googleClientRegistration(); |
||||
|
||||
AuthorizationCodeAuthenticationProcessingFilter filter = spy(setupFilter(clientRegistration)); |
||||
|
||||
String requestURI = "/path"; |
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestURI); |
||||
request.setServletPath(requestURI); |
||||
MockHttpServletResponse response = new MockHttpServletResponse(); |
||||
FilterChain filterChain = mock(FilterChain.class); |
||||
|
||||
filter.doFilter(request, response, filterChain); |
||||
|
||||
verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class)); |
||||
verify(filter, never()).attemptAuthentication(any(HttpServletRequest.class), any(HttpServletResponse.class)); |
||||
} |
||||
|
||||
@Test |
||||
public void doFilterWhenAuthorizationCodeErrorResponseThenAuthenticationFailureHandlerIsCalled() throws Exception { |
||||
ClientRegistration clientRegistration = githubClientRegistration(); |
||||
|
||||
AuthorizationCodeAuthenticationProcessingFilter filter = spy(setupFilter(clientRegistration)); |
||||
AuthenticationFailureHandler failureHandler = mock(AuthenticationFailureHandler.class); |
||||
filter.setAuthenticationFailureHandler(failureHandler); |
||||
|
||||
MockHttpServletRequest request = this.setupRequest(clientRegistration); |
||||
String errorCode = OAuth2Error.INVALID_GRANT_ERROR_CODE; |
||||
request.addParameter(OAuth2Parameter.ERROR, errorCode); |
||||
request.addParameter(OAuth2Parameter.STATE, "some state"); |
||||
MockHttpServletResponse response = new MockHttpServletResponse(); |
||||
FilterChain filterChain = mock(FilterChain.class); |
||||
|
||||
filter.doFilter(request, response, filterChain); |
||||
|
||||
verify(filter).attemptAuthentication(any(HttpServletRequest.class), any(HttpServletResponse.class)); |
||||
verify(failureHandler).onAuthenticationFailure(any(HttpServletRequest.class), any(HttpServletResponse.class), |
||||
any(AuthenticationException.class)); |
||||
} |
||||
|
||||
@Test |
||||
public void doFilterWhenAuthorizationCodeSuccessResponseThenAuthenticationSuccessHandlerIsCalled() throws Exception { |
||||
TestingAuthenticationToken authentication = new TestingAuthenticationToken("joe", "password", "user", "admin"); |
||||
AuthenticationManager authenticationManager = mock(AuthenticationManager.class); |
||||
when(authenticationManager.authenticate(any(Authentication.class))).thenReturn(authentication); |
||||
|
||||
ClientRegistration clientRegistration = githubClientRegistration(); |
||||
|
||||
AuthorizationCodeAuthenticationProcessingFilter filter = spy(setupFilter(authenticationManager, clientRegistration)); |
||||
AuthenticationSuccessHandler successHandler = mock(AuthenticationSuccessHandler.class); |
||||
filter.setAuthenticationSuccessHandler(successHandler); |
||||
AuthorizationRequestRepository authorizationRequestRepository = new HttpSessionAuthorizationRequestRepository(); |
||||
filter.setAuthorizationRequestRepository(authorizationRequestRepository); |
||||
|
||||
MockHttpServletRequest request = this.setupRequest(clientRegistration); |
||||
String authCode = "some code"; |
||||
String state = "some state"; |
||||
request.addParameter(OAuth2Parameter.CODE, authCode); |
||||
request.addParameter(OAuth2Parameter.STATE, state); |
||||
setupAuthorizationRequest(authorizationRequestRepository, request, clientRegistration, state); |
||||
MockHttpServletResponse response = new MockHttpServletResponse(); |
||||
FilterChain filterChain = mock(FilterChain.class); |
||||
|
||||
filter.doFilter(request, response, filterChain); |
||||
|
||||
verify(filter).attemptAuthentication(any(HttpServletRequest.class), any(HttpServletResponse.class)); |
||||
|
||||
ArgumentCaptor<Authentication> authenticationArgCaptor = ArgumentCaptor.forClass(Authentication.class); |
||||
verify(successHandler).onAuthenticationSuccess(any(HttpServletRequest.class), any(HttpServletResponse.class), |
||||
authenticationArgCaptor.capture()); |
||||
assertThat(authenticationArgCaptor.getValue()).isEqualTo(authentication); |
||||
} |
||||
|
||||
@Test |
||||
public void doFilterWhenAuthorizationCodeSuccessResponseAndNoMatchingAuthorizationRequestThenThrowOAuth2AuthenticationExceptionAuthorizationRequestNotFound() throws Exception { |
||||
ClientRegistration clientRegistration = githubClientRegistration(); |
||||
|
||||
AuthorizationCodeAuthenticationProcessingFilter filter = spy(setupFilter(clientRegistration)); |
||||
AuthenticationFailureHandler failureHandler = mock(AuthenticationFailureHandler.class); |
||||
filter.setAuthenticationFailureHandler(failureHandler); |
||||
|
||||
MockHttpServletRequest request = this.setupRequest(clientRegistration); |
||||
String authCode = "some code"; |
||||
String state = "some state"; |
||||
request.addParameter(OAuth2Parameter.CODE, authCode); |
||||
request.addParameter(OAuth2Parameter.STATE, state); |
||||
MockHttpServletResponse response = new MockHttpServletResponse(); |
||||
FilterChain filterChain = mock(FilterChain.class); |
||||
|
||||
filter.doFilter(request, response, filterChain); |
||||
|
||||
verifyThrowsOAuth2AuthenticationExceptionWithErrorCode(filter, failureHandler, "authorization_request_not_found"); |
||||
} |
||||
|
||||
@Test |
||||
public void doFilterWhenAuthorizationCodeSuccessResponseWithInvalidStateParamThenThrowOAuth2AuthenticationExceptionInvalidStateParameter() throws Exception { |
||||
ClientRegistration clientRegistration = githubClientRegistration(); |
||||
|
||||
AuthorizationCodeAuthenticationProcessingFilter filter = spy(setupFilter(clientRegistration)); |
||||
AuthenticationFailureHandler failureHandler = mock(AuthenticationFailureHandler.class); |
||||
filter.setAuthenticationFailureHandler(failureHandler); |
||||
AuthorizationRequestRepository authorizationRequestRepository = new HttpSessionAuthorizationRequestRepository(); |
||||
filter.setAuthorizationRequestRepository(authorizationRequestRepository); |
||||
|
||||
MockHttpServletRequest request = this.setupRequest(clientRegistration); |
||||
String authCode = "some code"; |
||||
String state = "some other state"; |
||||
request.addParameter(OAuth2Parameter.CODE, authCode); |
||||
request.addParameter(OAuth2Parameter.STATE, state); |
||||
setupAuthorizationRequest(authorizationRequestRepository, request, clientRegistration, "some state"); |
||||
MockHttpServletResponse response = new MockHttpServletResponse(); |
||||
FilterChain filterChain = mock(FilterChain.class); |
||||
|
||||
filter.doFilter(request, response, filterChain); |
||||
|
||||
verifyThrowsOAuth2AuthenticationExceptionWithErrorCode(filter, failureHandler, "invalid_state_parameter"); |
||||
} |
||||
|
||||
@Test |
||||
public void doFilterWhenAuthorizationCodeSuccessResponseWithInvalidRedirectUriParamThenThrowOAuth2AuthenticationExceptionInvalidRedirectUriParameter() throws Exception { |
||||
ClientRegistration clientRegistration = githubClientRegistration(); |
||||
|
||||
AuthorizationCodeAuthenticationProcessingFilter filter = spy(setupFilter(clientRegistration)); |
||||
AuthenticationFailureHandler failureHandler = mock(AuthenticationFailureHandler.class); |
||||
filter.setAuthenticationFailureHandler(failureHandler); |
||||
AuthorizationRequestRepository authorizationRequestRepository = new HttpSessionAuthorizationRequestRepository(); |
||||
filter.setAuthorizationRequestRepository(authorizationRequestRepository); |
||||
|
||||
MockHttpServletRequest request = this.setupRequest(clientRegistration); |
||||
request.setRequestURI(request.getRequestURI() + "-other"); |
||||
String authCode = "some code"; |
||||
String state = "some state"; |
||||
request.addParameter(OAuth2Parameter.CODE, authCode); |
||||
request.addParameter(OAuth2Parameter.STATE, state); |
||||
setupAuthorizationRequest(authorizationRequestRepository, request, clientRegistration, state); |
||||
MockHttpServletResponse response = new MockHttpServletResponse(); |
||||
FilterChain filterChain = mock(FilterChain.class); |
||||
|
||||
filter.doFilter(request, response, filterChain); |
||||
|
||||
verifyThrowsOAuth2AuthenticationExceptionWithErrorCode(filter, failureHandler, "invalid_redirect_uri_parameter"); |
||||
} |
||||
|
||||
private void verifyThrowsOAuth2AuthenticationExceptionWithErrorCode(AuthorizationCodeAuthenticationProcessingFilter filter, |
||||
AuthenticationFailureHandler failureHandler, |
||||
String errorCode) throws Exception { |
||||
|
||||
verify(filter).attemptAuthentication(any(HttpServletRequest.class), any(HttpServletResponse.class)); |
||||
|
||||
ArgumentCaptor<AuthenticationException> authenticationExceptionArgCaptor = |
||||
ArgumentCaptor.forClass(AuthenticationException.class); |
||||
verify(failureHandler).onAuthenticationFailure(any(HttpServletRequest.class), any(HttpServletResponse.class), |
||||
authenticationExceptionArgCaptor.capture()); |
||||
assertThat(authenticationExceptionArgCaptor.getValue()).isInstanceOf(OAuth2AuthenticationException.class); |
||||
OAuth2AuthenticationException oauth2AuthenticationException = |
||||
(OAuth2AuthenticationException)authenticationExceptionArgCaptor.getValue(); |
||||
assertThat(oauth2AuthenticationException.getErrorObject()).isNotNull(); |
||||
assertThat(oauth2AuthenticationException.getErrorObject().getErrorCode()).isEqualTo(errorCode); |
||||
} |
||||
|
||||
private AuthorizationCodeAuthenticationProcessingFilter setupFilter(ClientRegistration... clientRegistrations) throws Exception { |
||||
AuthenticationManager authenticationManager = mock(AuthenticationManager.class); |
||||
|
||||
return setupFilter(authenticationManager, clientRegistrations); |
||||
} |
||||
|
||||
private AuthorizationCodeAuthenticationProcessingFilter setupFilter( |
||||
AuthenticationManager authenticationManager, ClientRegistration... clientRegistrations) throws Exception { |
||||
|
||||
ClientRegistrationRepository clientRegistrationRepository = clientRegistrationRepository(clientRegistrations); |
||||
|
||||
AuthorizationCodeAuthenticationProcessingFilter filter = new AuthorizationCodeAuthenticationProcessingFilter(); |
||||
filter.setClientRegistrationRepository(clientRegistrationRepository); |
||||
filter.setAuthenticationManager(authenticationManager); |
||||
|
||||
return filter; |
||||
} |
||||
|
||||
private void setupAuthorizationRequest(AuthorizationRequestRepository authorizationRequestRepository, |
||||
HttpServletRequest request, |
||||
ClientRegistration clientRegistration, |
||||
String state) { |
||||
|
||||
AuthorizationRequestAttributes authorizationRequestAttributes = |
||||
AuthorizationRequestAttributes.withAuthorizationCode() |
||||
.clientId(clientRegistration.getClientId()) |
||||
.authorizeUri(clientRegistration.getProviderDetails().getAuthorizationUri()) |
||||
.redirectUri(clientRegistration.getRedirectUri()) |
||||
.scopes(clientRegistration.getScopes()) |
||||
.state(state) |
||||
.build(); |
||||
|
||||
authorizationRequestRepository.saveAuthorizationRequest(authorizationRequestAttributes, request); |
||||
} |
||||
|
||||
private MockHttpServletRequest setupRequest(ClientRegistration clientRegistration) { |
||||
String requestURI = AUTHORIZE_BASE_URI + "/" + clientRegistration.getClientAlias(); |
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestURI); |
||||
request.setScheme(DEFAULT_SCHEME); |
||||
request.setServerName(DEFAULT_SERVER_NAME); |
||||
request.setServerPort(DEFAULT_SERVER_PORT); |
||||
request.setServletPath(requestURI); |
||||
return request; |
||||
} |
||||
} |
||||
@ -0,0 +1,142 @@
@@ -0,0 +1,142 @@
|
||||
/* |
||||
* 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.oauth2.client.authentication; |
||||
|
||||
import org.junit.Test; |
||||
import org.springframework.mock.web.MockHttpServletRequest; |
||||
import org.springframework.mock.web.MockHttpServletResponse; |
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration; |
||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; |
||||
import org.springframework.security.oauth2.core.endpoint.AuthorizationRequestAttributes; |
||||
|
||||
import javax.servlet.FilterChain; |
||||
import javax.servlet.http.HttpServletRequest; |
||||
import javax.servlet.http.HttpServletResponse; |
||||
import java.net.URI; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.mockito.Mockito.*; |
||||
import static org.springframework.security.oauth2.client.authentication.TestUtil.*; |
||||
|
||||
/** |
||||
* Tests {@link AuthorizationCodeRequestRedirectFilter}. |
||||
* |
||||
* @author Joe Grandja |
||||
*/ |
||||
public class AuthorizationCodeRequestRedirectFilterTests { |
||||
|
||||
@Test(expected = IllegalArgumentException.class) |
||||
public void constructorWhenClientRegistrationRepositoryIsNullThenThrowIllegalArgumentException() { |
||||
new AuthorizationCodeRequestRedirectFilter(null, mock(AuthorizationRequestUriBuilder.class)); |
||||
} |
||||
|
||||
@Test(expected = IllegalArgumentException.class) |
||||
public void constructorWhenAuthorizationRequestUriBuilderIsNullThenThrowIllegalArgumentException() { |
||||
new AuthorizationCodeRequestRedirectFilter(mock(ClientRegistrationRepository.class), null); |
||||
} |
||||
|
||||
@Test |
||||
public void doFilterWhenRequestDoesNotMatchClientThenContinueChain() throws Exception { |
||||
ClientRegistration clientRegistration = googleClientRegistration(); |
||||
String authorizationUri = clientRegistration.getProviderDetails().getAuthorizationUri().toString(); |
||||
AuthorizationCodeRequestRedirectFilter filter = |
||||
setupFilter(authorizationUri, clientRegistration); |
||||
|
||||
String requestURI = "/path"; |
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestURI); |
||||
request.setServletPath(requestURI); |
||||
MockHttpServletResponse response = new MockHttpServletResponse(); |
||||
FilterChain filterChain = mock(FilterChain.class); |
||||
|
||||
filter.doFilter(request, response, filterChain); |
||||
|
||||
verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class)); |
||||
} |
||||
|
||||
@Test |
||||
public void doFilterWhenRequestMatchesClientThenRedirectForAuthorization() throws Exception { |
||||
ClientRegistration clientRegistration = googleClientRegistration(); |
||||
String authorizationUri = clientRegistration.getProviderDetails().getAuthorizationUri().toString(); |
||||
AuthorizationCodeRequestRedirectFilter filter = |
||||
setupFilter(authorizationUri, clientRegistration); |
||||
|
||||
String requestUri = AUTHORIZATION_BASE_URI + "/" + clientRegistration.getClientAlias(); |
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri); |
||||
request.setServletPath(requestUri); |
||||
MockHttpServletResponse response = new MockHttpServletResponse(); |
||||
FilterChain filterChain = mock(FilterChain.class); |
||||
|
||||
filter.doFilter(request, response, filterChain); |
||||
|
||||
verifyZeroInteractions(filterChain); // Request should not proceed up the chain
|
||||
|
||||
assertThat(response.getRedirectedUrl()).isEqualTo(authorizationUri); |
||||
} |
||||
|
||||
@Test |
||||
public void doFilterWhenRequestMatchesClientThenAuthorizationRequestSavedInSession() throws Exception { |
||||
ClientRegistration clientRegistration = githubClientRegistration(); |
||||
String authorizationUri = clientRegistration.getProviderDetails().getAuthorizationUri().toString(); |
||||
AuthorizationCodeRequestRedirectFilter filter = |
||||
setupFilter(authorizationUri, clientRegistration); |
||||
AuthorizationRequestRepository authorizationRequestRepository = new HttpSessionAuthorizationRequestRepository(); |
||||
filter.setAuthorizationRequestRepository(authorizationRequestRepository); |
||||
|
||||
String requestUri = AUTHORIZATION_BASE_URI + "/" + clientRegistration.getClientAlias(); |
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri); |
||||
request.setServletPath(requestUri); |
||||
MockHttpServletResponse response = new MockHttpServletResponse(); |
||||
FilterChain filterChain = mock(FilterChain.class); |
||||
|
||||
filter.doFilter(request, response, filterChain); |
||||
|
||||
verifyZeroInteractions(filterChain); // Request should not proceed up the chain
|
||||
|
||||
// The authorization request attributes are saved in the session before the redirect happens
|
||||
AuthorizationRequestAttributes authorizationRequestAttributes = |
||||
authorizationRequestRepository.loadAuthorizationRequest(request); |
||||
assertThat(authorizationRequestAttributes).isNotNull(); |
||||
|
||||
assertThat(authorizationRequestAttributes.getAuthorizeUri()).isNotNull(); |
||||
assertThat(authorizationRequestAttributes.getGrantType()).isNotNull(); |
||||
assertThat(authorizationRequestAttributes.getResponseType()).isNotNull(); |
||||
assertThat(authorizationRequestAttributes.getClientId()).isNotNull(); |
||||
assertThat(authorizationRequestAttributes.getRedirectUri()).isNotNull(); |
||||
assertThat(authorizationRequestAttributes.getScopes()).isNotNull(); |
||||
assertThat(authorizationRequestAttributes.getState()).isNotNull(); |
||||
} |
||||
|
||||
private AuthorizationCodeRequestRedirectFilter setupFilter(String authorizationUri, |
||||
ClientRegistration... clientRegistrations) throws Exception { |
||||
|
||||
AuthorizationRequestUriBuilder authorizationUriBuilder = mock(AuthorizationRequestUriBuilder.class); |
||||
URI authorizationURI = new URI(authorizationUri); |
||||
when(authorizationUriBuilder.build(any(AuthorizationRequestAttributes.class))).thenReturn(authorizationURI); |
||||
|
||||
return setupFilter(authorizationUriBuilder, clientRegistrations); |
||||
} |
||||
|
||||
private AuthorizationCodeRequestRedirectFilter setupFilter(AuthorizationRequestUriBuilder authorizationUriBuilder, |
||||
ClientRegistration... clientRegistrations) throws Exception { |
||||
|
||||
ClientRegistrationRepository clientRegistrationRepository = clientRegistrationRepository(clientRegistrations); |
||||
|
||||
AuthorizationCodeRequestRedirectFilter filter = new AuthorizationCodeRequestRedirectFilter( |
||||
clientRegistrationRepository, authorizationUriBuilder); |
||||
|
||||
return filter; |
||||
} |
||||
} |
||||
@ -0,0 +1,81 @@
@@ -0,0 +1,81 @@
|
||||
/* |
||||
* 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.oauth2.client.authentication; |
||||
|
||||
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.security.oauth2.core.AuthorizationGrantType; |
||||
|
||||
import java.util.Arrays; |
||||
import java.util.stream.Collectors; |
||||
|
||||
/** |
||||
* @author Joe Grandja |
||||
*/ |
||||
class TestUtil { |
||||
static final String DEFAULT_SCHEME = "https"; |
||||
static final String DEFAULT_SERVER_NAME = "localhost"; |
||||
static final int DEFAULT_SERVER_PORT = 8080; |
||||
static final String DEFAULT_SERVER_URL = DEFAULT_SCHEME + "://" + DEFAULT_SERVER_NAME + ":" + DEFAULT_SERVER_PORT; |
||||
static final String AUTHORIZATION_BASE_URI = "/oauth2/authorization/code"; |
||||
static final String AUTHORIZE_BASE_URI = "/oauth2/authorize/code"; |
||||
static final String GOOGLE_CLIENT_ALIAS = "google"; |
||||
static final String GITHUB_CLIENT_ALIAS = "github"; |
||||
|
||||
static ClientRegistrationRepository clientRegistrationRepository(ClientRegistration... clientRegistrations) { |
||||
return new InMemoryClientRegistrationRepository(Arrays.asList(clientRegistrations)); |
||||
} |
||||
|
||||
static ClientRegistration googleClientRegistration() { |
||||
return googleClientRegistration(DEFAULT_SERVER_URL + AUTHORIZE_BASE_URI + "/" + GOOGLE_CLIENT_ALIAS); |
||||
} |
||||
|
||||
static ClientRegistration googleClientRegistration(String redirectUri) { |
||||
ClientRegistrationProperties clientRegistrationProperties = new ClientRegistrationProperties(); |
||||
clientRegistrationProperties.setClientId("google-client-id"); |
||||
clientRegistrationProperties.setClientSecret("secret"); |
||||
clientRegistrationProperties.setAuthorizedGrantType(AuthorizationGrantType.AUTHORIZATION_CODE); |
||||
clientRegistrationProperties.setClientName("Google Client"); |
||||
clientRegistrationProperties.setClientAlias(GOOGLE_CLIENT_ALIAS); |
||||
clientRegistrationProperties.setAuthorizationUri("https://accounts.google.com/o/oauth2/auth"); |
||||
clientRegistrationProperties.setTokenUri("https://accounts.google.com/o/oauth2/token"); |
||||
clientRegistrationProperties.setUserInfoUri("https://www.googleapis.com/oauth2/v3/userinfo"); |
||||
clientRegistrationProperties.setRedirectUri(redirectUri); |
||||
clientRegistrationProperties.setScopes(Arrays.stream(new String[] {"openid", "email", "profile"}).collect(Collectors.toSet())); |
||||
return new ClientRegistration.Builder(clientRegistrationProperties).build(); |
||||
} |
||||
|
||||
static ClientRegistration githubClientRegistration() { |
||||
return githubClientRegistration(DEFAULT_SERVER_URL + AUTHORIZE_BASE_URI + "/" + GITHUB_CLIENT_ALIAS); |
||||
} |
||||
|
||||
static ClientRegistration githubClientRegistration(String redirectUri) { |
||||
ClientRegistrationProperties clientRegistrationProperties = new ClientRegistrationProperties(); |
||||
clientRegistrationProperties.setClientId("github-client-id"); |
||||
clientRegistrationProperties.setClientSecret("secret"); |
||||
clientRegistrationProperties.setAuthorizedGrantType(AuthorizationGrantType.AUTHORIZATION_CODE); |
||||
clientRegistrationProperties.setClientName("GitHub Client"); |
||||
clientRegistrationProperties.setClientAlias(GITHUB_CLIENT_ALIAS); |
||||
clientRegistrationProperties.setAuthorizationUri("https://github.com/login/oauth/authorize"); |
||||
clientRegistrationProperties.setTokenUri("https://github.com/login/oauth/access_token"); |
||||
clientRegistrationProperties.setUserInfoUri("https://api.github.com/user"); |
||||
clientRegistrationProperties.setRedirectUri(redirectUri); |
||||
clientRegistrationProperties.setScopes(Arrays.stream(new String[] {"user"}).collect(Collectors.toSet())); |
||||
return new ClientRegistration.Builder(clientRegistrationProperties).build(); |
||||
} |
||||
} |
||||
@ -0,0 +1,138 @@
@@ -0,0 +1,138 @@
|
||||
<?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-oauth2-core</artifactId> |
||||
<version>5.0.0.BUILD-SNAPSHOT</version> |
||||
<name>spring-security-oauth2-core</name> |
||||
<description>spring-security-oauth2-core</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> |
||||
</dependencies> |
||||
</dependencyManagement> |
||||
<dependencies> |
||||
<dependency> |
||||
<groupId>org.springframework.security</groupId> |
||||
<artifactId>spring-security-core</artifactId> |
||||
<version>5.0.0.BUILD-SNAPSHOT</version> |
||||
<scope>compile</scope> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>org.springframework</groupId> |
||||
<artifactId>spring-core</artifactId> |
||||
<scope>compile</scope> |
||||
<exclusions> |
||||
<exclusion> |
||||
<artifactId>commons-logging</artifactId> |
||||
<groupId>commons-logging</groupId> |
||||
</exclusion> |
||||
</exclusions> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>org.springframework</groupId> |
||||
<artifactId>spring-web</artifactId> |
||||
<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>javax.servlet</groupId> |
||||
<artifactId>javax.servlet-api</artifactId> |
||||
<version>3.1.0</version> |
||||
<scope>provided</scope> |
||||
</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>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</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,9 @@
@@ -0,0 +1,9 @@
|
||||
apply plugin: 'io.spring.convention.spring-module' |
||||
|
||||
dependencies { |
||||
compile project(':spring-security-core') |
||||
compile springCoreDependency |
||||
compile 'org.springframework:spring-web' |
||||
|
||||
provided 'javax.servlet:javax.servlet-api' |
||||
} |
||||
@ -0,0 +1,59 @@
@@ -0,0 +1,59 @@
|
||||
/* |
||||
* 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.oauth2.core; |
||||
|
||||
import org.springframework.security.core.SpringSecurityCoreVersion; |
||||
import org.springframework.util.Assert; |
||||
|
||||
import java.io.Serializable; |
||||
import java.time.Instant; |
||||
|
||||
/** |
||||
* Base class for <i>Security Token</i> implementations. |
||||
* |
||||
* <p> |
||||
* It is highly recommended that implementations be immutable. |
||||
* |
||||
* @author Joe Grandja |
||||
* @since 5.0 |
||||
*/ |
||||
public abstract class AbstractToken implements Serializable { |
||||
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID; |
||||
private final String tokenValue; |
||||
private final Instant issuedAt; |
||||
private final Instant expiresAt; |
||||
|
||||
protected AbstractToken(String tokenValue, Instant issuedAt, Instant expiresAt) { |
||||
Assert.hasLength(tokenValue, "tokenValue cannot be empty"); |
||||
Assert.notNull(issuedAt, "issuedAt cannot be null"); |
||||
Assert.notNull(expiresAt, "expiresAt cannot be null"); |
||||
this.tokenValue = tokenValue; |
||||
this.issuedAt = issuedAt; |
||||
this.expiresAt = expiresAt; |
||||
} |
||||
|
||||
public String getTokenValue() { |
||||
return this.tokenValue; |
||||
} |
||||
|
||||
public Instant getIssuedAt() { |
||||
return this.issuedAt; |
||||
} |
||||
|
||||
public Instant getExpiresAt() { |
||||
return this.expiresAt; |
||||
} |
||||
} |
||||
@ -0,0 +1,88 @@
@@ -0,0 +1,88 @@
|
||||
/* |
||||
* 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.oauth2.core; |
||||
|
||||
import org.springframework.util.Assert; |
||||
|
||||
import java.time.Instant; |
||||
import java.util.Collections; |
||||
import java.util.Map; |
||||
import java.util.Set; |
||||
|
||||
/** |
||||
* An implementation of an {@link AbstractToken} representing an <i>OAuth 2.0 Access Token</i>. |
||||
* |
||||
* <p> |
||||
* An access token is a credential that represents an authorization |
||||
* granted by the resource owner to the client. |
||||
* It is primarily used by the client to access protected resources on either a |
||||
* resource server or the authorization server that originally issued the access token. |
||||
* |
||||
* @author Joe Grandja |
||||
* @since 5.0 |
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-1.4">Section 1.4 Access Token</a> |
||||
*/ |
||||
public class AccessToken extends AbstractToken { |
||||
private final TokenType tokenType; |
||||
private final Set<String> scopes; |
||||
private final Map<String,Object> additionalParameters; |
||||
|
||||
public enum TokenType { |
||||
BEARER("Bearer"); |
||||
|
||||
private final String value; |
||||
|
||||
TokenType(String value) { |
||||
this.value = value; |
||||
} |
||||
|
||||
public String value() { |
||||
return this.value; |
||||
} |
||||
} |
||||
|
||||
public AccessToken(TokenType tokenType, String tokenValue, Instant issuedAt, Instant expiresAt) { |
||||
this(tokenType, tokenValue, issuedAt, expiresAt, Collections.emptySet()); |
||||
} |
||||
|
||||
public AccessToken(TokenType tokenType, String tokenValue, Instant issuedAt, Instant expiresAt, Set<String> scopes) { |
||||
this(tokenType, tokenValue, issuedAt, expiresAt, scopes, Collections.emptyMap()); |
||||
} |
||||
|
||||
public AccessToken(TokenType tokenType, String tokenValue, Instant issuedAt, Instant expiresAt, |
||||
Set<String> scopes, Map<String,Object> additionalParameters) { |
||||
|
||||
super(tokenValue, issuedAt, expiresAt); |
||||
Assert.notNull(tokenType, "tokenType cannot be null"); |
||||
this.tokenType = tokenType; |
||||
this.scopes = Collections.unmodifiableSet( |
||||
scopes != null ? scopes : Collections.emptySet()); |
||||
this.additionalParameters = Collections.unmodifiableMap( |
||||
additionalParameters != null ? additionalParameters : Collections.emptyMap()); |
||||
} |
||||
|
||||
public TokenType getTokenType() { |
||||
return this.tokenType; |
||||
} |
||||
|
||||
public Set<String> getScopes() { |
||||
return this.scopes; |
||||
} |
||||
|
||||
public Map<String, Object> getAdditionalParameters() { |
||||
return additionalParameters; |
||||
} |
||||
} |
||||
@ -0,0 +1,46 @@
@@ -0,0 +1,46 @@
|
||||
/* |
||||
* 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.oauth2.core; |
||||
|
||||
/** |
||||
* An authorization grant is a credential representing the resource owner's authorization |
||||
* (to access it's protected resources) to the client and used by the client to obtain an access token. |
||||
* |
||||
* <p> |
||||
* The <i>OAuth 2.0 Authorization Framework</i> defines four standard grant types: |
||||
* authorization code, implicit, resource owner password credentials, and client credentials. |
||||
* It also provides an extensibility mechanism for defining additional grant types. |
||||
* |
||||
* <p> |
||||
* <b>NOTE:</b> "authorization code" is currently the only supported grant type. |
||||
* |
||||
* @author Joe Grandja |
||||
* @since 5.0 |
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-1.3">Section 1.3 Authorization Grant</a> |
||||
*/ |
||||
public enum AuthorizationGrantType { |
||||
AUTHORIZATION_CODE("authorization_code"); |
||||
|
||||
private final String value; |
||||
|
||||
AuthorizationGrantType(String value) { |
||||
this.value = value; |
||||
} |
||||
|
||||
public String value() { |
||||
return this.value; |
||||
} |
||||
} |
||||
@ -0,0 +1,38 @@
@@ -0,0 +1,38 @@
|
||||
/* |
||||
* 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.oauth2.core; |
||||
|
||||
/** |
||||
* The available authentication methods used when authenticating the client with the authorization server. |
||||
* |
||||
* @author Joe Grandja |
||||
* @since 5.0 |
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-2.3">Section 2.3 Client Authentication</a> |
||||
*/ |
||||
public enum ClientAuthenticationMethod { |
||||
HEADER("header"), |
||||
FORM("form"); |
||||
|
||||
private final String value; |
||||
|
||||
ClientAuthenticationMethod(String value) { |
||||
this.value = value; |
||||
} |
||||
|
||||
public String value() { |
||||
return this.value; |
||||
} |
||||
} |
||||
@ -0,0 +1,74 @@
@@ -0,0 +1,74 @@
|
||||
/* |
||||
* 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.oauth2.core; |
||||
|
||||
import org.springframework.util.Assert; |
||||
|
||||
/** |
||||
* A representation of an <i>OAuth 2.0 Error</i>. |
||||
* |
||||
* <p> |
||||
* At a minimum, an error response will contain an error code. |
||||
* The error code may be one of the standard codes defined by the specification, |
||||
* or a <i>new</i> code defined in the <i>OAuth Extensions Error Registry</i>, |
||||
* for cases where protocol extensions require additional error code(s) above the standard codes. |
||||
* |
||||
* @author Joe Grandja |
||||
* @since 5.0 |
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-11.4">Section 11.4 OAuth Extensions Error Registry</a> |
||||
*/ |
||||
public class OAuth2Error { |
||||
// Standard error codes
|
||||
public static final String INVALID_REQUEST_ERROR_CODE = "invalid_request"; |
||||
public static final String INVALID_CLIENT_ERROR_CODE = "invalid_client"; |
||||
public static final String INVALID_GRANT_ERROR_CODE = "invalid_grant"; |
||||
public static final String UNAUTHORIZED_CLIENT_ERROR_CODE = "unauthorized_client"; |
||||
public static final String UNSUPPORTED_GRANT_TYPE_ERROR_CODE = "unsupported_grant_type"; |
||||
public static final String INVALID_SCOPE_ERROR_CODE = "invalid_scope"; |
||||
|
||||
private final String errorCode; |
||||
private final String description; |
||||
private final String uri; |
||||
|
||||
public OAuth2Error(String errorCode) { |
||||
this(errorCode, null, null); |
||||
} |
||||
|
||||
public OAuth2Error(String errorCode, String description, String uri) { |
||||
Assert.hasText(errorCode, "errorCode cannot be empty"); |
||||
this.errorCode = errorCode; |
||||
this.description = description; |
||||
this.uri = uri; |
||||
} |
||||
|
||||
public String getErrorCode() { |
||||
return this.errorCode; |
||||
} |
||||
|
||||
public String getDescription() { |
||||
return this.description; |
||||
} |
||||
|
||||
public String getUri() { |
||||
return this.uri; |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return "[" + this.getErrorCode() + "] " + |
||||
(this.getDescription() != null ? this.getDescription() : ""); |
||||
} |
||||
} |
||||
@ -0,0 +1,44 @@
@@ -0,0 +1,44 @@
|
||||
/* |
||||
* 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.oauth2.core.endpoint; |
||||
|
||||
import org.springframework.util.Assert; |
||||
|
||||
/** |
||||
* A representation of an <i>OAuth 2.0 Authorization Response</i> for the authorization code grant type. |
||||
* |
||||
* @author Joe Grandja |
||||
* @since 5.0 |
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.2">Section 4.1.2 Authorization Response</a> |
||||
*/ |
||||
public final class AuthorizationCodeAuthorizationResponseAttributes { |
||||
private final String code; |
||||
private final String state; |
||||
|
||||
public AuthorizationCodeAuthorizationResponseAttributes(String code, String state) { |
||||
Assert.notNull(code, "code cannot be null"); |
||||
this.code = code; |
||||
this.state = state; |
||||
} |
||||
|
||||
public String getCode() { |
||||
return this.code; |
||||
} |
||||
|
||||
public String getState() { |
||||
return this.state; |
||||
} |
||||
} |
||||
@ -0,0 +1,76 @@
@@ -0,0 +1,76 @@
|
||||
/* |
||||
* 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.oauth2.core.endpoint; |
||||
|
||||
import org.springframework.util.Assert; |
||||
|
||||
/** |
||||
* A representation of an <i>OAuth 2.0 Access Token Request</i> for the authorization code grant type. |
||||
* |
||||
* @author Joe Grandja |
||||
* @since 5.0 |
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.3">Section 4.1.3 Access Token Request</a> |
||||
*/ |
||||
public final class AuthorizationCodeTokenRequestAttributes { |
||||
private String code; |
||||
private String clientId; |
||||
private String redirectUri; |
||||
|
||||
private AuthorizationCodeTokenRequestAttributes() { |
||||
} |
||||
|
||||
public String getCode() { |
||||
return this.code; |
||||
} |
||||
|
||||
public String getClientId() { |
||||
return this.clientId; |
||||
} |
||||
|
||||
public String getRedirectUri() { |
||||
return this.redirectUri; |
||||
} |
||||
|
||||
public static Builder withCode(String code) { |
||||
return new Builder(code); |
||||
} |
||||
|
||||
public static class Builder { |
||||
private final AuthorizationCodeTokenRequestAttributes authorizationCodeTokenRequest; |
||||
|
||||
private Builder(String code) { |
||||
Assert.hasText(code, "code cannot be empty"); |
||||
this.authorizationCodeTokenRequest = new AuthorizationCodeTokenRequestAttributes(); |
||||
this.authorizationCodeTokenRequest.code = code; |
||||
} |
||||
|
||||
public Builder clientId(String clientId) { |
||||
Assert.hasText(clientId, "clientId cannot be empty"); |
||||
this.authorizationCodeTokenRequest.clientId = clientId; |
||||
return this; |
||||
} |
||||
|
||||
public Builder redirectUri(String redirectUri) { |
||||
Assert.hasText(redirectUri, "redirectUri cannot be empty"); |
||||
this.authorizationCodeTokenRequest.redirectUri = redirectUri; |
||||
return this; |
||||
} |
||||
|
||||
public AuthorizationCodeTokenRequestAttributes build() { |
||||
return this.authorizationCodeTokenRequest; |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,127 @@
@@ -0,0 +1,127 @@
|
||||
/* |
||||
* 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.oauth2.core.endpoint; |
||||
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType; |
||||
import org.springframework.util.Assert; |
||||
import org.springframework.util.CollectionUtils; |
||||
|
||||
import java.io.Serializable; |
||||
import java.util.Collections; |
||||
import java.util.LinkedHashSet; |
||||
import java.util.Set; |
||||
|
||||
/** |
||||
* A representation of an <i>OAuth 2.0 Authorization Request</i> |
||||
* for the authorization code grant type or implicit grant type. |
||||
* |
||||
* @author Joe Grandja |
||||
* @since 5.0 |
||||
* @see AuthorizationGrantType |
||||
* @see ResponseType |
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.1">Section 4.1.1 Authorization Code Grant Request</a> |
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.2.1">Section 4.2.1 Implicit Grant Request</a> |
||||
*/ |
||||
public final class AuthorizationRequestAttributes implements Serializable { |
||||
private String authorizeUri; |
||||
private AuthorizationGrantType authorizationGrantType; |
||||
private ResponseType responseType; |
||||
private String clientId; |
||||
private String redirectUri; |
||||
private Set<String> scopes; |
||||
private String state; |
||||
|
||||
private AuthorizationRequestAttributes() { |
||||
} |
||||
|
||||
public String getAuthorizeUri() { |
||||
return this.authorizeUri; |
||||
} |
||||
|
||||
public AuthorizationGrantType getGrantType() { |
||||
return this.authorizationGrantType; |
||||
} |
||||
|
||||
public ResponseType getResponseType() { |
||||
return this.responseType; |
||||
} |
||||
|
||||
public String getClientId() { |
||||
return this.clientId; |
||||
} |
||||
|
||||
public String getRedirectUri() { |
||||
return this.redirectUri; |
||||
} |
||||
|
||||
public Set<String> getScopes() { |
||||
return this.scopes; |
||||
} |
||||
|
||||
public String getState() { |
||||
return this.state; |
||||
} |
||||
|
||||
public static Builder withAuthorizationCode() { |
||||
return new Builder(AuthorizationGrantType.AUTHORIZATION_CODE); |
||||
} |
||||
|
||||
public static class Builder { |
||||
private final AuthorizationRequestAttributes authorizationRequest; |
||||
|
||||
private Builder(AuthorizationGrantType authorizationGrantType) { |
||||
Assert.notNull(authorizationGrantType, "authorizationGrantType cannot be null"); |
||||
this.authorizationRequest = new AuthorizationRequestAttributes(); |
||||
this.authorizationRequest.authorizationGrantType = authorizationGrantType; |
||||
if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(authorizationGrantType)) { |
||||
this.authorizationRequest.responseType = ResponseType.CODE; |
||||
} |
||||
} |
||||
|
||||
public Builder authorizeUri(String authorizeUri) { |
||||
Assert.hasText(authorizeUri, "authorizeUri cannot be empty"); |
||||
this.authorizationRequest.authorizeUri = authorizeUri; |
||||
return this; |
||||
} |
||||
|
||||
public Builder clientId(String clientId) { |
||||
Assert.hasText(clientId, "clientId cannot be empty"); |
||||
this.authorizationRequest.clientId = clientId; |
||||
return this; |
||||
} |
||||
|
||||
public Builder redirectUri(String redirectUri) { |
||||
Assert.hasText(redirectUri, "redirectUri cannot be empty"); |
||||
this.authorizationRequest.redirectUri = redirectUri; |
||||
return this; |
||||
} |
||||
|
||||
public Builder scopes(Set<String> scopes) { |
||||
this.authorizationRequest.scopes = Collections.unmodifiableSet( |
||||
CollectionUtils.isEmpty(scopes) ? Collections.emptySet() : new LinkedHashSet<>(scopes)); |
||||
return this; |
||||
} |
||||
|
||||
public Builder state(String state) { |
||||
this.authorizationRequest.state = state; |
||||
return this; |
||||
} |
||||
|
||||
public AuthorizationRequestAttributes build() { |
||||
return this.authorizationRequest; |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,96 @@
@@ -0,0 +1,96 @@
|
||||
/* |
||||
* 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.oauth2.core.endpoint; |
||||
|
||||
import org.springframework.security.oauth2.core.OAuth2Error; |
||||
import org.springframework.util.Assert; |
||||
|
||||
/** |
||||
* A representation of an <i>OAuth 2.0 Error Response</i>. |
||||
* |
||||
* <p> |
||||
* An error response may be returned from either of the following locations: |
||||
* <ul> |
||||
* <li><a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.2.1">Section 4.1.2.1</a> Authorization Code Grant Response</li> |
||||
* <li><a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.2.2.1">Section 4.2.2.1</a> Implicit Grant Response</li> |
||||
* <li><a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-5.2">Section 5.2</a> Access Token Response</li> |
||||
* <li><a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-7.2">Section 7.2</a> Protected Resource Response</li> |
||||
* </ul> |
||||
* |
||||
* @author Joe Grandja |
||||
* @since 5.0 |
||||
*/ |
||||
public final class ErrorResponseAttributes { |
||||
private OAuth2Error errorObject; |
||||
private String state; |
||||
|
||||
private ErrorResponseAttributes() { |
||||
} |
||||
|
||||
public String getErrorCode() { |
||||
return this.errorObject.getErrorCode(); |
||||
} |
||||
|
||||
public String getDescription() { |
||||
return this.errorObject.getDescription(); |
||||
} |
||||
|
||||
public String getUri() { |
||||
return this.errorObject.getUri(); |
||||
} |
||||
|
||||
public String getState() { |
||||
return this.state; |
||||
} |
||||
|
||||
public static Builder withErrorCode(String errorCode) { |
||||
return new Builder(errorCode); |
||||
} |
||||
|
||||
public static class Builder { |
||||
private String errorCode; |
||||
private String description; |
||||
private String uri; |
||||
private String state; |
||||
|
||||
private Builder(String errorCode) { |
||||
Assert.hasText(errorCode, "errorCode cannot be empty"); |
||||
this.errorCode = errorCode; |
||||
} |
||||
|
||||
public Builder description(String description) { |
||||
this.description = description; |
||||
return this; |
||||
} |
||||
|
||||
public Builder uri(String uri) { |
||||
this.uri = uri; |
||||
return this; |
||||
} |
||||
|
||||
public Builder state(String state) { |
||||
this.state = state; |
||||
return this; |
||||
} |
||||
|
||||
public ErrorResponseAttributes build() { |
||||
ErrorResponseAttributes errorResponse = new ErrorResponseAttributes(); |
||||
errorResponse.errorObject = new OAuth2Error(this.errorCode, this.description, this.uri); |
||||
errorResponse.state = this.state; |
||||
return errorResponse; |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,46 @@
@@ -0,0 +1,46 @@
|
||||
/* |
||||
* 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.oauth2.core.endpoint; |
||||
|
||||
/** |
||||
* Standard parameters defined in the OAuth Parameters Registry |
||||
* and used by the authorization endpoint and token endpoint. |
||||
* |
||||
* @author Joe Grandja |
||||
* @since 5.0 |
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-11.2">11.2 OAuth Parameters Registry</a> |
||||
*/ |
||||
public interface OAuth2Parameter { |
||||
|
||||
String RESPONSE_TYPE = "response_type"; |
||||
|
||||
String CLIENT_ID = "client_id"; |
||||
|
||||
String REDIRECT_URI = "redirect_uri"; |
||||
|
||||
String SCOPE = "scope"; |
||||
|
||||
String STATE = "state"; |
||||
|
||||
String CODE = "code"; |
||||
|
||||
String ERROR = "error"; |
||||
|
||||
String ERROR_DESCRIPTION = "error_description"; |
||||
|
||||
String ERROR_URI = "error_uri"; |
||||
|
||||
} |
||||
@ -0,0 +1,46 @@
@@ -0,0 +1,46 @@
|
||||
/* |
||||
* 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.oauth2.core.endpoint; |
||||
|
||||
/** |
||||
* The <i>response_type</i> parameter is consumed by the authorization endpoint which |
||||
* is used by the authorization code grant type and implicit grant type flows. |
||||
* The client sets the <i>response_type</i> parameter with the desired grant type before initiating the authorization request. |
||||
* |
||||
* <p> |
||||
* The <i>response_type</i> parameter value may be one of "code" for requesting an authorization code or |
||||
* "token" for requesting an access token (implicit grant). |
||||
|
||||
* <p> |
||||
* <b>NOTE:</b> "code" is currently the only supported response type. |
||||
* |
||||
* @author Joe Grandja |
||||
* @since 5.0 |
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-3.1.1">Section 3.1.1 Response Type</a> |
||||
*/ |
||||
public enum ResponseType { |
||||
CODE("code"); |
||||
|
||||
private final String value; |
||||
|
||||
ResponseType(String value) { |
||||
this.value = value; |
||||
} |
||||
|
||||
public String value() { |
||||
return this.value; |
||||
} |
||||
} |
||||
@ -0,0 +1,109 @@
@@ -0,0 +1,109 @@
|
||||
/* |
||||
* 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.oauth2.core.endpoint; |
||||
|
||||
import org.springframework.security.oauth2.core.AccessToken; |
||||
import org.springframework.util.Assert; |
||||
|
||||
import java.time.Instant; |
||||
import java.util.Map; |
||||
import java.util.Set; |
||||
|
||||
/** |
||||
* A representation of an <i>OAuth 2.0 Access Token Response</i>. |
||||
* |
||||
* @author Joe Grandja |
||||
* @since 5.0 |
||||
* @see AccessToken |
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-5.1">Section 5.1 Access Token Response</a> |
||||
*/ |
||||
public final class TokenResponseAttributes { |
||||
private AccessToken accessToken; |
||||
|
||||
private TokenResponseAttributes() { |
||||
} |
||||
|
||||
public String getTokenValue() { |
||||
return this.accessToken.getTokenValue(); |
||||
} |
||||
|
||||
public AccessToken.TokenType getTokenType() { |
||||
return this.accessToken.getTokenType(); |
||||
} |
||||
|
||||
public Instant getIssuedAt() { |
||||
return this.accessToken.getIssuedAt(); |
||||
} |
||||
|
||||
public Instant getExpiresAt() { |
||||
return this.accessToken.getExpiresAt(); |
||||
} |
||||
|
||||
public Set<String> getScopes() { |
||||
return this.accessToken.getScopes(); |
||||
} |
||||
|
||||
public Map<String, Object> getAdditionalParameters() { |
||||
return this.accessToken.getAdditionalParameters(); |
||||
} |
||||
|
||||
public static Builder withToken(String tokenValue) { |
||||
return new Builder(tokenValue); |
||||
} |
||||
|
||||
public static class Builder { |
||||
private String tokenValue; |
||||
private AccessToken.TokenType tokenType; |
||||
private long expiresIn; |
||||
private Set<String> scopes; |
||||
private Map<String,Object> additionalParameters; |
||||
|
||||
private Builder(String tokenValue) { |
||||
this.tokenValue = tokenValue; |
||||
} |
||||
|
||||
public Builder tokenType(AccessToken.TokenType tokenType) { |
||||
this.tokenType = tokenType; |
||||
return this; |
||||
} |
||||
|
||||
public Builder expiresIn(long expiresIn) { |
||||
this.expiresIn = expiresIn; |
||||
return this; |
||||
} |
||||
|
||||
public Builder scopes(Set<String> scopes) { |
||||
this.scopes = scopes; |
||||
return this; |
||||
} |
||||
|
||||
public Builder additionalParameters(Map<String,Object> additionalParameters) { |
||||
this.additionalParameters = additionalParameters; |
||||
return this; |
||||
} |
||||
|
||||
public TokenResponseAttributes build() { |
||||
Assert.isTrue(this.expiresIn >= 0, "expiresIn must be a positive number"); |
||||
Instant issuedAt = Instant.now(); |
||||
AccessToken accessToken = new AccessToken(this.tokenType, this.tokenValue, issuedAt, |
||||
issuedAt.plusSeconds(this.expiresIn), this.scopes, this.additionalParameters); |
||||
|
||||
TokenResponseAttributes tokenResponse = new TokenResponseAttributes(); |
||||
tokenResponse.accessToken = accessToken; |
||||
return tokenResponse; |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,21 @@
@@ -0,0 +1,21 @@
|
||||
/* |
||||
* 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. |
||||
*/ |
||||
/** |
||||
* Support classes that model the request/response messages from the |
||||
* <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-3.1">Authorization Endpoint</a> |
||||
* and <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-3.2">Token Endpoint</a>. |
||||
*/ |
||||
package org.springframework.security.oauth2.core.endpoint; |
||||
@ -0,0 +1,19 @@
@@ -0,0 +1,19 @@
|
||||
/* |
||||
* 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. |
||||
*/ |
||||
/** |
||||
* Core classes and interfaces providing support for the <i>OAuth 2.0 Authorization Framework</i>. |
||||
*/ |
||||
package org.springframework.security.oauth2.core; |
||||
@ -0,0 +1,153 @@
@@ -0,0 +1,153 @@
|
||||
/* |
||||
* 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.oauth2.core.user; |
||||
|
||||
import org.springframework.security.core.GrantedAuthority; |
||||
import org.springframework.security.core.SpringSecurityCoreVersion; |
||||
import org.springframework.util.Assert; |
||||
import org.springframework.util.CollectionUtils; |
||||
|
||||
import java.time.Instant; |
||||
import java.util.*; |
||||
import java.util.stream.Collectors; |
||||
|
||||
/** |
||||
* The default implementation of an {@link OAuth2User}. |
||||
* |
||||
* <p> |
||||
* User attribute names are <b><i>not</i></b> standardized between providers |
||||
* and therefore it is required that the user supply the <i>key</i> |
||||
* for the user's "name" attribute to one of the constructors. |
||||
* The <i>key</i> will be used for accessing the "name" of the |
||||
* <code>Principal</code> (user) via {@link #getAttributes()} |
||||
* and returning it from {@link #getName()}. |
||||
* |
||||
* @author Joe Grandja |
||||
* @since 5.0 |
||||
* @see OAuth2User |
||||
*/ |
||||
public class DefaultOAuth2User implements OAuth2User { |
||||
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID; |
||||
private final Set<GrantedAuthority> authorities; |
||||
private final Map<String, Object> attributes; |
||||
private final String nameAttributeKey; |
||||
|
||||
public DefaultOAuth2User(Map<String, Object> attributes, String nameAttributeKey) { |
||||
this(Collections.emptySet(), attributes, nameAttributeKey); |
||||
} |
||||
|
||||
public DefaultOAuth2User(Set<GrantedAuthority> authorities, Map<String, Object> attributes, String nameAttributeKey) { |
||||
Assert.notNull(authorities, "authorities cannot be null"); |
||||
Assert.notEmpty(attributes, "attributes cannot be empty"); |
||||
Assert.hasText(nameAttributeKey, "nameAttributeKey cannot be empty"); |
||||
if (!attributes.containsKey(nameAttributeKey)) { |
||||
throw new IllegalArgumentException("Invalid nameAttributeKey: " + nameAttributeKey); |
||||
} |
||||
this.authorities = Collections.unmodifiableSet(this.sortAuthorities(authorities)); |
||||
this.attributes = Collections.unmodifiableMap(new LinkedHashMap<>(attributes)); |
||||
this.nameAttributeKey = nameAttributeKey; |
||||
} |
||||
|
||||
@Override |
||||
public String getName() { |
||||
return this.getAttributes().get(this.nameAttributeKey).toString(); |
||||
} |
||||
|
||||
@Override |
||||
public Collection<? extends GrantedAuthority> getAuthorities() { |
||||
return this.authorities; |
||||
} |
||||
|
||||
@Override |
||||
public Map<String, Object> getAttributes() { |
||||
return this.attributes; |
||||
} |
||||
|
||||
protected String getAttributeAsString(String key) { |
||||
Object value = this.getAttributes().get(key); |
||||
return (value != null ? value.toString() : null); |
||||
} |
||||
|
||||
protected Boolean getAttributeAsBoolean(String key) { |
||||
String value = this.getAttributeAsString(key); |
||||
return (value != null ? Boolean.valueOf(value) : null); |
||||
} |
||||
|
||||
protected Instant getAttributeAsInstant(String key) { |
||||
String value = this.getAttributeAsString(key); |
||||
if (value == null) { |
||||
return null; |
||||
} |
||||
try { |
||||
return Instant.ofEpochSecond(Long.valueOf(value)); |
||||
} catch (NumberFormatException ex) { |
||||
throw new IllegalArgumentException("Invalid long value: " + ex.getMessage(), ex); |
||||
} |
||||
} |
||||
|
||||
private Set<GrantedAuthority> sortAuthorities(Set<GrantedAuthority> authorities) { |
||||
if (CollectionUtils.isEmpty(authorities)) { |
||||
return Collections.emptySet(); |
||||
} |
||||
|
||||
SortedSet<GrantedAuthority> sortedAuthorities = |
||||
new TreeSet<>((g1, g2) -> g1.getAuthority().compareTo(g2.getAuthority())); |
||||
authorities.stream().forEach(sortedAuthorities::add); |
||||
|
||||
return sortedAuthorities; |
||||
} |
||||
|
||||
@Override |
||||
public boolean equals(Object obj) { |
||||
if (this == obj) { |
||||
return true; |
||||
} |
||||
if (obj == null || this.getClass() != obj.getClass()) { |
||||
return false; |
||||
} |
||||
|
||||
DefaultOAuth2User that = (DefaultOAuth2User) obj; |
||||
|
||||
if (!this.getName().equals(that.getName())) { |
||||
return false; |
||||
} |
||||
if (!this.getAuthorities().equals(that.getAuthorities())) { |
||||
return false; |
||||
} |
||||
return this.getAttributes().equals(that.getAttributes()); |
||||
} |
||||
|
||||
@Override |
||||
public int hashCode() { |
||||
int result = this.getName().hashCode(); |
||||
result = 31 * result + this.getAuthorities().hashCode(); |
||||
result = 31 * result + this.getAttributes().hashCode(); |
||||
return result; |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
StringBuilder sb = new StringBuilder(); |
||||
sb.append("Name: ["); |
||||
sb.append(this.getName()); |
||||
sb.append("], Granted Authorities: ["); |
||||
sb.append(this.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.joining(", "))); |
||||
sb.append("], User Attributes: ["); |
||||
sb.append(this.getAttributes().entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.joining(", "))); |
||||
sb.append("]"); |
||||
return sb.toString(); |
||||
} |
||||
} |
||||
@ -0,0 +1,55 @@
@@ -0,0 +1,55 @@
|
||||
/* |
||||
* 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.oauth2.core.user; |
||||
|
||||
import org.springframework.security.core.AuthenticatedPrincipal; |
||||
import org.springframework.security.core.Authentication; |
||||
import org.springframework.security.core.GrantedAuthority; |
||||
|
||||
import java.io.Serializable; |
||||
import java.util.Collection; |
||||
import java.util.Map; |
||||
|
||||
/** |
||||
* A representation of a user <code>Principal</code> |
||||
* that is registered with a standard <i>OAuth 2.0 Provider</i>. |
||||
* |
||||
* <p> |
||||
* An OAuth 2.0 user is composed of one or more attributes, for example, |
||||
* first name, middle name, last name, email, phone number, address, etc. |
||||
* Each user attribute has a "name" and "value" and |
||||
* is keyed by the "name" in {@link #getAttributes()}. |
||||
* |
||||
* <p> |
||||
* <b>NOTE:</b> Attribute names are <b><i>not</i></b> standardized between providers and therefore will vary. |
||||
* Please consult the provider's API documentation for the set of supported user attribute names. |
||||
* |
||||
* <p> |
||||
* Implementation instances of this interface represent an {@link AuthenticatedPrincipal} |
||||
* which is associated to an {@link Authentication} object |
||||
* and may be accessed via {@link Authentication#getPrincipal()}. |
||||
* |
||||
* @author Joe Grandja |
||||
* @since 5.0 |
||||
* @see DefaultOAuth2User |
||||
* @see AuthenticatedPrincipal |
||||
*/ |
||||
public interface OAuth2User extends AuthenticatedPrincipal, Serializable { |
||||
|
||||
Collection<? extends GrantedAuthority> getAuthorities(); |
||||
|
||||
Map<String, Object> getAttributes(); |
||||
} |
||||
@ -0,0 +1,19 @@
@@ -0,0 +1,19 @@
|
||||
/* |
||||
* 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. |
||||
*/ |
||||
/** |
||||
* Provides a model for an <i>OAuth 2.0</i> representation of a user <code>Principal</code>. |
||||
*/ |
||||
package org.springframework.security.oauth2.core.user; |
||||
@ -0,0 +1,69 @@
@@ -0,0 +1,69 @@
|
||||
/* |
||||
* 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.oauth2.oidc; |
||||
|
||||
/** |
||||
* The Standard Claims defined by the <i>OpenID Connect Core 1.0</i> specification |
||||
* and returned in either the <i>UserInfo Response</i> or in the <i>ID Token</i>. |
||||
* |
||||
* @author Joe Grandja |
||||
* @since 5.0 |
||||
* @see <a target="_blank" href="http://openid.net/specs/openid-connect-core-1_0.html#StandardClaims">Standard Claims</a> |
||||
* @see <a target="_blank" href="http://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse">UserInfo Response</a> |
||||
* @see <a target="_blank" href="http://openid.net/specs/openid-connect-core-1_0.html#IDToken">ID Token</a> |
||||
*/ |
||||
public interface StandardClaimName { |
||||
|
||||
String SUB = "sub"; |
||||
|
||||
String NAME = "name"; |
||||
|
||||
String GIVEN_NAME = "given_name"; |
||||
|
||||
String FAMILY_NAME = "family_name"; |
||||
|
||||
String MIDDLE_NAME = "middle_name"; |
||||
|
||||
String NICKNAME = "nickname"; |
||||
|
||||
String PREFERRED_USERNAME = "preferred_username"; |
||||
|
||||
String PROFILE = "profile"; |
||||
|
||||
String PICTURE = "picture"; |
||||
|
||||
String WEBSITE = "website"; |
||||
|
||||
String EMAIL = "email"; |
||||
|
||||
String EMAIL_VERIFIED = "email_verified"; |
||||
|
||||
String GENDER = "gender"; |
||||
|
||||
String BIRTHDATE = "birthdate"; |
||||
|
||||
String ZONEINFO = "zoneinfo"; |
||||
|
||||
String LOCALE = "locale"; |
||||
|
||||
String PHONE_NUMBER = "phone_number"; |
||||
|
||||
String PHONE_NUMBER_VERIFIED = "phone_number_verified"; |
||||
|
||||
String ADDRESS = "address"; |
||||
|
||||
String UPDATED_AT = "updated_at"; |
||||
} |
||||
@ -0,0 +1,19 @@
@@ -0,0 +1,19 @@
|
||||
/* |
||||
* 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. |
||||
*/ |
||||
/** |
||||
* Core classes and interfaces providing support for <i>OpenID Connect Core 1.0</i>. |
||||
*/ |
||||
package org.springframework.security.oauth2.oidc; |
||||
@ -0,0 +1,154 @@
@@ -0,0 +1,154 @@
|
||||
/* |
||||
* 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.oauth2.oidc.user; |
||||
|
||||
import org.springframework.security.core.GrantedAuthority; |
||||
import org.springframework.security.oauth2.core.user.DefaultOAuth2User; |
||||
import org.springframework.security.oauth2.oidc.StandardClaimName; |
||||
|
||||
import java.time.Instant; |
||||
import java.util.Collections; |
||||
import java.util.Map; |
||||
import java.util.Set; |
||||
|
||||
import static org.springframework.security.oauth2.oidc.StandardClaimName.*; |
||||
|
||||
/** |
||||
* The default implementation of a {@link UserInfo}. |
||||
* |
||||
* <p> |
||||
* The <i>key</i> used for accessing the "name" of the |
||||
* <code>Principal</code> (user) via {@link #getAttributes()} |
||||
* is {@link StandardClaimName#NAME} or if not available |
||||
* will default to {@link StandardClaimName#SUB}. |
||||
* |
||||
* @author Joe Grandja |
||||
* @since 5.0 |
||||
* @see UserInfo |
||||
* @see DefaultOAuth2User |
||||
*/ |
||||
public class DefaultUserInfo extends DefaultOAuth2User implements UserInfo { |
||||
|
||||
public DefaultUserInfo(Map<String, Object> attributes) { |
||||
this(Collections.emptySet(), attributes); |
||||
} |
||||
|
||||
public DefaultUserInfo(Set<GrantedAuthority> authorities, Map<String, Object> attributes) { |
||||
super(authorities, attributes, SUB); |
||||
} |
||||
|
||||
@Override |
||||
public String getSubject() { |
||||
return this.getAttributeAsString(SUB); |
||||
} |
||||
|
||||
@Override |
||||
public String getName() { |
||||
String name = this.getAttributeAsString(NAME); |
||||
return (name != null ? name : super.getName()); |
||||
} |
||||
|
||||
@Override |
||||
public String getGivenName() { |
||||
return this.getAttributeAsString(GIVEN_NAME); |
||||
} |
||||
|
||||
@Override |
||||
public String getFamilyName() { |
||||
return this.getAttributeAsString(FAMILY_NAME); |
||||
} |
||||
|
||||
@Override |
||||
public String getMiddleName() { |
||||
return this.getAttributeAsString(MIDDLE_NAME); |
||||
} |
||||
|
||||
@Override |
||||
public String getNickName() { |
||||
return this.getAttributeAsString(NICKNAME); |
||||
} |
||||
|
||||
@Override |
||||
public String getPreferredUsername() { |
||||
return this.getAttributeAsString(PREFERRED_USERNAME); |
||||
} |
||||
|
||||
@Override |
||||
public String getProfile() { |
||||
return this.getAttributeAsString(PROFILE); |
||||
} |
||||
|
||||
@Override |
||||
public String getPicture() { |
||||
return this.getAttributeAsString(PICTURE); |
||||
} |
||||
|
||||
@Override |
||||
public String getWebsite() { |
||||
return this.getAttributeAsString(WEBSITE); |
||||
} |
||||
|
||||
@Override |
||||
public String getEmail() { |
||||
return this.getAttributeAsString(EMAIL); |
||||
} |
||||
|
||||
@Override |
||||
public Boolean getEmailVerified() { |
||||
return this.getAttributeAsBoolean(EMAIL_VERIFIED); |
||||
} |
||||
|
||||
@Override |
||||
public String getGender() { |
||||
return this.getAttributeAsString(GENDER); |
||||
} |
||||
|
||||
@Override |
||||
public String getBirthdate() { |
||||
return this.getAttributeAsString(BIRTHDATE); |
||||
} |
||||
|
||||
@Override |
||||
public String getZoneInfo() { |
||||
return this.getAttributeAsString(ZONEINFO); |
||||
} |
||||
|
||||
@Override |
||||
public String getLocale() { |
||||
return this.getAttributeAsString(LOCALE); |
||||
} |
||||
|
||||
@Override |
||||
public String getPhoneNumber() { |
||||
return this.getAttributeAsString(PHONE_NUMBER); |
||||
} |
||||
|
||||
@Override |
||||
public Boolean getPhoneNumberVerified() { |
||||
return this.getAttributeAsBoolean(PHONE_NUMBER_VERIFIED); |
||||
} |
||||
|
||||
@Override |
||||
public Address getAddress() { |
||||
// TODO Impl
|
||||
return null; |
||||
} |
||||
|
||||
@Override |
||||
public Instant getUpdatedAt() { |
||||
return this.getAttributeAsInstant(UPDATED_AT); |
||||
} |
||||
} |
||||
@ -0,0 +1,103 @@
@@ -0,0 +1,103 @@
|
||||
/* |
||||
* 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.oauth2.oidc.user; |
||||
|
||||
import org.springframework.security.core.AuthenticatedPrincipal; |
||||
import org.springframework.security.core.Authentication; |
||||
import org.springframework.security.oauth2.core.user.OAuth2User; |
||||
|
||||
import java.time.Instant; |
||||
|
||||
/** |
||||
* A representation of a user <code>Principal</code> |
||||
* that is registered with an <i>OpenID Connect 1.0 Provider</i>. |
||||
* |
||||
* <p> |
||||
* The structure of the user <code>Principal</code> is defined by the |
||||
* <a target="_blank" href="http://openid.net/specs/openid-connect-core-1_0.html#UserInfo">UserInfo Endpoint</a>, |
||||
* which is an <i>OAuth 2.0 Protected Resource</i> that returns a set of |
||||
* <a target="_blank" href="http://openid.net/specs/openid-connect-core-1_0.html#StandardClaims">Claims</a> |
||||
* about the authenticated End-User. |
||||
* |
||||
* <p> |
||||
* Implementation instances of this interface represent an {@link AuthenticatedPrincipal} |
||||
* which is associated to an {@link Authentication} object |
||||
* and may be accessed via {@link Authentication#getPrincipal()}. |
||||
* |
||||
* @author Joe Grandja |
||||
* @since 5.0 |
||||
* @see DefaultUserInfo |
||||
* @see AuthenticatedPrincipal |
||||
* @see <a target="_blank" href="http://openid.net/specs/openid-connect-core-1_0.html">OpenID Connect Core 1.0</a> |
||||
* @see <a target="_blank" href="http://openid.net/specs/openid-connect-core-1_0.html#UserInfo">UserInfo Endpoint</a> |
||||
* @see <a target="_blank" href="http://openid.net/specs/openid-connect-core-1_0.html#StandardClaims">Standard Claims</a> |
||||
*/ |
||||
public interface UserInfo extends OAuth2User { |
||||
|
||||
String getSubject(); |
||||
|
||||
String getGivenName(); |
||||
|
||||
String getFamilyName(); |
||||
|
||||
String getMiddleName(); |
||||
|
||||
String getNickName(); |
||||
|
||||
String getPreferredUsername(); |
||||
|
||||
String getProfile(); |
||||
|
||||
String getPicture(); |
||||
|
||||
String getWebsite(); |
||||
|
||||
String getEmail(); |
||||
|
||||
Boolean getEmailVerified(); |
||||
|
||||
String getGender(); |
||||
|
||||
String getBirthdate(); |
||||
|
||||
String getZoneInfo(); |
||||
|
||||
String getLocale(); |
||||
|
||||
String getPhoneNumber(); |
||||
|
||||
Boolean getPhoneNumberVerified(); |
||||
|
||||
Address getAddress(); |
||||
|
||||
Instant getUpdatedAt(); |
||||
|
||||
|
||||
interface Address { |
||||
|
||||
String getFormatted(); |
||||
|
||||
String getStreetAddress(); |
||||
|
||||
String getLocality(); |
||||
|
||||
String getRegion(); |
||||
|
||||
String getPostalCode(); |
||||
|
||||
String getCountry(); |
||||
} |
||||
} |
||||
@ -0,0 +1,19 @@
@@ -0,0 +1,19 @@
|
||||
/* |
||||
* 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. |
||||
*/ |
||||
/** |
||||
* Provides a model for an <i>OpenID Connect Core 1.0</i> representation of a user <code>Principal</code>. |
||||
*/ |
||||
package org.springframework.security.oauth2.oidc.user; |
||||
@ -0,0 +1,342 @@
@@ -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 @@
@@ -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 @@
@@ -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' |
||||
} |
||||
@ -0,0 +1,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 { |
||||
} |
||||
} |
||||
@ -0,0 +1,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)")); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,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)); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,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); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,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; |
||||
} |
||||
} |
||||
@ -0,0 +1,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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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> |
||||
Loading…
Reference in new issue