Polish Federation Example
Issue gh-122
This commit is contained in:
@@ -29,14 +29,14 @@ import org.opensaml.saml.saml2.core.impl.ResponseUnmarshaller;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.security.saml2.core.OpenSamlInitializationService;
|
||||
import org.springframework.security.saml2.core.Saml2Error;
|
||||
import org.springframework.security.saml2.core.Saml2ErrorCodes;
|
||||
import org.springframework.security.saml2.core.Saml2ParameterNames;
|
||||
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException;
|
||||
import org.springframework.security.saml2.provider.service.registration.InMemoryRelyingPartyRegistrationRepository;
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
|
||||
import org.springframework.security.saml2.provider.service.web.DefaultRelyingPartyRegistrationResolver;
|
||||
import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver;
|
||||
|
||||
public class EntityIdRelyingPartyRegistrationResolver implements RelyingPartyRegistrationResolver {
|
||||
@@ -47,25 +47,34 @@ public class EntityIdRelyingPartyRegistrationResolver implements RelyingPartyReg
|
||||
private final ResponseUnmarshaller responseUnmarshaller;
|
||||
private final ParserPool parserPool;
|
||||
|
||||
private final RelyingPartyRegistrationResolver delegate;
|
||||
private final InMemoryRelyingPartyRegistrationRepository registrations;
|
||||
|
||||
public EntityIdRelyingPartyRegistrationResolver(RelyingPartyRegistrationRepository registrations) {
|
||||
public EntityIdRelyingPartyRegistrationResolver(InMemoryRelyingPartyRegistrationRepository registrations) {
|
||||
XMLObjectProviderRegistry registry = ConfigurationService.get(XMLObjectProviderRegistry.class);
|
||||
this.responseUnmarshaller = (ResponseUnmarshaller) registry.getUnmarshallerFactory()
|
||||
.getUnmarshaller(Response.DEFAULT_ELEMENT_NAME);
|
||||
this.parserPool = registry.getParserPool();
|
||||
this.delegate = new DefaultRelyingPartyRegistrationResolver(registrations);
|
||||
this.registrations = registrations;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RelyingPartyRegistration resolve(HttpServletRequest request, String relyingPartyRegistrationId) {
|
||||
if (relyingPartyRegistrationId != null) {
|
||||
return this.delegate.resolve(request, relyingPartyRegistrationId);
|
||||
return this.registrations.findByRegistrationId(relyingPartyRegistrationId);
|
||||
}
|
||||
return this.delegate.resolve(request, resolveRegistrationId(request));
|
||||
String entityId = resolveEntityId(request);
|
||||
if (entityId == null) {
|
||||
return null;
|
||||
}
|
||||
for (RelyingPartyRegistration registration : this.registrations) {
|
||||
if (entityId.equals(registration.getAssertingPartyDetails().getEntityId())) {
|
||||
return registration;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String resolveRegistrationId(HttpServletRequest request) {
|
||||
private String resolveEntityId(HttpServletRequest request) {
|
||||
String saml2Response = request.getParameter(Saml2ParameterNames.SAML_RESPONSE);
|
||||
if (saml2Response == null) {
|
||||
return null;
|
||||
|
||||
@@ -16,13 +16,8 @@
|
||||
|
||||
package example;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal;
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
@@ -30,13 +25,6 @@ import org.springframework.web.bind.annotation.GetMapping;
|
||||
@Controller
|
||||
public class IndexController {
|
||||
|
||||
private final Iterable<RelyingPartyRegistration> registrations;
|
||||
|
||||
@Autowired
|
||||
public IndexController(Iterable<RelyingPartyRegistration> registrations) {
|
||||
this.registrations = registrations;
|
||||
}
|
||||
|
||||
@GetMapping("/")
|
||||
public String index(Model model, @AuthenticationPrincipal Saml2AuthenticatedPrincipal principal) {
|
||||
String emailAddress = principal.getFirstAttribute("email");
|
||||
@@ -45,14 +33,4 @@ public class IndexController {
|
||||
return "index";
|
||||
}
|
||||
|
||||
@GetMapping("/login")
|
||||
public String login(Model model) {
|
||||
Collection<String> urls = new ArrayList<>();
|
||||
for (RelyingPartyRegistration registration : this.registrations) {
|
||||
urls.add("/saml/login?id=" + registration.getRegistrationId());
|
||||
}
|
||||
model.addAttribute("urls", urls);
|
||||
return "login";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -40,11 +40,10 @@ import org.springframework.web.filter.OncePerRequestFilter;
|
||||
public class SamlExtensionUrlForwardingFilter extends OncePerRequestFilter {
|
||||
|
||||
// @formatter:off
|
||||
private static final Map<String, String> urlMapping = Map.of("/saml/SSO", "/login/saml2/sso/one",
|
||||
"/saml/login", "/saml2/authenticate/one",
|
||||
private static final Map<String, String> urlMapping = Map.of("/saml/SSO", "/login/saml2/sso/sp",
|
||||
"/saml/logout", "/logout/saml2/slo",
|
||||
"/saml/SingleLogout", "/logout/saml2/slo",
|
||||
"/saml/metadata", "/saml2/service-provider-metadata/one");
|
||||
"/saml/metadata", "/saml2/service-provider-metadata/sp");
|
||||
// @formatter:on
|
||||
|
||||
private final RequestMatcher matcher = createRequestMatcher();
|
||||
|
||||
@@ -20,11 +20,11 @@ import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.KeyStore;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.security.interfaces.RSAPrivateKey;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
@@ -41,13 +41,10 @@ import org.springframework.security.saml2.core.Saml2X509Credential;
|
||||
import org.springframework.security.saml2.provider.service.metadata.OpenSamlMetadataResolver;
|
||||
import org.springframework.security.saml2.provider.service.registration.InMemoryRelyingPartyRegistrationRepository;
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrations;
|
||||
import org.springframework.security.saml2.provider.service.web.DefaultRelyingPartyRegistrationResolver;
|
||||
import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationTokenConverter;
|
||||
import org.springframework.security.saml2.provider.service.web.Saml2MetadataFilter;
|
||||
import org.springframework.security.saml2.provider.service.web.authentication.OpenSaml4AuthenticationRequestResolver;
|
||||
import org.springframework.security.saml2.provider.service.web.authentication.Saml2AuthenticationRequestResolver;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
|
||||
@Configuration
|
||||
@@ -59,10 +56,10 @@ public class SecurityConfiguration {
|
||||
// @formatter:off
|
||||
http
|
||||
.authorizeHttpRequests((authorize) -> authorize
|
||||
.requestMatchers("/login", "/error").permitAll()
|
||||
.requestMatchers("/error").permitAll()
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.saml2Login((saml2) -> saml2.loginPage("/login"))
|
||||
.saml2Login(Customizer.withDefaults())
|
||||
.saml2Logout(Customizer.withDefaults());
|
||||
// @formatter:on
|
||||
return http.build();
|
||||
@@ -70,18 +67,11 @@ public class SecurityConfiguration {
|
||||
|
||||
|
||||
@Bean
|
||||
Saml2AuthenticationTokenConverter usingEntityId(RelyingPartyRegistrationRepository repository) {
|
||||
Saml2AuthenticationTokenConverter usingEntityId(InMemoryRelyingPartyRegistrationRepository repository) {
|
||||
var registrations = new EntityIdRelyingPartyRegistrationResolver(repository);
|
||||
return new Saml2AuthenticationTokenConverter(registrations);
|
||||
}
|
||||
|
||||
@Bean
|
||||
Saml2AuthenticationRequestResolver usingQueryParameter(RelyingPartyRegistrationRepository repository) {
|
||||
var registrations = new DefaultRelyingPartyRegistrationResolver(repository);
|
||||
return new OpenSaml4AuthenticationRequestResolver((request, id) ->
|
||||
registrations.resolve(request, request.getParameter("id")));
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(-101)
|
||||
FilterRegistrationBean<Saml2MetadataFilter> metadata(Iterable<RelyingPartyRegistration> repository) {
|
||||
@@ -95,10 +85,11 @@ public class SecurityConfiguration {
|
||||
@Value("classpath:credentials/rp-private.key") RSAPrivateKey key,
|
||||
@Value("classpath:credentials/rp-certificate.crt") File cert) {
|
||||
Saml2X509Credential signing = Saml2X509Credential.signing(key, x509Certificate(cert));
|
||||
Registration registration = properties.getRegistration().get("one");
|
||||
Registration registration = properties.getRegistration().values().iterator().next();
|
||||
return new InMemoryRelyingPartyRegistrationRepository(RelyingPartyRegistrations
|
||||
.collectionFromMetadataLocation(registration.getAssertingparty().getMetadataUri())
|
||||
.stream().map((builder) -> builder
|
||||
.registrationId(UUID.randomUUID().toString())
|
||||
.entityId(registration.getEntityId())
|
||||
.assertionConsumerServiceLocation(registration.getAcs().getLocation())
|
||||
.singleLogoutServiceLocation(registration.getSinglelogout().getUrl())
|
||||
|
||||
@@ -8,12 +8,12 @@ spring:
|
||||
saml2:
|
||||
relyingparty:
|
||||
registration:
|
||||
one:
|
||||
entity-id: "{baseUrl}/saml2/service-provider-metadata/one"
|
||||
sp:
|
||||
entity-id: "http://localhost:8080/saml2/service-provider-metadata/one"
|
||||
singlelogout:
|
||||
binding: POST
|
||||
url: "{baseUrl}/saml/logout"
|
||||
responseUrl: "{baseUrl}/saml/SingleLogout"
|
||||
url: "http://localhost:8080/saml/logout"
|
||||
responseUrl: "http://localhost:8080/saml/SingleLogout"
|
||||
acs:
|
||||
location: "{baseUrl}/saml/SSO"
|
||||
location: "http://localhost:8080/saml/SSO"
|
||||
assertingparty.metadata-uri: https://dev-05937739.okta.com/app/exk598vc9bHhwoTXM5d7/sso/saml/metadata
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
<!--
|
||||
~ Copyright 2002-2021 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
|
||||
~
|
||||
~ https://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.
|
||||
-->
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Please sign in</title>
|
||||
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" rel="stylesheet"
|
||||
integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous">
|
||||
<link href="https://getbootstrap.com/docs/4.0/examples/signin/signin.css" rel="stylesheet" crossorigin="anonymous">
|
||||
</head>
|
||||
<body data-new-gr-c-s-check-loaded="8.904.0" data-gr-ext-installed="">
|
||||
<div class="container">
|
||||
<h2 class="form-signin-heading">Login with SAML 2.0</h2>
|
||||
<table class="table table-striped">
|
||||
<tbody>
|
||||
<tr th:each="url : ${urls}">
|
||||
<td><a th:href="${url}"><span th:text="${url}"></span></a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user