2

I like the idea of using spring-security-saml2-service-provider - from of docs: https://docs.spring.io/spring-security/reference/5.6.0-RC1/servlet/saml2/index.html Instead of spring-security-saml2-core it looks way less boilerplate, but I catch 400 response when I send App Embed Link from Okta admin app. Through debug it seems that

protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        AbstractSaml2AuthenticationRequest authenticationRequest = this.authenticationRequestResolver.resolve(request);
        if (authenticationRequest == null) {
            filterChain.doFilter(request, response);..}

can't resolve the incoming request,but I am not sure whether it's related. My yml config:

 security:
    saml2:
      relyingparty:
        registration:
          okta:
            identityprovider:
              entity-id: http://www.okta.com/exk1juy5xrR5BsW44697
              verification.credentials:
                - certificate-location: "classpath:saml/okta.cert"
              singlesignon.url: https://trial-8410773.okta.com/app/trial-8410773_templatemanager_2/exk1juy5xrR5BsW44697/sso/saml
              singlesignon.sign-request: false
              assertingparty.metadata-uri: https://trial-8410773.okta.com/app/trial-8410773_templatemanager_2/exk1juy5xrR5BsW44697/sso/saml/metadata

My Okta config:

GENERAL
Single Sign On URLhttp://localhost:8080/api/v1/saml2/SSO
Requestable SSO URLsURLIndex
http://localhost:8080/api/v1/saml2/SSO0Recipient URLhttp://localhost:8080/api/v1/saml2/SSODestination URLhttp://localhost:8080/api/v1/saml2/SSOAudience Restrictionhttp://localhost:8080/saml/metadata

Also I provide endpoint for saml authentication:

@RequestMapping(SsoAuthenticationController.BASE_NAME)
public interface SsoAuthenticationController {

    final String BASE_NAME = "/v1/saml2/SSO";

    @GetMapping("/")
    public ResponseEntity<HttpStatus> index( Saml2AuthenticatedPrincipal principal) ;
}

Actual security config:

http.cors()
        .and()
        .csrf()
        .disable()
        .authorizeRequests()
        .antMatchers(SECURITY_WHITELIST)
        .permitAll()
        .anyRequest()
        .authenticated()
        /*.and()
        .httpBasic()
        .and()
        .exceptionHandling()
        .authenticationEntryPoint(authenticationEntryPoint).and().sessionManagement()
        .sessionCreationPolicy(SessionCreationPolicy.STATELESS)*/
        .and()
        .saml2Login(Customizer.withDefaults());

Here is Saml interceptor's logs for google chrome: https://pastebin.com/Be3NZe5B

Any ideas?

tarmogoyf
  • 298
  • 3
  • 17

1 Answers1

5

I created a Spring Boot 3 + SAML example with Okta recently. Hopefully, these instructions help.

Create a Spring Boot app using start.spring.io. Select the following options:

  • Project: Gradle
  • Spring Boot: 3.0.0 (SNAPSHOT)
  • Dependencies: Spring Web, Spring Security, Thymeleaf

Add src/main/java/com/example/demo/HomeController.java to populate the authenticated user's information.

package com.example.demo;

import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class HomeController {

    @RequestMapping("/")
    public String home(@AuthenticationPrincipal Saml2AuthenticatedPrincipal principal, Model model) {
        model.addAttribute("name", principal.getName());
        model.addAttribute("emailAddress", principal.getFirstAttribute("email"));
        model.addAttribute("userAttributes", principal.getAttributes());
        return "home";
    }

}

Create a src/main/resources/templates/home.html file to render the user's information.

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org"
      xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity6">
<head>
    <title>Spring Boot and SAML</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
</head>
<body>

<h1>Welcome</h1>
<p>You are successfully logged in as <span sec:authentication="name"></span></p>
<p>Your email address is <span th:text="${emailAddress}"></span>.</p>
<p>Your authorities are <span sec:authentication="authorities"></span>.</p>
<h2>All Your Attributes</h2>
<dl th:each="userAttribute : ${userAttributes}">
    <dt th:text="${userAttribute.key}"></dt>
    <dd th:text="${userAttribute.value}"></dd>
</dl>

<form th:action="@{/logout}" method="post">
    <button id="logout" type="submit">Logout</button>
</form>

</body>
</html>

Create a src/main/resources/application.yml file to contain your metadata URI.

spring:
  security:
    saml2:
      relyingparty:
        registration:
            assertingparty:
              metadata-uri: <your-metadata-uri>

Then, change build.gradle to use thymeleaf-extras-springsecurity6 instead of thymeleaf-extras-springsecurity5 and add Spring Security SAML's dependency:

implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6'
implementation 'org.springframework.security:spring-security-saml2-service-provider'

To get the metadata URI from Okta, log in to your account and go to Applications > Create App Integration. Select SAML 2.0 and click Next. Name your app something like Spring Boot SAML and click Next.

Use the following settings:

  • Single sign on URL: http://localhost:8080/login/saml2/sso/okta
  • Use this for Recipient URL and Destination URL: ✅ (the default)
  • Audience URI: http://localhost:8080/saml2/service-provider-metadata/okta

Then click Next. Select the following options:

  • I'm an Okta customer adding an internal app
  • This is an internal app that we have created

Select Finish.

Okta will create your app, and you will be redirected to its Sign On tab. Scroll down to the SAML Signing Certificates and go to SHA-2 > Actions > View IdP Metadata. You can right-click and copy this menu item's link or open its URL. Copy the resulting link to your clipboard. It should look something like the following:

https://dev-13337.okta.com/app/<random-characters>/sso/saml/metadata

Go to your app's Assignment tab and assign access to the Everyone group.

Paste your metadata URI in to your application.yml file. Start the app using ./gradlew bootRun and open http://localhost:8080 in your favorite browser. You should be redirected to login.

Matt Raible
  • 8,187
  • 9
  • 61
  • 120
  • Thanks for your reply, after I switch to basic single sign on URL http://localhost:8080/login/saml2/sso/okta I catch 401, which is sort of progress for me. – tarmogoyf Aug 04 '22 at 18:08
  • 1
    Finally I catch '''Invalid assertion [id375715554935288561550335403] for SAML response [id37571555493389223699622518]: Condition '{urn:oasis:names:tc:SAML:2.0:assertion}AudienceRestriction' of type 'null' in assertion 'id375715554935288561550335403' was not valid.: None of the audiences within Assertion 'id375715554935288561550335403' matched the list of valid audiances''' - which seems a bit weird cause I provided default http://localhost:8080/saml2/service-provider-metadata/okta – tarmogoyf Aug 05 '22 at 06:57
  • 2
    I found this happened because of a request to favicon that happens first. It didn't happen for me with Firefox. See https://github.com/spring-projects/spring-security/issues/11657 for more information. I also published a blog post today that might help: https://developer.okta.com/blog/2022/08/05/spring-boot-saml – Matt Raible Aug 05 '22 at 17:30
  • 1
    Sorry for being annoying, but would it be bad practice to retrieve user from SamlResponse , create JWT to keep session stateless and pass with HttpResponse to the frontend? I watched lots of thymeleaf based manuals and information is messed hard in my head. – tarmogoyf Aug 08 '22 at 18:41
  • 2
    There's no reason to keep the session stateless. If you do all the authentication on the server-side in Spring Boot, you can use good ol' fashioned session cookies to keep the session with the client. That's what we do in JHipster (https://jhipster.tech) because it's the most secure. If you want to scale, use Redis. https://developer.okta.com/blog/2020/12/14/spring-session-redis – Matt Raible Aug 08 '22 at 23:06