On this page

Spring Security SAML

This guide describes how to use Spring Security SAML to add support for Okta to Java applications that use Spring Boot.


Learning outcomes

  • Install and configure an Okta SAML application

What you need


Create an Okta app integration for your SAML app

An Application Integration represents your app in your Okta org. To create an app integration for a SAML app:

  1. Open the Admin Console for your org.
  2. Choose Applications > Applications.
  3. Click Create App Integration.
  4. Select SAML 2.0 as the Sign-in method, and then click Next.
  5. Give your application name, for example "Spring Boot SAML", and then click Next.
  6. On the Configure SAML page
    • Set Single sign-on URL to a URL that is appropriate for your app. For example http://localhost:8080/login/saml2/sso/okta
    • Verify that Use this for Recipient URLs and Destination URLs is checked.
    • Set Audience URI to a URL that is appropriate for your app. For example http://localhost:8080/saml2/service-provider-metadata/okta
  7. Click Next.
  8. Set Are you a customer or a partner? to I'm an Okta customer adding an internal app.
  9. Set App type to This is an internal app that we have created.
  10. Click Finish.

Okta will create your app and redirect you to its Sign On tab. Continue the required setup:

  1. Locate the SAML Signing Certificates section.
  2. Locate the entry for SHA-2, and then select Actions > View IdP metadata.
  3. Copy the URL for the resulting link to your clipboard. It will look like https://${yourOktaDomain}/app/<random-characters>/sso/saml/metadata.
  4. Choose the Assignments tab, and then select Assign > Assign to Groups.
  5. Locate the entry for Everyone and click Assign.
  6. Click Done.

Create a Spring Boot app with SAML support

  1. Spring Boot 3 requires Java 17. Install it with SDKMAN:

    sdk install java 17-open
    
  2. Create a brand-new Spring Boot app using start.spring.io (opens new window). Select the following options:

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

    You can also use this URL (opens new window)

    Or use HTTPie (opens new window):

    https start.spring.io/starter.zip bootVersion==3.0.0-SNAPSHOT \
      dependencies==web,security,thymeleaf type==gradle-project \
      baseDir==spring-boot-saml | tar -xzvf -
    

Open the project in your favorite IDE and complete the following steps.

  1. 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";
        }
    
    }
    
  2. 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 signed 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="signout" type="submit">Sign out</button>
            </form>
        </body>
    </html>
    
  3. Create a src/main/resources/application.yml file to contain the metadata URI you copied to your clipboard earlier.

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

Run the app and authenticate

  1. Run your Spring Boot app. You can do this from your IDE, or as follows using the command line:

    ./gradlew bootRun
    
  2. Open http://localhost:8080 in your favorite browser and sign in with a user account set up in your org. You should see a successful result in your browser.

    Login success

If you try to sign out, it won't work. You'll fix that in the next section.

Add a sign-out feature

Spring Security's SAML support has a sign-out feature (opens new window) that requires a private key and certificate. To use it, you'll need to:

  1. Create a private key and certificate to sign the outgoing sign-out request using OpenSSL.

    openssl req -newkey rsa:2048 -nodes -keyout local.key -x509 -days 365 -out local.crt
    
  2. Copy the generated local.crt and local.key files to your app's src/main/resources directory.

  3. Update the signing and singlelogout fields in application.yml to refer to the new certificate files:

    spring:
      security:
        saml2:
          relyingparty:
            registration:
              okta:
                signing:
                  credentials:
                    - private-key-location: classpath:local.key
                      certificate-location: classpath:local.crt
                singlelogout:
                  binding: POST
                  response-url: "{baseUrl}/logout/saml2/slo"
                assertingparty:
                  metadata-uri: <your-metadata-uri>
    
  4. Add this certificate to your app integration.

    1. Open the Admin Console for your org.
    2. Choose Applications > Applications.
    3. Click on the name of your SAML app integration.
    4. Choose the General tab, locate the SAML Settings section, and click Edit.
    5. Click Next.
    6. In the SAML Settings section:
      • Click Show Advanced Settings.
      • Select Allow application to initiate Single Logout for Enable Single Logout.
      • Set Single Logout URL to to a URL that is appropriate for your app. For example http://localhost:8080/logout/saml2/slo
      • Set SP Issuer to a URL that is appropriate for your app. For example http://localhost:8080/saml2/service-provider-metadata/okta
      • Click the Browse button for Signature Certificate, locate the local.crt file you created in step 1, and click Upload Certificate.
      • Click Next.
    7. Click Finish.
  5. Restart your Spring Boot app, and the button should work.

    Sign out success

Customize authorities with Spring Security SAML

When you sign in, the resulting page shows that you have a ROLE_USER authority. However, when you assigned users to the app, you gave access to Everyone. You can configure your SAML app on Okta to send a user's groups as an attribute, and add other attributes like name and email.

  1. Edit your Okta app's SAML settings and fill in the Group Attribute Statements section.

    • Name: groups
    • Name format: Unspecified
    • Filter: Matches regex and use .* for the value

    Just above, you can add other attribute statements. For instance:

    Name Name format Value
    email Unspecified user.email
    firstName Unspecified user.firstName
    lastName Unspecified user.lastName
  2. Save these changes.

  3. If you cloned the repo earlier, restart your app and sign in to see your user's groups as authorities.

  4. If you created a Spring Boot app from scratch, create a SecurityConfiguration class that overrides the default configuration and uses a converter to translate the values in the groups attribute into Spring Security authorities.

    package com.example.demo;
    
    import java.util.HashSet;
    import java.util.List;
    import java.util.Set;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.core.convert.converter.Converter;
    import org.springframework.security.authentication.ProviderManager;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.authority.SimpleGrantedAuthority;
    import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider;
    import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider.ResponseToken;
    import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal;
    import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication;
    import org.springframework.security.web.SecurityFilterChain;
    
    import static org.springframework.security.config.Customizer.withDefaults;
    
    @Configuration
    public class SecurityConfiguration {
    
       @Bean
       SecurityFilterChain configure(HttpSecurity http) throws Exception {
    
          OpenSaml4AuthenticationProvider authenticationProvider = new OpenSaml4AuthenticationProvider();
          authenticationProvider.setResponseAuthenticationConverter(groupsConverter());
    
          // @formatter:off
          http
                .authorizeHttpRequests(authorize -> authorize
                   .mvcMatchers("/favicon.ico").permitAll()
                   .anyRequest().authenticated()
                )
                .saml2Login(saml2 -> saml2
                   .authenticationManager(new ProviderManager(authenticationProvider))
                )
                .saml2Logout(withDefaults());
          // @formatter:on
    
          return http.build();
       }
    
       private Converter<OpenSaml4AuthenticationProvider.ResponseToken, Saml2Authentication> groupsConverter() {
    
          Converter<ResponseToken, Saml2Authentication> delegate =
                OpenSaml4AuthenticationProvider.createDefaultResponseAuthenticationConverter();
    
          return (responseToken) -> {
                Saml2Authentication authentication = delegate.convert(responseToken);
                Saml2AuthenticatedPrincipal principal = (Saml2AuthenticatedPrincipal) authentication.getPrincipal();
                List<String> groups = principal.getAttribute("groups");
                Set<GrantedAuthority> authorities = new HashSet<>();
                if (groups != null) {
                   groups.stream().map(SimpleGrantedAuthority::new).forEach(authorities::add);
                } else {
                   authorities.addAll(authentication.getAuthorities());
                }
                return new Saml2Authentication(principal, authentication.getSaml2Response(), authorities);
          };
       }
    }
    
  5. Modify your build.gradle file to force the latest version of Open SAML that works with Spring Security 6.

    repositories {
        ...
        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"
        }
        ...
    }
    
  6. Restart your app and sign in. You should see your user's groups listed as authorities.

    Groups as authorities

Deploy with Heroku

After you have Okta working with the generated Spring Security SAML application, the next step is to take the example code and move it to your production enviroment. The specifics of how this works are different depending on how your application is set up.

One quick way to see this app working in a production environment is to deploy it to Heroku. Install the Heroku CLI (opens new window) and create an account to begin. Then, follow the steps below to prepare and deploy your app.

  1. Create a new app on Heroku using heroku create.

  2. Create a system.properties file in the root directory of your app to force Java 17:

    java.runtime.version=17
    
  3. Create a Procfile that specifies how to run your app:

    web: java -Xmx256m -jar build/libs/*.jar --server.port=$PORT
    
  4. Commit your changes:

    git add .
    git commit -m "Add Heroku configuration"
    
  5. Set the Gradle task to build your app:

    heroku config:set GRADLE_TASK="bootJar"
    
  6. Deploy to production using Git:

    git push heroku main
    

For authentication to work with SAML, you'll need to update your Okta app to use your Heroku app's URL in place of http://localhost:8080, wherever applicable.

Learn More

At this point, you should be familiar with setting up SAML-enabled applications to work with an Okta organization, how to configure Spring Security SAML to work with Okta, and how to deploy the sample app you built on Heroku.

If you want to learn more about configuring SAML and what to consider when writing a SAML application, see the in-depth Okta SAML guidance (opens new window) documentation, which is great place to learn more.