diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java b/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java
index 175cf5b11f..d1ab52e23c 100644
--- a/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java
+++ b/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java
@@ -239,6 +239,128 @@ public final class HttpSecurity extends
return getOrApply(new OpenIDLoginConfigurer<>());
}
+ /**
+ * Allows configuring OpenID based authentication.
+ *
+ *
Example Configurations
+ *
+ * A basic example accepting the defaults and not using attribute exchange:
+ *
+ *
+ * @Configuration
+ * @EnableWebSecurity
+ * public class OpenIDLoginConfig extends WebSecurityConfigurerAdapter {
+ *
+ * @Override
+ * protected void configure(HttpSecurity http) {
+ * http
+ * .authorizeRequests(authorizeRequests ->
+ * authorizeRequests
+ * .antMatchers("/**").hasRole("USER")
+ * )
+ * .openidLogin(openidLogin ->
+ * openidLogin
+ * .permitAll()
+ * );
+ * }
+ *
+ * @Override
+ * protected void configure(AuthenticationManagerBuilder auth) throws Exception {
+ * auth.inMemoryAuthentication()
+ * // the username must match the OpenID of the user you are
+ * // logging in with
+ * .withUser(
+ * "https://www.google.com/accounts/o8/id?id=lmkCn9xzPdsxVwG7pjYMuDgNNdASFmobNkcRPaWU")
+ * .password("password").roles("USER");
+ * }
+ * }
+ *
+ *
+ * A more advanced example demonstrating using attribute exchange and providing a
+ * custom AuthenticationUserDetailsService that will make any user that authenticates
+ * a valid user.
+ *
+ *
+ * @Configuration
+ * @EnableWebSecurity
+ * public class OpenIDLoginConfig extends WebSecurityConfigurerAdapter {
+ *
+ * @Override
+ * protected void configure(HttpSecurity http) throws Exception {
+ * http.authorizeRequests(authorizeRequests ->
+ * authorizeRequests
+ * .antMatchers("/**").hasRole("USER")
+ * )
+ * .openidLogin(openidLogin ->
+ * openidLogin
+ * .loginPage("/login")
+ * .permitAll()
+ * .authenticationUserDetailsService(
+ * new AutoProvisioningUserDetailsService())
+ * .attributeExchange(googleExchange ->
+ * googleExchange
+ * .identifierPattern("https://www.google.com/.*")
+ * .attribute(emailAttribute ->
+ * emailAttribute
+ * .name("email")
+ * .type("https://axschema.org/contact/email")
+ * .required(true)
+ * )
+ * .attribute(firstnameAttribute ->
+ * firstnameAttribute
+ * .name("firstname")
+ * .type("https://axschema.org/namePerson/first")
+ * .required(true)
+ * )
+ * .attribute(lastnameAttribute ->
+ * lastnameAttribute
+ * .name("lastname")
+ * .type("https://axschema.org/namePerson/last")
+ * .required(true)
+ * )
+ * )
+ * .attributeExchange(yahooExchange ->
+ * yahooExchange
+ * .identifierPattern(".*yahoo.com.*")
+ * .attribute(emailAttribute ->
+ * emailAttribute
+ * .name("email")
+ * .type("https://schema.openid.net/contact/email")
+ * .required(true)
+ * )
+ * .attribute(fullnameAttribute ->
+ * fullnameAttribute
+ * .name("fullname")
+ * .type("https://axschema.org/namePerson")
+ * .required(true)
+ * )
+ * )
+ * );
+ * }
+ * }
+ *
+ * public class AutoProvisioningUserDetailsService implements
+ * AuthenticationUserDetailsService<OpenIDAuthenticationToken> {
+ * public UserDetails loadUserDetails(OpenIDAuthenticationToken token)
+ * throws UsernameNotFoundException {
+ * return new User(token.getName(), "NOTUSED",
+ * AuthorityUtils.createAuthorityList("ROLE_USER"));
+ * }
+ * }
+ *
+ *
+ * @see OpenIDLoginConfigurer
+ *
+ * @param openidLoginCustomizer the {@link Customizer} to provide more options for
+ * the {@link OpenIDLoginConfigurer}
+ * @return the {@link HttpSecurity} for further customizations
+ * @throws Exception
+ */
+ public HttpSecurity openidLogin(Customizer> openidLoginCustomizer) throws Exception {
+ openidLoginCustomizer.customize(getOrApply(new OpenIDLoginConfigurer<>()));
+ return HttpSecurity.this;
+ }
+
/**
* Adds the Security headers to the response. This is activated by default when using
* {@link WebSecurityConfigurerAdapter}'s default constructor. Accepting the
diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/openid/OpenIDLoginConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/openid/OpenIDLoginConfigurer.java
index 71d7c2e215..01e1b3198a 100644
--- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/openid/OpenIDLoginConfigurer.java
+++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/openid/OpenIDLoginConfigurer.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2019 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.
@@ -27,6 +27,7 @@ import org.openid4java.consumer.ConsumerManager;
import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@@ -148,6 +149,24 @@ public final class OpenIDLoginConfigurer> exten
return attributeExchangeConfigurer;
}
+ /**
+ * Sets up OpenID attribute exchange for OpenIDs matching the specified pattern.
+ * The default pattern is ".*", it can be specified using
+ * {@link AttributeExchangeConfigurer#identifierPattern(String)}
+ *
+ * @param attributeExchangeCustomizer the {@link Customizer} to provide more options for
+ * the {@link AttributeExchangeConfigurer}
+ * @return a {@link OpenIDLoginConfigurer} for further customizations
+ * @throws Exception
+ */
+ public OpenIDLoginConfigurer attributeExchange(Customizer attributeExchangeCustomizer)
+ throws Exception {
+ AttributeExchangeConfigurer attributeExchangeConfigurer = new AttributeExchangeConfigurer(".*");
+ attributeExchangeCustomizer.customize(attributeExchangeConfigurer);
+ this.attributeExchangeConfigurers.add(attributeExchangeConfigurer);
+ return this;
+ }
+
/**
* Allows specifying the {@link OpenIDConsumer} to be used. The default is using an
* {@link OpenID4JavaConsumer}.
@@ -373,7 +392,7 @@ public final class OpenIDLoginConfigurer> exten
* @author Rob Winch
*/
public final class AttributeExchangeConfigurer {
- private final String identifier;
+ private String identifier;
private List attributes = new ArrayList<>();
private List attributeConfigurers = new ArrayList<>();
@@ -395,6 +414,19 @@ public final class OpenIDLoginConfigurer> exten
return OpenIDLoginConfigurer.this;
}
+ /**
+ * Sets the regular expression for matching on OpenID's (i.e.
+ * "https://www.google.com/.*", ".*yahoo.com.*", etc)
+ *
+ * @param identifierPattern the regular expression for matching on OpenID's
+ * @return the {@link AttributeExchangeConfigurer} for further customization of
+ * attribute exchange
+ */
+ public AttributeExchangeConfigurer identifierPattern(String identifierPattern) {
+ this.identifier = identifierPattern;
+ return this;
+ }
+
/**
* Adds an {@link OpenIDAttribute} to be obtained for the configured OpenID
* pattern.
@@ -419,6 +451,22 @@ public final class OpenIDLoginConfigurer> exten
return attributeConfigurer;
}
+ /**
+ * Adds an {@link OpenIDAttribute} named "default-attribute".
+ * The name can by updated using {@link AttributeConfigurer#name(String)}.
+ *
+ * @param attributeCustomizer the {@link Customizer} to provide more options for
+ * the {@link AttributeConfigurer}
+ * @return a {@link AttributeExchangeConfigurer} for further customizations
+ * @throws Exception
+ */
+ public AttributeExchangeConfigurer attribute(Customizer attributeCustomizer) throws Exception {
+ AttributeConfigurer attributeConfigurer = new AttributeConfigurer();
+ attributeCustomizer.customize(attributeConfigurer);
+ this.attributeConfigurers.add(attributeConfigurer);
+ return this;
+ }
+
/**
* Gets the {@link OpenIDAttribute}'s for the configured OpenID pattern
* @return
@@ -443,6 +491,16 @@ public final class OpenIDLoginConfigurer> exten
private boolean required = false;
private String type;
+ /**
+ * Creates a new instance named "default-attribute".
+ * The name can by updated using {@link #name(String)}.
+ *
+ * @see AttributeExchangeConfigurer#attribute(String)
+ */
+ private AttributeConfigurer() {
+ this.name = "default-attribute";
+ }
+
/**
* Creates a new instance
* @param name the name of the attribute
@@ -486,6 +544,16 @@ public final class OpenIDLoginConfigurer> exten
return this;
}
+ /**
+ * The OpenID attribute name.
+ * @param name
+ * @return the {@link AttributeConfigurer} for further customizations
+ */
+ public AttributeConfigurer name(String name) {
+ this.name = name;
+ return this;
+ }
+
/**
* Gets the {@link AttributeExchangeConfigurer} for further customization of
* the attributes
diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/openid/OpenIDLoginConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/openid/OpenIDLoginConfigurerTests.java
index d18efe0b37..159c54967e 100644
--- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/openid/OpenIDLoginConfigurerTests.java
+++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/openid/OpenIDLoginConfigurerTests.java
@@ -16,8 +16,13 @@
package org.springframework.security.config.annotation.web.configurers.openid;
+import okhttp3.mockwebserver.MockResponse;
+import okhttp3.mockwebserver.MockWebServer;
import org.junit.Rule;
import org.junit.Test;
+import org.openid4java.consumer.ConsumerManager;
+import org.openid4java.discovery.DiscoveryInformation;
+import org.openid4java.message.AuthRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.ObjectPostProcessor;
@@ -26,13 +31,23 @@ 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.config.test.SpringTestRule;
+import org.springframework.security.openid.OpenIDAttribute;
import org.springframework.security.openid.OpenIDAuthenticationFilter;
import org.springframework.security.openid.OpenIDAuthenticationProvider;
import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.MvcResult;
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.openid4java.discovery.yadis.YadisResolver.YADIS_XRDS_LOCATION;
+import static org.springframework.security.config.Customizer.withDefaults;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@@ -128,4 +143,167 @@ public class OpenIDLoginConfigurerTests {
// @formatter:on
}
}
+
+ @Test
+ public void requestWhenOpenIdLoginPageInLambdaThenRedirectsToLoginPAge() throws Exception {
+ this.spring.register(OpenIdLoginPageInLambdaConfig.class).autowire();
+
+ this.mvc.perform(get("/"))
+ .andExpect(status().isFound())
+ .andExpect(redirectedUrl("http://localhost/login/custom"));
+ }
+
+ @EnableWebSecurity
+ static class OpenIdLoginPageInLambdaConfig extends WebSecurityConfigurerAdapter {
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ // @formatter:off
+ http
+ .authorizeRequests(authorizeRequests ->
+ authorizeRequests
+ .anyRequest().authenticated()
+ )
+ .openidLogin(openIdLogin ->
+ openIdLogin
+ .loginPage("/login/custom")
+ );
+ // @formatter:on
+ }
+ }
+
+ @Test
+ public void requestWhenAttributeExchangeConfiguredThenFetchAttributesMatchAttributeList() throws Exception {
+ OpenIdAttributesInLambdaConfig.CONSUMER_MANAGER = mock(ConsumerManager.class);
+ AuthRequest mockAuthRequest = mock(AuthRequest.class);
+ DiscoveryInformation mockDiscoveryInformation = mock(DiscoveryInformation.class);
+ when(mockAuthRequest.getDestinationUrl(anyBoolean())).thenReturn("mockUrl");
+ when(OpenIdAttributesInLambdaConfig.CONSUMER_MANAGER.associate(any()))
+ .thenReturn(mockDiscoveryInformation);
+ when(OpenIdAttributesInLambdaConfig.CONSUMER_MANAGER.authenticate(any(DiscoveryInformation.class), any(), any()))
+ .thenReturn(mockAuthRequest);
+ this.spring.register(OpenIdAttributesInLambdaConfig.class).autowire();
+
+ try ( MockWebServer server = new MockWebServer() ) {
+ String endpoint = server.url("/").toString();
+
+ server.enqueue(new MockResponse()
+ .addHeader(YADIS_XRDS_LOCATION, endpoint));
+ server.enqueue(new MockResponse()
+ .setBody(String.format("%s", endpoint)));
+
+ MvcResult mvcResult = this.mvc.perform(get("/login/openid")
+ .param(OpenIDAuthenticationFilter.DEFAULT_CLAIMED_IDENTITY_FIELD, endpoint))
+ .andExpect(status().isFound())
+ .andReturn();
+
+ Object attributeObject = mvcResult.getRequest().getSession().getAttribute("SPRING_SECURITY_OPEN_ID_ATTRIBUTES_FETCH_LIST");
+ assertThat(attributeObject).isInstanceOf(List.class);
+ List attributeList = (List) attributeObject;
+ assertThat(attributeList.stream().anyMatch(attribute ->
+ "nickname".equals(attribute.getName())
+ && "https://schema.openid.net/namePerson/friendly".equals(attribute.getType())))
+ .isTrue();
+ assertThat(attributeList.stream().anyMatch(attribute ->
+ "email".equals(attribute.getName())
+ && "https://schema.openid.net/contact/email".equals(attribute.getType())
+ && attribute.isRequired()
+ && attribute.getCount() == 2))
+ .isTrue();
+ }
+ }
+
+ @EnableWebSecurity
+ static class OpenIdAttributesInLambdaConfig extends WebSecurityConfigurerAdapter {
+ static ConsumerManager CONSUMER_MANAGER;
+
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ // @formatter:off
+ http
+ .authorizeRequests(authorizeRequests ->
+ authorizeRequests
+ .anyRequest().permitAll()
+ )
+ .openidLogin(openIdLogin ->
+ openIdLogin
+ .consumerManager(CONSUMER_MANAGER)
+ .attributeExchange(attributeExchange ->
+ attributeExchange
+ .identifierPattern(".*")
+ .attribute(nicknameAttribute ->
+ nicknameAttribute
+ .name("nickname")
+ .type("https://schema.openid.net/namePerson/friendly")
+ )
+ .attribute(emailAttribute ->
+ emailAttribute
+ .name("email")
+ .type("https://schema.openid.net/contact/email")
+ .required(true)
+ .count(2)
+ )
+ )
+ );
+ // @formatter:on
+ }
+ }
+
+ @Test
+ public void requestWhenAttributeNameNotSpecifiedThenAttributeNameDefaulted()
+ throws Exception {
+ OpenIdAttributesNullNameConfig.CONSUMER_MANAGER = mock(ConsumerManager.class);
+ AuthRequest mockAuthRequest = mock(AuthRequest.class);
+ DiscoveryInformation mockDiscoveryInformation = mock(DiscoveryInformation.class);
+ when(mockAuthRequest.getDestinationUrl(anyBoolean())).thenReturn("mockUrl");
+ when(OpenIdAttributesNullNameConfig.CONSUMER_MANAGER.associate(any()))
+ .thenReturn(mockDiscoveryInformation);
+ when(OpenIdAttributesNullNameConfig.CONSUMER_MANAGER.authenticate(any(DiscoveryInformation.class), any(), any()))
+ .thenReturn(mockAuthRequest);
+ this.spring.register(OpenIdAttributesNullNameConfig.class).autowire();
+
+ try ( MockWebServer server = new MockWebServer() ) {
+ String endpoint = server.url("/").toString();
+
+ server.enqueue(new MockResponse()
+ .addHeader(YADIS_XRDS_LOCATION, endpoint));
+ server.enqueue(new MockResponse()
+ .setBody(String.format("%s", endpoint)));
+
+ MvcResult mvcResult = this.mvc.perform(get("/login/openid")
+ .param(OpenIDAuthenticationFilter.DEFAULT_CLAIMED_IDENTITY_FIELD, endpoint))
+ .andExpect(status().isFound())
+ .andReturn();
+
+ Object attributeObject = mvcResult.getRequest().getSession().getAttribute("SPRING_SECURITY_OPEN_ID_ATTRIBUTES_FETCH_LIST");
+ assertThat(attributeObject).isInstanceOf(List.class);
+ List attributeList = (List) attributeObject;
+ assertThat(attributeList).hasSize(1);
+ assertThat(attributeList.get(0).getName()).isEqualTo("default-attribute");
+ }
+ }
+
+ @EnableWebSecurity
+ static class OpenIdAttributesNullNameConfig extends WebSecurityConfigurerAdapter {
+ static ConsumerManager CONSUMER_MANAGER;
+
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ // @formatter:off
+ http
+ .authorizeRequests(authorizeRequests ->
+ authorizeRequests
+ .anyRequest().permitAll()
+ )
+ .openidLogin(openIdLogin ->
+ openIdLogin
+ .consumerManager(CONSUMER_MANAGER)
+ .attributeExchange(attributeExchange ->
+ attributeExchange
+ .identifierPattern(".*")
+ .attribute(withDefaults())
+ )
+ );
+ // @formatter:on
+ }
+ }
}