I have created a sample project which can demonstrate SAML 2 SSO capabilities with saml providers such as Azure AD and Okta.
I was able to configure both of above providers at once in spring configuration by using RelyingPartyRegistrationRepository
and both of them are working as expected.
@Bean
protected RelyingPartyRegistrationRepository relyingPartyRegistrations() throws Exception {
RelyingPartyRegistration oktaRegistration = RelyingPartyRegistrations.fromMetadataLocation("https://trial-27.okta.com/app/e/sso/saml/metadata").registrationId("okta").build();
RelyingPartyRegistration azureRegistration = RelyingPartyRegistrations.fromMetadataLocation("file:D:\\saml-test-5.xml").registrationId("azure-saml-test").build();
List<RelyingPartyRegistration> registrationList = new ArrayList<>();
registrationList.add(oktaRegistration);
registrationList.add(azureRegistration);
return new InMemoryRelyingPartyRegistrationRepository(registrationList);
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests(authorize ->
authorize.antMatchers("/").permitAll().anyRequest().authenticated()
).saml2Login();
RelyingPartyRegistrationResolver relyingPartyRegistrationResolver = new DefaultRelyingPartyRegistrationResolver(relyingPartyRegistrations());
Saml2MetadataFilter filter = new Saml2MetadataFilter(relyingPartyRegistrationResolver, new OpenSamlMetadataResolver());
http.addFilterBefore(filter, Saml2WebSsoAuthenticationFilter.class);
return http.build();
}
I would like to know whether there is any way to create RelyingPartyRegistrationRepository
dynamically once the application fully started. The requirement is to take the SAML metadata file from user in some sort of a form upload and then create RelyingPartyRegistrationRepository
based on it.
The issue is, RelyingPartyRegistrationRepository
is a Spring bean which is used by the Spring security internals. In this case even though we could create new RelyingPartyRegistrationRepository
instances, will Spring security take them dynamically?
You will not create multiple RelyingPartyRegistrationRepository
, you will create your custom implementation of RelyingPartyRegistrationRepository
that accepts adding new entries to it. A simple example:
@Service
public class MyRelyingPartyRegistrationRepository implements RelyingPartyRegistrationRepository {
private final List<RelyingPartyRegistration> registrations = new ArrayList<>();
public MyRelyingPartyRegistrationRepository() {
addDefaultRegistrations();
}
private void addDefaultRegistrations() {
RelyingPartyRegistration oktaRegistration = RelyingPartyRegistrations.fromMetadataLocation("https://trial-27.okta.com/app/e/sso/saml/metadata").registrationId("okta").build();
RelyingPartyRegistration azureRegistration = RelyingPartyRegistrations.fromMetadataLocation("file:D:\\saml-test-5.xml").registrationId("azure-saml-test").build();
add(oktaRegistration);
add(azureRegistration);
}
@Override
public RelyingPartyRegistration findByRegistrationId(String registrationId) {
for (RelyingPartyRegistration registration : this.registrations) {
if (registration.getRegistrationId().equals(registrationId)) {
return registration;
}
}
return null;
}
public void add(RelyingPartyRegistration newRegistration) {
this.registrations.add(newRegistration);
}
}
And then in a Controller, for example, you can autowire this dependency and add new registrations to it:
@RestController
public class SamlController {
private final MyRelyingPartyRegistrationRepository repository;
@PostMapping("/registration")
public void addRegistration(/* receive it somehow */) {
this.repository.add(theRegistration);
}
}