HttpSecurity.saml2login() - MVP Core Code
Implements minimal SAML 2.0 login/authentication functionality with the
following feature set:
- Supports IDP initiated login at the default url of /login/saml2/sso/{registrationId}
- Supports SP initiated login at the default url of /saml2/authenticate/{registrationId}
- Supports basic java-configuration via DSL
- Provides an integration sample using Spring Boot
Not implemented with this MVP
- Single Logout
- Dynamic Service Provider Metadata
Fixes gh-6019
This commit is contained in:
+33
-6
@@ -24,6 +24,11 @@ import org.springframework.util.Assert;
|
||||
import org.springframework.web.filter.GenericFilterBean;
|
||||
import org.springframework.web.util.HtmlUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
@@ -31,11 +36,6 @@ import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* For internal use with namespace configuration in the case where a user doesn't
|
||||
@@ -56,6 +56,7 @@ public class DefaultLoginPageGeneratingFilter extends GenericFilterBean {
|
||||
private boolean formLoginEnabled;
|
||||
private boolean openIdEnabled;
|
||||
private boolean oauth2LoginEnabled;
|
||||
private boolean saml2LoginEnabled;
|
||||
private String authenticationUrl;
|
||||
private String usernameParameter;
|
||||
private String passwordParameter;
|
||||
@@ -64,6 +65,7 @@ public class DefaultLoginPageGeneratingFilter extends GenericFilterBean {
|
||||
private String openIDusernameParameter;
|
||||
private String openIDrememberMeParameter;
|
||||
private Map<String, String> oauth2AuthenticationUrlToClientName;
|
||||
private Map<String, String> saml2AuthenticationUrlToProviderName;
|
||||
private Function<HttpServletRequest, Map<String, String>> resolveHiddenInputs = request -> Collections
|
||||
.emptyMap();
|
||||
|
||||
@@ -126,7 +128,7 @@ public class DefaultLoginPageGeneratingFilter extends GenericFilterBean {
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return formLoginEnabled || openIdEnabled || oauth2LoginEnabled;
|
||||
return formLoginEnabled || openIdEnabled || oauth2LoginEnabled || this.saml2LoginEnabled;
|
||||
}
|
||||
|
||||
public void setLogoutSuccessUrl(String logoutSuccessUrl) {
|
||||
@@ -157,6 +159,10 @@ public class DefaultLoginPageGeneratingFilter extends GenericFilterBean {
|
||||
this.oauth2LoginEnabled = oauth2LoginEnabled;
|
||||
}
|
||||
|
||||
public void setSaml2LoginEnabled(boolean saml2LoginEnabled) {
|
||||
this.saml2LoginEnabled = saml2LoginEnabled;
|
||||
}
|
||||
|
||||
public void setAuthenticationUrl(String authenticationUrl) {
|
||||
this.authenticationUrl = authenticationUrl;
|
||||
}
|
||||
@@ -186,6 +192,10 @@ public class DefaultLoginPageGeneratingFilter extends GenericFilterBean {
|
||||
this.oauth2AuthenticationUrlToClientName = oauth2AuthenticationUrlToClientName;
|
||||
}
|
||||
|
||||
public void setSaml2AuthenticationUrlToProviderName(Map<String, String> saml2AuthenticationUrlToProviderName) {
|
||||
this.saml2AuthenticationUrlToProviderName = saml2AuthenticationUrlToProviderName;
|
||||
}
|
||||
|
||||
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
|
||||
throws IOException, ServletException {
|
||||
HttpServletRequest request = (HttpServletRequest) req;
|
||||
@@ -287,6 +297,23 @@ public class DefaultLoginPageGeneratingFilter extends GenericFilterBean {
|
||||
}
|
||||
sb.append("</table>\n");
|
||||
}
|
||||
|
||||
if (this.saml2LoginEnabled) {
|
||||
sb.append("<h2 class=\"form-signin-heading\">Login with SAML 2.0</h2>");
|
||||
sb.append(createError(loginError, errorMsg));
|
||||
sb.append(createLogoutSuccess(logoutSuccess));
|
||||
sb.append("<table class=\"table table-striped\">\n");
|
||||
for (Map.Entry<String, String> relyingPartyUrlToName : saml2AuthenticationUrlToProviderName.entrySet()) {
|
||||
sb.append(" <tr><td>");
|
||||
String url = relyingPartyUrlToName.getKey();
|
||||
sb.append("<a href=\"").append(contextPath).append(url).append("\">");
|
||||
String partyName = HtmlUtils.htmlEscape(relyingPartyUrlToName.getValue());
|
||||
sb.append(partyName);
|
||||
sb.append("</a>");
|
||||
sb.append("</td></tr>\n");
|
||||
}
|
||||
sb.append("</table>\n");
|
||||
}
|
||||
sb.append("</div>\n");
|
||||
sb.append("</body></html>");
|
||||
|
||||
|
||||
+28
-11
@@ -15,17 +15,6 @@
|
||||
*/
|
||||
package org.springframework.security.web.authentication;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.context.support.MessageSourceAccessor;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
@@ -36,6 +25,17 @@ import org.springframework.security.core.SpringSecurityMessageSource;
|
||||
import org.springframework.security.web.WebAttributes;
|
||||
import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Locale;
|
||||
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.Mockito.mock;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Luke Taylor
|
||||
@@ -205,4 +205,21 @@ public class DefaultLoginPageGeneratingFilterTests {
|
||||
|
||||
assertThat(response.getContentAsString()).contains("<a href=\"/oauth2/authorization/google\">Google < > " ' &</a>");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void generatesForSaml2LoginAndEscapesClientName() throws Exception {
|
||||
DefaultLoginPageGeneratingFilter filter = new DefaultLoginPageGeneratingFilter();
|
||||
filter.setLoginPageUrl(DefaultLoginPageGeneratingFilter.DEFAULT_LOGIN_PAGE_URL);
|
||||
filter.setSaml2LoginEnabled(true);
|
||||
|
||||
String clientName = "Google < > \" \' &";
|
||||
filter.setSaml2AuthenticationUrlToProviderName(
|
||||
Collections.singletonMap("/saml/sso/google", clientName));
|
||||
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
filter.doFilter(new MockHttpServletRequest("GET", "/login"), response, chain);
|
||||
|
||||
assertThat(response.getContentAsString()).contains("Login with SAML 2.0");
|
||||
assertThat(response.getContentAsString()).contains("<a href=\"/saml/sso/google\">Google < > " ' &</a>");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user