We have hundreds of customers registered their ADFS with our application. At the time of login based on their EMail domain the application should automatically fetch the federated xml and redirects the user to the ADFS login page / URL belongs to the customer for the authentication. This is already implemented using spring-security-extensions library. Now we are migrating to spring-security-service-provider library. Is there any examples available that how a federated metadata xml can be loaded dynamically using RelyingPartyRegistration class
Comment From: abinesh-s
@jzheaux - could you please help me by pointing to right direction
Comment From: jzheaux
Happy to help, @abinesh-s. I think you can achieve this with minimal customization.
First, create a class that implements RelyingPartyRegistrationRepository, including a method for lookup by email domain. This will replace the Spring Boot default RelyingPartyRegistrationRepository like so:
@Component
public class DynamicRelyingPartyRegistrationRepository implements RelyingPartyRegistrationRepository {
private final Map<String, RelyingPartyRegistration> registrations = new HashMap<>();
@Override
public RelyingPartyRegistration findByRegistrationId(String registrationId) {
return this.registrations.get(registrationId);
}
public RelyingPartyRegistration computeByEmailDomain(String domain) {
String metadtaLocation = computeMetadataLocation(domain);
RelyingPartyRegistration registration = RelyingPartyRegistrations
.fromMetadataLocation(metadataLocation).registrationId(domain).build();
this.registrations.put(domain, registration);
return registration;
}
}
Then wire it into a custom controller:
@Controller
public class LoginController {
@Autowired
private DynamicRelyingPartyRegistrationRepository registrations;
@PostMapping("/discovery")
public String discovery(@RequestParam("email") String email) {
String domain = computeDomain(email);
RelyingPartyRegistration registration = this.registrations.computeByEmailDomain(domain);
return "redirect:/saml2/authenticate/" + registration.getRegistrationId();
}
}
This will invoke Spring Securiy's AuthnRequest filter for the registration tied to that domain.
Would this approach work for you?
Comment From: abinesh-s
Hi @jzheaux , thanks for the reply. That was helpful to make some progress. But, I stuck with below issue and have added my code here
My controller
package com.sample.test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.servlet.ModelAndView;
@Controller
public class HelloController {
@Autowired
private DynamicRelyingPartyRegistrationRepository registrations;
private static final Logger logger = LoggerFactory.getLogger(HelloController.class);
@GetMapping("/samlLogin")
public String samlLogin(Model model, @AuthenticationPrincipal Saml2AuthenticatedPrincipal principal) {
RelyingPartyRegistration registration = this.registrations.computeByEmailDomain(null);
logger.info("Asserting Party Details - Single Sign on Service");
logger.info(registration.getAssertingPartyDetails().getSingleSignOnServiceLocation());
// return "redirect:"+registration.getAssertingPartyDetails().getSingleSignOnServiceLocation();
return "redirect:/saml2/authenticate/" + registration.getRegistrationId();
}
@GetMapping("/loginPage")
public String index(Model model, @AuthenticationPrincipal Saml2AuthenticatedPrincipal principal) {
String emailAddress = principal.getFirstAttribute("email");
model.addAttribute("emailAddress", emailAddress);
model.addAttribute("userAttributes", principal.getAttributes());
return "index";
}
@GetMapping("/")
public ModelAndView welcomePage() {
ModelAndView model = new ModelAndView();
model.setViewName("welcomePage");
return model;
}
}
DynamicRelyingPartyRegistrationRepository
package com.sample.test;
import java.util.HashMap;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.stereotype.Component;
@Component
public class DynamicRelyingPartyRegistrationRepository implements RelyingPartyRegistrationRepository {
private final Map<String, RelyingPartyRegistration> registrations = new HashMap<>();
private static final Logger logger = LoggerFactory.getLogger(HelloController.class);
@Override
public RelyingPartyRegistration findByRegistrationId(String registrationId) {
return this.registrations.get(registrationId);
}
public RelyingPartyRegistration computeByEmailDomain(String domain) {
String metadataLocation = "http://localhost:8080/getMetadata";
String assertionConsumerServiceLocation = "https://w10cvslfg3.blr.apac.com:8443/SpringMVCNew/login/saml2/sso/SpringMVCNew";
RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistrations
.fromMetadataLocation(metadataLocation)
.registrationId("SpringMVCNew")
.assertionConsumerServiceLocation(assertionConsumerServiceLocation)
.build();
logger.info("Inside Dynamic Relying Party Registration : "+relyingPartyRegistration.getAssertionConsumerServiceLocation());
return relyingPartyRegistration;
}
}
Security.xml
<b:beans xmlns="http://www.springframework.org/schema/security"
xmlns:b="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd">
<http auto-config="true">
<intercept-url pattern="/samlLogin" access="authenticated"/>
<saml2-login />
<saml2-logout />
</http>
<user-service>
<user name="user" password="{noop}password" authorities="ROLE_USER" />
</user-service>
</b:beans>
spring-servlet.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- Provide support for component scanning -->
<context:component-scan base-package="com.sample.test" />
<!--Provide support for conversion, formatting and validation -->
<mvc:annotation-driven/>
<mvc:annotation-driven>
<mvc:argument-resolvers>
<bean class="org.springframework.security.web.method.annotation.AuthenticationPrincipalArgumentResolver"/>
</mvc:argument-resolvers>
</mvc:annotation-driven>
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/templates/" />
<property name="suffix" value=".jsp" />
</bean>
</beans>
Web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
<display-name>SpringMVCNew</display-name>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/spring-servlet.xml
/WEB-INF/spring/*.xml
</param-value>
</context-param>
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>spring</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>spring</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
Facing 2 issues
- Dont want to see the below login page. Instead the application should redirect to the ADFS login page.
- As per the suggestion provided and the page redirect is failing and getting below screen
Any help will be appreciated.