3

I am integrating SAML into a Spring Boot application using the implementation built into Spring Security 5.6. Much of the online help references the now deprecated external library implementation (https://github.com/spring-projects/spring-security-saml) so I am following this document:

https://docs.spring.io/spring-security/reference/servlet/saml2/login/index.html

I have this interaction working and I am authenticating from SAML now. Here is the configuration:

spring:
  security:
    saml2:
      relyingparty:
        registration:
          adfs:
            signing:
              credentials:
                - private-key-location: "file:///C:/tmp/keys/private.key"
                  certificate-location: "file:///C:/tmp/keys/public.crt"
            identityprovider:
              entity-id: << SNIPPED >>
              verification.credentials:
                - certificate-location: "classpath:saml-certificate/adfs.crt"
              singlesignon:
                url: << SNIPPED >>
                sign-request: true

The code looks like this now:

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    private final RelyingPartyRegistrationRepository _relyingPartyRegistrationRepository;

    @Autowired
    public WebSecurityConfig(RelyingPartyRegistrationRepository relyingPartyRegistrationRepository {
        _relyingPartyRegistrationRepository = relyingPartyRegistrationRepository;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // add auto-generation of ServiceProvider Metadata at {baseUrl}/saml2/service-provider-metadata/ims-adfs
        RelyingPartyRegistrationResolver relyingPartyRegistrationResolver = new DefaultRelyingPartyRegistrationResolver(_relyingPartyRegistrationRepository);
        Saml2MetadataFilter filter = new Saml2MetadataFilter(relyingPartyRegistrationResolver, new OpenSamlMetadataResolver());

        http
                .authorizeRequests()
                .antMatchers("/seer.ico", "/monitor", "/**/check").permitAll()
                .anyRequest().authenticated()
                .and().sessionManagement()
                .and().csrf().ignoringAntMatchers("/servers/**/searches")
                .and()
                .saml2Login(withDefaults())
                .saml2Logout(withDefaults())
                .addFilterBefore(filter, Saml2WebSsoAuthenticationFilter.class);
    }

}

The issue is that I need to remap the user details to set up correct roles and also limit the logins to users who have the correct Linux permissions. The permissions are being correcting returned in the assertion; I just need to verify they are correct or fail the login.

The Spring Security documentation has a section on coordinating with a UserDetailsService which seems like exactly what I need.

https://docs.spring.io/spring-security/reference/servlet/saml2/login/authentication.html#servlet-saml2login-opensamlauthenticationprovider-userdetailsservice

However when I implement it like the example, I now get the following error from Spring:

No assertions found in response.

Here is the updated code:

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    private final RelyingPartyRegistrationRepository _relyingPartyRegistrationRepository;

    private final AuthenticationService _userDetailsService;

    @Autowired
    public WebSecurityConfig(RelyingPartyRegistrationRepository relyingPartyRegistrationRepository, AuthenticationService userDetailsService) {
        _relyingPartyRegistrationRepository = relyingPartyRegistrationRepository;
        _userDetailsService = userDetailsService;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
         OpenSaml4AuthenticationProvider authenticationProvider = new OpenSaml4AuthenticationProvider();
         authenticationProvider.setResponseAuthenticationConverter(responseToken -> {
             Saml2Authentication authentication = OpenSaml4AuthenticationProvider.createDefaultResponseAuthenticationConverter().convert(responseToken);

             Assertion assertion = responseToken.getResponse().getAssertions().get(0);
             String username = assertion.getSubject().getNameID().getValue();

             UserDetails userDetails = _userDetailsService.loadUserByUsername(username);
             authentication.setDetails(userDetails);

             return authentication;
         });

        // add auto-generation of ServiceProvider Metadata at {baseUrl}/saml2/service-provider-metadata/ims-adfs
        RelyingPartyRegistrationResolver relyingPartyRegistrationResolver = new DefaultRelyingPartyRegistrationResolver(_relyingPartyRegistrationRepository);
        Saml2MetadataFilter filter = new Saml2MetadataFilter(relyingPartyRegistrationResolver, new OpenSamlMetadataResolver());

        http
                .authorizeRequests()
                .antMatchers("/seer.ico", "/monitor", "/**/check").permitAll()
                .anyRequest().authenticated()
                .and().sessionManagement()
                .and().csrf().ignoringAntMatchers("/servers/**/searches")
                .and()
                .saml2Login(saml2 -> saml2.authenticationManager(new ProviderManager(authenticationProvider)))
                .saml2Logout(withDefaults())
                .addFilterBefore(filter, Saml2WebSsoAuthenticationFilter.class);
    }
}

So basically this gets a correct response from SAML:

.saml2Login(withDefaults())

and when I switch it to this then the SAML response is missing the Assertion:

.saml2Login(saml2 -> saml2.authenticationManager(new ProviderManager(authenticationProvider)))

I've been looking all over for other solutions but like I said there are very few examples that don't use the old deprecated SAML library for Spring.

Any thoughts?

Chuck M
  • 1,175
  • 3
  • 17
  • 26
  • I was able to get this working with the help of this post, however this is totally not the how the documentation says to accomplish it. Is there a downside to doing it this way? https://stackoverflow.com/questions/70338287/saml2-authentication-with-authorization-based-on-saml-assertions – Chuck M Mar 30 '22 at 13:37
  • Okay, I think I may found the issue. If I follow the documentation and use OpenSaml4AuthenticationProvider then my login does not work. If I use OpenSamlAuthenticationProvider however it does work. OpenSaml4AuthenticationProvider is deprecated and doesn't match the documentation but it definitely is working for me. Is there an issue with using that class? Is the documentation just wrong? – Chuck M Mar 30 '22 at 18:09
  • Check if this heps: https://stackoverflow.com/questions/71429215/customizing-opensaml4authenticationprovider-in-spring-security-saml2 – pringi Mar 31 '22 at 10:39
  • Also check if spring boot is not importing version 3 and version 4 of open saml. Check this example: https://github.com/spring-projects/spring-security-samples/blob/main/servlet/spring-boot/java/saml2/login/build.gradle. Also you are not setting an assertion validator (setAssertionValidator) like in the example of Spring security (https://docs.spring.io/spring-security/reference/servlet/saml2/login/authentication.html). I guess this is the main problem. – pringi Mar 31 '22 at 10:52
  • I've never done a bounty before. How do I assign it to you? I think it might only work with new comments as opposed to sub-comments, right? – Chuck M Mar 31 '22 at 14:49
  • I've added as answer. If you concur can assign the bounty to it. – pringi Apr 01 '22 at 10:03

1 Answers1

2

Check if Spring Boot is importing version 3 and version 4 of Open SAML. If it is use only version 4.

Spring Security Samples has an example for SAML2. The build.gradle in the project contains the following:

repositories {
    mavenCentral()
    maven { url "https://repo.spring.io/milestone" }
    maven { url "https://repo.spring.io/snapshot" }
    maven { url "https://build.shibboleth.net/nexus/content/repositories/releases/" }
}

dependencies {
    constraints {
        implementation "org.opensaml:opensaml-core:4.1.1"
        implementation "org.opensaml:opensaml-saml-api:4.1.1"
        implementation "org.opensaml:opensaml-saml-impl:4.1.1"
    }
    implementation 'org.springframework.boot:spring-boot-starter-security'
    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.security:spring-security-saml2-service-provider'
    implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6'

    testImplementation 'net.sourceforge.htmlunit:htmlunit:2.44.0'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'org.springframework.security:spring-security-test'
    testImplementation 'org.awaitility:awaitility:4.2.0'
}

References:

pringi
  • 3,987
  • 5
  • 35
  • 45
  • 1
    I'm using Maven, but adding the OpenSAML4 dependencies (and the external shibboleth repository) to the pom.xml fixed my issue. Hopefully the non-deprecated version becomes the default in the next version of Spring Security. – Chuck M Apr 01 '22 at 14:09