首页 > 解决方案 > Azure AD SAML Authentication with Spring Security 5.3.2 (not SAML Extension)

问题描述

I am attempting to replace CAS with Azure Active Directory SAML authentication (SSO) in a Spring Boot API. My version of Spring Security is 5.3.2. Spring boot is 2.3.0.

Documentation has been hard to find. I think this is explained by 8685. I found 8010 and attempted the workaround mentioned there, but my breakpoints there are not getting hit.

Given the current state of the transition from SAML Extension to Spring Security, should I be using the old SAML Extension? I can reach my "success" endpoint with a JSESSIONID and SAMLReponse, but it's encrypted. Is this something I need to do myself? (If so, how?) The SecurityContext / user details are not getting set. I see AccessDenied stack traces in my logs, but I think that's a symptom of the Anonymous user context.

Relevant code is below. I have application.yml and application.properties files, but all config is annotations-based. If you see anything way off base, please let me know! Any guidance would be appreciated.

Here is my SecurityConfig:

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    RelyingPartyRegistration getSaml2AuthenticationConfiguration() throws Exception {
        // remote IDP entity ID
        String idpEntityId = "https://sts.windows.net/xxxxxxxxxxxx/";
        // remote WebSSO Endpoint - Where to Send AuthNRequests to
        String webSsoEndpoint = "https://login.microsoftonline.com/xxxxxxxxxxxx/saml2";
        // local registration ID
        String registrationId = "xxxxxxxxxxxx";
        // local entity ID - autogenerated based on URL
        String localEntityIdTemplate = "xxxxxxxxxxxx.local";
        // local signing (and decryption key)
        Saml2X509Credential signingCredential = getSigningCredential(); //private method not included
        // IDP certificate for verification of incoming messages
        Saml2X509Credential idpVerificationCertificate = getVerificationCertificate();  //private method not included
        String acsUrlTemplate = "https://xxxxxxxxxxxx.local/success"; //REST endpoint, see below
        return RelyingPartyRegistration.withRegistrationId(registrationId)
                .providerDetails(config -> config.entityId(idpEntityId))
                .providerDetails(config -> config.webSsoUrl(webSsoEndpoint)).credentials(c -> c.add(signingCredential))
                .credentials(c -> c.add(idpVerificationCertificate)).localEntityIdTemplate(localEntityIdTemplate)
                .assertionConsumerServiceUrlTemplate(acsUrlTemplate).build();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        // Just a test
        OpenSamlAuthenticationProvider provider = new OpenSamlAuthenticationProvider();

        http
            .headers()
            .frameOptions()
            .sameOrigin()
            .httpStrictTransportSecurity()
            .disable()
            .and()
            .authorizeRequests()
            //... more antMatchers and permitAlls
            .antMatchers("/success").permitAll()
            .antMatchers("/login").permitAll()
            .antMatchers("/logout").permitAll()
            .antMatchers("/error").permitAll().anyRequest().authenticated().and()
            .csrf().disable()
            .saml2Login(
                    saml2 -> {
                        try {
                            saml2
                            .authenticationManager(a -> {
                                // This code is never reached
                                Authentication result = provider.authenticate(a);
                                Saml2Authentication saml2Authentication = (Saml2Authentication) result;
                                return result; 
                            }).relyingPartyRegistrationRepository(
                                    new InMemoryRelyingPartyRegistrationRepository(getSaml2AuthenticationConfiguration())
                            )
                            .loginProcessingUrl("/login/{registrationId}");
                        } catch (Exception e) {
                            // It made me put this try/catch here... this isn't getting reached either
                            e.printStackTrace();
                        }
                    });
    }

}

And my REST endpoint:

@RestController
public class HelloController {

    @RequestMapping(value = "/success", method=RequestMethod.POST)
    public String saml2Post(HttpServletRequest request) throws IOException {
        String jSessionId = request.getHeader("cookie");
        System.out.println(jSessionId);
        
        String samlResponse = request.getReader().lines().collect(Collectors.joining(System.lineSeparator()));
        System.out.println(samlResponse);
        
        return "login success";
    }

}

And my gradle dependencies (Gradle 6.5):

dependencies {

    implementation 'org.springframework.boot:spring-boot-starter-security'
    
    compile 'org.springframework.security:spring-security-config'
    compile 'org.springframework.security:spring-security-saml2-service-provider'
    compile 'org.springframework.boot:spring-boot-starter-thymeleaf'
    compile 'org.springframework.boot:spring-boot-starter-web'
    compile 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5'
    implementation 'org.springframework.boot:spring-boot-starter-freemarker'
    implementation 'org.springframework.boot:spring-boot-starter-integration'
    implementation 'org.springframework.boot:spring-boot-starter-jdbc'
    implementation 'org.springframework.boot:spring-boot-starter-mail'
    compile 'org.springframework.security:spring-security-oauth2-client'
    compile 'org.springframework.security:spring-security-oauth2-jose'
    implementation 'joda-time:joda-time:2.10.6'
    implementation 'com.google.guava:guava:29.0-jre'
    implementation 'com.opencsv:opencsv:5.2'
    implementation 'org.apache.commons:commons-lang3:3.10'
    implementation 'net.minidev:json-smart:2.3'

    developmentOnly 'org.springframework.boot:spring-boot-devtools'
    runtimeOnly 'com.microsoft.sqlserver:mssql-jdbc'
    runtimeOnly 'org.hsqldb:hsqldb'
    providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
    testImplementation('org.springframework.boot:spring-boot-starter-test') {
        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    }
    testImplementation 'org.springframework.integration:spring-integration-test'
    testImplementation 'org.springframework.security:spring-security-test'
}

标签: spring-securityspring-oauth2

解决方案


推荐阅读