mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-09-08 20:51:41 +00:00
SEC-1349: Allow configuration of OpenID with parameters which should be transferred to the return_to URL.
The OpenIDAuthenticationFilter now has a returnToUrlParameters property (a Set). If this is set, the named parameters will be copied from the incoming submitted request to the return_to URL. If not set, it defaults to the "parameter" property of the AbstractRememberMeServices of the parent class. If remember-me is not in use, it defaults to the empty set. Enabled remember-me in the OpenID sample.
This commit is contained in:
parent
bc02fc2de1
commit
e211f9b35f
@ -8,12 +8,15 @@ import static org.springframework.security.config.http.AuthenticationConfigBuild
|
|||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import javax.servlet.Filter;
|
import javax.servlet.Filter;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@ -39,6 +42,9 @@ import org.springframework.security.openid.OpenID4JavaConsumer;
|
|||||||
import org.springframework.security.openid.OpenIDAttribute;
|
import org.springframework.security.openid.OpenIDAttribute;
|
||||||
import org.springframework.security.openid.OpenIDAuthenticationFilter;
|
import org.springframework.security.openid.OpenIDAuthenticationFilter;
|
||||||
import org.springframework.security.openid.OpenIDAuthenticationProvider;
|
import org.springframework.security.openid.OpenIDAuthenticationProvider;
|
||||||
|
import org.springframework.security.openid.OpenIDAuthenticationToken;
|
||||||
|
import org.springframework.security.openid.OpenIDConsumer;
|
||||||
|
import org.springframework.security.openid.OpenIDConsumerException;
|
||||||
import org.springframework.security.util.FieldUtils;
|
import org.springframework.security.util.FieldUtils;
|
||||||
import org.springframework.security.web.FilterChainProxy;
|
import org.springframework.security.web.FilterChainProxy;
|
||||||
import org.springframework.security.web.FilterInvocation;
|
import org.springframework.security.web.FilterInvocation;
|
||||||
@ -63,6 +69,7 @@ import org.springframework.security.web.authentication.logout.LogoutHandler;
|
|||||||
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
|
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
|
||||||
import org.springframework.security.web.authentication.preauth.x509.SubjectDnX509PrincipalExtractor;
|
import org.springframework.security.web.authentication.preauth.x509.SubjectDnX509PrincipalExtractor;
|
||||||
import org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter;
|
import org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter;
|
||||||
|
import org.springframework.security.web.authentication.rememberme.AbstractRememberMeServices;
|
||||||
import org.springframework.security.web.authentication.rememberme.InMemoryTokenRepositoryImpl;
|
import org.springframework.security.web.authentication.rememberme.InMemoryTokenRepositoryImpl;
|
||||||
import org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices;
|
import org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices;
|
||||||
import org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter;
|
import org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter;
|
||||||
@ -1070,6 +1077,55 @@ public class HttpSecurityBeanDefinitionParserTests {
|
|||||||
getFilter(DefaultLoginPageGeneratingFilter.class);
|
getFilter(DefaultLoginPageGeneratingFilter.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void openIDAndRememberMeWorkTogether() throws Exception {
|
||||||
|
setContext(
|
||||||
|
"<http>" +
|
||||||
|
" <intercept-url pattern='/**' access='ROLE_NOBODY'/>" +
|
||||||
|
" <openid-login />" +
|
||||||
|
" <remember-me />" +
|
||||||
|
"</http>" +
|
||||||
|
AUTH_PROVIDER_XML);
|
||||||
|
// Default login filter should be present since we haven't specified any login URLs
|
||||||
|
DefaultLoginPageGeneratingFilter loginFilter = getFilter(DefaultLoginPageGeneratingFilter.class);
|
||||||
|
OpenIDAuthenticationFilter openIDFilter = getFilter(OpenIDAuthenticationFilter.class);
|
||||||
|
openIDFilter.setConsumer(new OpenIDConsumer() {
|
||||||
|
public String beginConsumption(HttpServletRequest req, String claimedIdentity, String returnToUrl, String realm)
|
||||||
|
throws OpenIDConsumerException {
|
||||||
|
return "http://testopenid.com?openid.return_to=" + returnToUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OpenIDAuthenticationToken endConsumption(HttpServletRequest req) throws OpenIDConsumerException {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Set<String> returnToUrlParameters = new HashSet<String>();
|
||||||
|
returnToUrlParameters.add(AbstractRememberMeServices.DEFAULT_PARAMETER);
|
||||||
|
openIDFilter.setReturnToUrlParameters(returnToUrlParameters);
|
||||||
|
assertNotNull(FieldUtils.getFieldValue(loginFilter, "openIDrememberMeParameter"));
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
|
||||||
|
FilterChainProxy fcp = (FilterChainProxy) appContext.getBean(BeanIds.FILTER_CHAIN_PROXY);
|
||||||
|
request.setServletPath("/something.html");
|
||||||
|
fcp.doFilter(request, response, new MockFilterChain());
|
||||||
|
assertTrue(response.getRedirectedUrl().endsWith("/spring_security_login"));
|
||||||
|
request.setServletPath("/spring_security_login");
|
||||||
|
request.setRequestURI("/spring_security_login");
|
||||||
|
response = new MockHttpServletResponse();
|
||||||
|
fcp.doFilter(request, response, new MockFilterChain());
|
||||||
|
assertTrue(response.getContentAsString().contains(AbstractRememberMeServices.DEFAULT_PARAMETER));
|
||||||
|
request.setRequestURI("/j_spring_openid_security_check");
|
||||||
|
request.setParameter(OpenIDAuthenticationFilter.DEFAULT_CLAIMED_IDENTITY_FIELD, "http://hey.openid.com/");
|
||||||
|
request.setParameter(AbstractRememberMeServices.DEFAULT_PARAMETER, "on");
|
||||||
|
response = new MockHttpServletResponse();
|
||||||
|
fcp.doFilter(request, response, new MockFilterChain());
|
||||||
|
String expectedReturnTo = request.getRequestURL().append("?")
|
||||||
|
.append(AbstractRememberMeServices.DEFAULT_PARAMETER)
|
||||||
|
.append("=").append("on").toString();
|
||||||
|
assertEquals("http://testopenid.com?openid.return_to=" + expectedReturnTo, response.getRedirectedUrl());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void formLoginEntryPointTakesPrecedenceIfLoginUrlIsSet() throws Exception {
|
public void formLoginEntryPointTakesPrecedenceIfLoginUrlIsSet() throws Exception {
|
||||||
setContext(
|
setContext(
|
||||||
|
@ -19,7 +19,10 @@ import java.io.IOException;
|
|||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
@ -31,6 +34,8 @@ import org.springframework.security.core.Authentication;
|
|||||||
import org.springframework.security.core.AuthenticationException;
|
import org.springframework.security.core.AuthenticationException;
|
||||||
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
|
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
|
||||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||||
|
import org.springframework.security.web.authentication.rememberme.AbstractRememberMeServices;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
|
||||||
@ -72,6 +77,7 @@ public class OpenIDAuthenticationFilter extends AbstractAuthenticationProcessing
|
|||||||
private OpenIDConsumer consumer;
|
private OpenIDConsumer consumer;
|
||||||
private String claimedIdentityFieldName = DEFAULT_CLAIMED_IDENTITY_FIELD;
|
private String claimedIdentityFieldName = DEFAULT_CLAIMED_IDENTITY_FIELD;
|
||||||
private Map<String,String> realmMapping = Collections.emptyMap();
|
private Map<String,String> realmMapping = Collections.emptyMap();
|
||||||
|
private Set<String> returnToUrlParameters = Collections.emptySet();
|
||||||
|
|
||||||
//~ Constructors ===================================================================================================
|
//~ Constructors ===================================================================================================
|
||||||
|
|
||||||
@ -84,6 +90,7 @@ public class OpenIDAuthenticationFilter extends AbstractAuthenticationProcessing
|
|||||||
@Override
|
@Override
|
||||||
public void afterPropertiesSet() {
|
public void afterPropertiesSet() {
|
||||||
super.afterPropertiesSet();
|
super.afterPropertiesSet();
|
||||||
|
|
||||||
if (consumer == null) {
|
if (consumer == null) {
|
||||||
try {
|
try {
|
||||||
consumer = new OpenID4JavaConsumer();
|
consumer = new OpenID4JavaConsumer();
|
||||||
@ -91,6 +98,12 @@ public class OpenIDAuthenticationFilter extends AbstractAuthenticationProcessing
|
|||||||
throw new IllegalArgumentException("Failed to initialize OpenID", e);
|
throw new IllegalArgumentException("Failed to initialize OpenID", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (returnToUrlParameters.isEmpty() &&
|
||||||
|
getRememberMeServices() instanceof AbstractRememberMeServices) {
|
||||||
|
returnToUrlParameters = new HashSet<String>();
|
||||||
|
returnToUrlParameters.add(((AbstractRememberMeServices)getRememberMeServices()).getParameter());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -194,7 +207,32 @@ public class OpenIDAuthenticationFilter extends AbstractAuthenticationProcessing
|
|||||||
* @return The <tt>return_to</tt> URL.
|
* @return The <tt>return_to</tt> URL.
|
||||||
*/
|
*/
|
||||||
protected String buildReturnToUrl(HttpServletRequest request) {
|
protected String buildReturnToUrl(HttpServletRequest request) {
|
||||||
return request.getRequestURL().toString();
|
StringBuffer sb = request.getRequestURL();
|
||||||
|
|
||||||
|
Iterator<String> iterator = returnToUrlParameters.iterator();
|
||||||
|
boolean isFirst = true;
|
||||||
|
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
String name = iterator.next();
|
||||||
|
// Assume for simplicity that there is only one value
|
||||||
|
String value = request.getParameter(name);
|
||||||
|
|
||||||
|
if (value == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isFirst) {
|
||||||
|
sb.append("?");
|
||||||
|
isFirst = false;
|
||||||
|
}
|
||||||
|
sb.append(name).append("=").append(value);
|
||||||
|
|
||||||
|
if (iterator.hasNext()) {
|
||||||
|
sb.append("&");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -232,4 +270,17 @@ public class OpenIDAuthenticationFilter extends AbstractAuthenticationProcessing
|
|||||||
public void setConsumer(OpenIDConsumer consumer) {
|
public void setConsumer(OpenIDConsumer consumer) {
|
||||||
this.consumer = consumer;
|
this.consumer = consumer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies any extra parameters submitted along with the identity field which should be appended to the
|
||||||
|
* {@literal return_to} URL which is assembled by {@link #buildReturnToUrl}.
|
||||||
|
*
|
||||||
|
* @param returnToUrlParameters
|
||||||
|
* the set of parameter names. If not set, it will default to the parameter name used by the
|
||||||
|
* {@code RememberMeServices} obtained from the parent class (if one is set).
|
||||||
|
*/
|
||||||
|
public void setReturnToUrlParameters(Set<String> returnToUrlParameters) {
|
||||||
|
Assert.notNull(returnToUrlParameters, "returnToUrlParameters cannot be null");
|
||||||
|
this.returnToUrlParameters = returnToUrlParameters;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
- Sample namespace-based configuration
|
- Namespace-based OpenID configuration
|
||||||
-
|
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<b:beans xmlns="http://www.springframework.org/schema/security"
|
<b:beans xmlns="http://www.springframework.org/schema/security"
|
||||||
@ -21,8 +20,12 @@
|
|||||||
<openid-attribute name="name" type="http://schema.openid.net/namePerson/friendly" />
|
<openid-attribute name="name" type="http://schema.openid.net/namePerson/friendly" />
|
||||||
</attribute-exchange>
|
</attribute-exchange>
|
||||||
</openid-login>
|
</openid-login>
|
||||||
|
<remember-me token-repository-ref="tokenRepo"/>
|
||||||
</http>
|
</http>
|
||||||
|
|
||||||
|
<b:bean id="tokenRepo"
|
||||||
|
class="org.springframework.security.web.authentication.rememberme.InMemoryTokenRepositoryImpl" />
|
||||||
|
|
||||||
<authentication-manager alias="authenticationManager"/>
|
<authentication-manager alias="authenticationManager"/>
|
||||||
|
|
||||||
<user-service id="userService">
|
<user-service id="userService">
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
<form name="f" action="<c:url value='j_spring_openid_security_check'/>" method="POST">
|
<form name="f" action="<c:url value='j_spring_openid_security_check'/>" method="POST">
|
||||||
<table>
|
<table>
|
||||||
<tr><td>OpenID Identity:</td><td><input type='text' name='openid_identifier' value='<c:if test="${not empty param.login_error}"><c:out value="${SPRING_SECURITY_LAST_USERNAME}"/></c:if>'/></td></tr>
|
<tr><td>OpenID Identity:</td><td><input type='text' name='openid_identifier' value='<c:if test="${not empty param.login_error}"><c:out value="${SPRING_SECURITY_LAST_USERNAME}"/></c:if>'/></td></tr>
|
||||||
|
<tr><td><input type="checkbox" name="_spring_security_remember_me"></td><td>Remember me on this computer.</td></tr>
|
||||||
<tr><td colspan='2'><input name="submit" type="submit"></td></tr>
|
<tr><td colspan='2'><input name="submit" type="submit"></td></tr>
|
||||||
<tr><td colspan='2'><input name="reset" type="reset"></td></tr>
|
<tr><td colspan='2'><input name="reset" type="reset"></td></tr>
|
||||||
</table>
|
</table>
|
||||||
|
@ -165,7 +165,17 @@ public abstract class AbstractRememberMeServices implements RememberMeServices,
|
|||||||
|
|
||||||
String cookieAsPlainText = new String(Base64.decode(cookieValue.getBytes()));
|
String cookieAsPlainText = new String(Base64.decode(cookieValue.getBytes()));
|
||||||
|
|
||||||
return StringUtils.delimitedListToStringArray(cookieAsPlainText, DELIMITER);
|
String[] tokens = StringUtils.delimitedListToStringArray(cookieAsPlainText, DELIMITER);
|
||||||
|
|
||||||
|
if (tokens[0].equalsIgnoreCase("http") && tokens[1].startsWith("//")) {
|
||||||
|
// Assume we've accidentally split a URL (OpenID identifier)
|
||||||
|
String[] newTokens = new String[tokens.length - 1];
|
||||||
|
newTokens[0] = "http:" + tokens[1];
|
||||||
|
System.arraycopy(tokens, 2, newTokens, 1, newTokens.length - 1);
|
||||||
|
tokens = newTokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
return tokens;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -147,7 +147,7 @@ public class DefaultLoginPageGeneratingFilter extends GenericFilterBean {
|
|||||||
sb.append(" <tr><td>Identity:</td><td><input type='text' name='");
|
sb.append(" <tr><td>Identity:</td><td><input type='text' name='");
|
||||||
sb.append(openIDusernameParameter).append("'/></td></tr>\n");
|
sb.append(openIDusernameParameter).append("'/></td></tr>\n");
|
||||||
|
|
||||||
if (rememberMeParameter != null) {
|
if (openIDrememberMeParameter != null) {
|
||||||
sb.append(" <tr><td><input type='checkbox' name='").append(openIDrememberMeParameter).append("'></td><td>Remember me on this computer.</td></tr>\n");
|
sb.append(" <tr><td><input type='checkbox' name='").append(openIDrememberMeParameter).append("'></td><td>Remember me on this computer.</td></tr>\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ public class AbstractRememberMeServicesTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void cookieShouldBeCorrectlyEncodedAndDecoded() {
|
public void cookieShouldBeCorrectlyEncodedAndDecoded() {
|
||||||
String[] cookie = new String[] {"the", "cookie", "tokens", "blah"};
|
String[] cookie = new String[] {"http://name", "cookie", "tokens", "blah"};
|
||||||
MockRememberMeServices services = new MockRememberMeServices();
|
MockRememberMeServices services = new MockRememberMeServices();
|
||||||
|
|
||||||
String encoded = services.encodeCookie(cookie);
|
String encoded = services.encodeCookie(cookie);
|
||||||
@ -44,7 +44,7 @@ public class AbstractRememberMeServicesTests {
|
|||||||
String[] decoded = services.decodeCookie(encoded);
|
String[] decoded = services.decodeCookie(encoded);
|
||||||
|
|
||||||
assertEquals(4, decoded.length);
|
assertEquals(4, decoded.length);
|
||||||
assertEquals("the", decoded[0]);
|
assertEquals("http://name", decoded[0]);
|
||||||
assertEquals("cookie", decoded[1]);
|
assertEquals("cookie", decoded[1]);
|
||||||
assertEquals("tokens", decoded[2]);
|
assertEquals("tokens", decoded[2]);
|
||||||
assertEquals("blah", decoded[3]);
|
assertEquals("blah", decoded[3]);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user