DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • Enterprise RIA With Spring 3, Flex 4 and GraniteDS
  • How To Build Web Service Using Spring Boot 2.x
  • Authentication With Remote LDAP Server in Spring Web MVC
  • Introduction to Spring Boot and JDBCTemplate: JDBC Template

Trending

  • Content Lakes: Harness Unstructured Data for Enterprise AI Readiness
  • Architecting Petabyte-Scale Hyperspectral Pipelines on AWS
  • Why Your QA Engineer Should Be the Most Stubborn Person on the Team
  • RAG Done Right: When to Use SQL, Search, and Vector Retrieval and How To Combine Them
  1. DZone
  2. Coding
  3. Frameworks
  4. Database Authentication + Spring Security SAML

Database Authentication + Spring Security SAML

In this article, take a look at database authentication plus Spring security SAML.

By 
Joe Cavazos user avatar
Joe Cavazos
·
Nov. 20, 20 · Tutorial
Likes (3)
Comment
Save
Tweet
Share
15.9K Views

Join the DZone community and get the full member experience.

Join For Free

If you are going to be developing web applications in Java, there is no doubt you are familiar with Spring Boot, a veritable toolbox for developing web applications. The most common method of authenticating users is database authentication, where credentials are stored in a database to identify authorized users that are accessible by the application. A common open standard is SAML, which can be used to handle authentication, specifically between service providers and identity providers. 

While configuring SAML auth in Spring Security is common and can be shown in many different examples, like this one from the Okta blog, it can add another layer of difficulty when continuing both database and SAML authentication methods in the same Spring Boot application. This will allow the user to be authenticated both ways, and this tutorial will show you how to find a solution! 

Prerequisites

  • Java 11

Acknowledgment: Much of the groundwork for the implementation of SAML 2.0 authentication used in this project was developed by Vincenzo De Notaris and can be found in this project on GitHub. For this project, some changes have been made to support dual DB + SAML authentication and use Okta as the SAML identity provider rather than SSOCircle.

SAML Authentication With Spring Security

There are several benefits to using SAML to handle authentication for your application:

  • Loose coupling between your application and your authentication mechanism increases independence between the two, allowing for more rapid development and evolution of application logic, with less risk of regression
  • Shifts the responsibility of authentication, which involves storing and retrieving sensitive user information, to the identity provider (e.g., Okta), which almost always offers less risk since identity management is their business model
  • Allows for an improved user experience via Single Sign-On while navigating between multiple apps

Okta is a very well established identity provider with robust features and a wealth of support. Managing users, accounts, and permissions with Okta is simple and straightforward. Simultaneously, it is still flexible and extensible enough to support your application no matter how much it grows (even as it grows into several applications). And the friendly, growing community is available to answer any questions you may have!

You’ll need to create a forever-free Okta developer account to complete this tutorial. If you already have a developer account, you should complete this tutorial by switching to the Classic UI in the top-left corner.

In case you need to support legacy systems or because you have strange security requirements, you may need to allow users to authenticate using either SAML or database credentials. The process to combine SAML 2.0 with DB auth in Spring Boot is what we’ll tackle here!

Set Up Your Okta Account With SAML and Run the Application

Please complete the following ten steps to see a working example.

Step 1: Clone the okta-spring-security-saml-db-example repository:

Java
 




xxxxxxxxxx
1


 
1
git clone https://github.com/oktadeveloper/okta-spring-security-saml-db-example.git


Step 2: Sign up for a free developer account at https://developer.okta.com/signup. This is required to create SAML 2.0 applications in Okta.

Step 3: Log in to your Okta account at https://your-okta-domain.okta.com. If you see a developer dashboard like the screenshot below, click on Developer Console in the top left, and select Classic UI.

Click Admin.

Step 4: Create a new application via Admin > Applications > Add Application > Create New App with the following settings:

  • Platform:Web
  • Sign On Method:SAML 2.0

Click Create.

Enter an App name like Spring Boot DB/SAML (or whatever you’d like). Click Next.

Enter the following SAML Settings:

  • Single Sign-On URL:http://localhost:8080/saml/SSO
  • Use this for Recipient URL and Destination URL:YES
  • Audience URI:http://localhost:8080/saml/metadata

Click Next.

Select the following two options:

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

Then, click Finish.

Step 5: Navigate to Assignments > Assign to People.

Step 6: Assign to your account with the custom username [email protected].

Step 7: Navigate to Sign On and copy the following values to your /src/main/resources/application.properties file:

  • saml.metadataUrl — Right-click and copy the URL from the Identity Provider metadata link below the View Setup Instructions button.

saml.idp — Click the View Setup Instructions button and copy the value in (2) Identity Provider Issuer.


For example, here are the values I used:

Java
 




xxxxxxxxxx
1


 
1
saml.metadataUrl=https://dev-763344.okta.com/app/exk74c26UmANQ0ema5d5/sso/saml/metadata
2
saml.idp=http://www.okta.com/exkrmibtn3VG9S2Pa4x6


Step 8: Run your Spring Boot application in your IDE or via Maven:

Java
 




xxxxxxxxxx
1


 
1
mvn spring-boot:run


Step 9: Navigate to your application’s home page at http://localhost:8080.

Step 10: For database authentication, log in using [email protected] / oktaiscool.

You should see a success message saying you’re logged in.


For SAML authentication, sign in using [email protected]. 

You should be prompted to select your identity provider.

Then, you should be redirected to the SAML Okta auth flow and returned to your application following successful authentication.

You’re done! You’ve successfully configured your project to support authentication via both the database and SAML 2.0! 

It’s nice to see everything working, but what about the code that makes it happen? Keep reading for a walkthrough of the code and how it works.

How to Combine Database and SAML Authentication in Spring Boot

To get a better understanding of how DB and SAML auth are combined in this example, clone the repository for this tutorial if you have not already:

Java
 




xxxxxxxxxx
1


 
1
git clone https://github.com/oktadeveloper/okta-spring-security-saml-db-example.git


Open the project up in your favorite IDE or editor and take a look at the Maven POM file located at /pom.xml.

This application inherits from the spring-boot-starter-parent parent project. This provides you with Spring Boot’s dependency and plugin management:

Java
 




x


 
1
<parent>
2
    <groupId>org.springframework.boot</groupId>
3
    <artifactId>spring-boot-starter-parent</artifactId>
4
    <version>2.3.4.RELEASE</version>
5
</parent>


This project uses the following Spring Boot Starter dependencies:

  • spring-boot-starter-web provides support for building web applications
  • spring-boot-starter-security provides support for securing the application (e.g., Basic Auth, Form Login)
  • spring-boot-starter-data-jpa provides support for the Java Persistence API, which is used to communicate with the database for DB authentication
  • spring-boot-starter-thymeleaf provides support for the Thymeleaf templating engine, a simple and powerful way to create web pages for Spring Boot applications

The spring-security-saml2-core extension for Spring Boot provides the necessary SAML-related libraries. This extension depends on the opensaml library, which is contained in the Shibboleth repository and is added to the <repositories> block:

Java
 




xxxxxxxxxx
1
17


 
1
<repositories>
2
    <repository>
3
        <id>Shibboleth</id>
4
        <name>Shibboleth</name>
5
        <url>https://build.shibboleth.net/nexus/content/repositories/releases/</url>
6
    </repository>
7
</repositories>
8

          
9
<dependencies>
10
    ...
11
    <dependency>
12
        <groupId>org.springframework.security.extensions</groupId>
13
        <artifactId>spring-security-saml2-core</artifactId>
14
        <version>1.0.10.RELEASE</version>
15
    </dependency>
16
    ...
17
</dependencies>


The following dependencies also make life easier:

  • com.h2database:h2 to provide a simple in-memory database
  • org.projectlombok:lombok to reduce boilerplate code (e.g. getters, setters, toString())
  • nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect, a useful add-on for formatting Thymeleaf templates

NOTE: Some IDEs have trouble digesting Lombok-ified code due to version and plugin incompatibilities. If you have difficulty compiling this project, consider removing this dependency and adding the missing boilerplate code, or just use Maven to build and run.

The SAML and Database Auth “Pre-Login” Page

You want to have an initial page in which a user enters their username for login. Depending on the username pattern, you either direct the user to a standard username-and-password page for authenticating against the database, or direct them to the SAML auth flow.

/src/main/resources/templates/index.html

Java
 




xxxxxxxxxx
1
21


 
1
<!doctype html>
2
<html
3
        lang="en"
4
        xmlns:th="http://www.thymeleaf.org"
5
        xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
6
        layout:decorate="~{layout}"
7
>
8
<body>
9
<section layout:fragment="content">
10
    <h6 class="border-bottom border-gray pb-2 mb-0">Please Log In:</h6>
11
    <div class="media text-muted pt-3">
12
        <form action="#" th:action="@{/pre-auth}" th:object="${username}" method="post">
13
            <p>Username: <input type="text" th:field="*{username}" /></p>
14
            <p><input type="submit" value="Submit" /></p>
15
        </form>
16
        <br/>
17
        <p th:text="${error}" style="color: red"></p>
18
    </div>
19
</section>
20
<body>
21
</html>


IndexController is the backend @Controller defined to serve this page and handle requests:

/src/main/java/com/okta/developer/controller/IndexController.java

Java
 




xxxxxxxxxx
1
27


 
1
package com.okta.developer.controller;
2

          
3
@Controller
4
public class IndexController {
5

          
6
    @GetMapping
7
    public String index(Model model) {
8
        model.addAttribute("username", new PreAuthUsername());
9
        return "index";
10
    }
11

          
12
    @PostMapping("/pre-auth")
13
    public String preAuth(@ModelAttribute PreAuthUsername username,
14
                          Model model,
15
                          RedirectAttributes redirectAttributes) {
16
        if (StringUtils.endsWithIgnoreCase(username.getUsername(), Constants.OKTA_USERNAME_SUFFIX)) {
17
            // redirect to SAML
18
            return "redirect:/doSaml";
19
        } else if (StringUtils.endsWithIgnoreCase(username.getUsername(), Constants.DB_USERNAME_SUFFIX)) {
20
            // redirect to DB/form login
21
            return "redirect:/form-login?username="+username.getUsername();
22
        } else {
23
            redirectAttributes.addFlashAttribute("error", "Invalid Username");
24
            return "redirect:/";
25
        }
26
    }
27
}


Within IndexController, you are checking whether the username matches a particular pattern and redirecting accordingly.

Authenticate With SAML and Spring Security

The WebSecurityConfig class, which extends the WebSecurityConfigurerAdapter parent, defines much of the security settings, including:

  • The filter chains to handle SAML requests and responses
  • How and when to authenticate a user with either the database or SAML and Okta
  • Required permissions for URLs within the application
  • Logging out

When redirected to the /doSaml endpoint, the SAML flow is initiated by a custom authentication entry point defined in WebSecurityConfig.configure(HttpSecurity):

/src/main/java/com/okta/developer/config/WebSecurityConfig.java

Java
 




xxxxxxxxxx
1
28


 
1
package com.okta.developer.config;
2

          
3
@Configuration
4
@EnableWebSecurity
5
@EnableGlobalMethodSecurity(securedEnabled = true)
6
public class WebSecurityConfig extends WebSecurityConfigurerAdapter implements DisposableBean {
7
    ...
8
    
9
    @Autowired
10
    private SAMLEntryPoint samlEntryPoint;
11
    
12
    ...
13
    
14
    @Override
15
    protected void configure(HttpSecurity http) throws Exception {
16
        ...
17
        http
18
            .httpBasic()
19
            .authenticationEntryPoint((request, response, authException) -> {
20
                if (request.getRequestURI().endsWith("doSaml")) {
21
                    samlEntryPoint.commence(request, response, authException);
22
                } else {
23
                    response.sendRedirect("/");
24
                }
25
            });
26
        ...
27
    }
28
}


Here you can see if the requested URL ends with doSaml, the request is handled by the SamlEntryPoint defined in your configuration. This redirects the user to authenticate via Okta, and returns the user to /doSaml upon completion. To handle this redirect, a Controller is defined to redirect the user following a successful SAML auth:

/src/main/java/com/okta/developer/controller/SamlResponseController.java

Java
 




xxxxxxxxxx
1
15


 
1
package com.okta.developer.controller;
2

          
3
@Controller
4
public class SamlResponseController {
5
    @GetMapping(value = "/doSaml")
6
    public String handleSamlAuth() {
7
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
8
        LOGGER.info("doSaml auth result: {}", auth);
9
        if (auth != null) {
10
            return "redirect:/landing";
11
        } else {
12
            return "/";
13
        }
14
    }
15
}


At this point, the user should be successfully authenticated with the app!

Authenticate With a Database and Spring Security

If the username matches another pattern, the user is redirected to a standard-looking form login page:

/src/main/resources/templates/form-login.html

Java
 




xxxxxxxxxx
1
22


 
1
<!doctype html>
2
<html
3
        lang="en"
4
        xmlns:th="http://www.thymeleaf.org"
5
        xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
6
        layout:decorate="~{layout}"
7
>
8
<body>
9
<section layout:fragment="content">
10
    <h6 class="border-bottom border-gray pb-2 mb-0">Database Login:</h6>
11
    <div class="media text-muted pt-3">
12
        <form action="#" th:action="@{/form-login}" th:object="${credentials}" method="post">
13
            <p>Username: <input type="text" th:field="*{username}" /></p>
14
            <p>Password: <input type="password" th:field="*{password}" /></p>
15
            <p><input type="submit" value="Submit" /></p>
16
        </form>
17
        <br/>
18
        <p th:text="${error}" style="color: red"></p>
19
    </div>
20
</section>
21
<body>
22
</html>


The login submission is handled by a @Controller which calls on the AuthenticationManager built in WebSecurityConfig:

/src/main/java/com/okta/developer/config/WebSecurityConfig.java

Java
 




xxxxxxxxxx
1
14


 
1
package com.okta.developer.config;
2

          
3
@Configuration
4
@EnableWebSecurity
5
@EnableGlobalMethodSecurity(securedEnabled = true)
6
public class WebSecurityConfig extends WebSecurityConfigurerAdapter implements DisposableBean {
7
    ...
8
    
9
    @Override
10
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
11
        auth.authenticationProvider(dbAuthProvider);
12
        auth.authenticationProvider(samlAuthenticationProvider);
13
    }
14
}


DbAuthProvider is a custom component which performs standard DB authentication by checking the supplied password versus a hashed copy in the database:

/src/main/java/com/okta/developer/auth/DbAuthProvider.java

Java
 




xxxxxxxxxx
1
36


 
1
package com.okta.developer.auth;
2

          
3
@Component
4
public class DbAuthProvider implements AuthenticationProvider {
5
    private final CombinedUserDetailsService combinedUserDetailsService;
6
    private final PasswordEncoder passwordEncoder;
7

          
8
    ...
9

          
10
    @Override
11
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
12
        if (!StringUtils.endsWithIgnoreCase(authentication.getPrincipal().toString(), Constants.DB_USERNAME_SUFFIX)) {
13
            // this user is not supported by DB authentication
14
            return null;
15
        }
16

          
17
        UserDetails user = combinedUserDetailsService.loadUserByUsername(authentication.getPrincipal().toString());
18
        String rawPw = authentication.getCredentials() == null ? null : authentication.getCredentials().toString();
19

          
20
        if (passwordEncoder.matches(rawPw, user.getPassword())) {
21
            LOGGER.warn("User successfully logged in: {}", user.getUsername());
22
            return new UsernamePasswordAuthenticationToken(
23
                    user.getUsername(),
24
                    rawPw,
25
                    Collections.emptyList());
26
        } else {
27
            LOGGER.error("User failed to log in: {}", user.getUsername());
28
            throw new BadCredentialsException("Bad password");
29
        }
30
    }
31

          
32
    @Override
33
    public boolean supports(Class<?> aClass) {
34
        return aClass.isAssignableFrom(UsernamePasswordAuthenticationToken.class);
35
    }
36
}


The above class calls on CombinedUserDetailsService which is another custom component providing an appropriate UserDetails object depending on whether the user is authenticated using the database or SAML, by implementing UserDetailsService and SAMLUserDetailsService respectively:

/src/main/java/com/okta/developer/auth/CombinedUserDetailsService.java

Java
 




xxxxxxxxxx
1
42


 
1
package com.okta.developer.auth;
2

          
3
@Service
4
public class CombinedUserDetailsService implements UserDetailsService, SAMLUserDetailsService {
5
    private final UserRepository userRepository;
6

          
7
    ...
8

          
9
    @Override
10
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
11
        StoredUser storedUser = lookupUser(s);
12
        return new CustomUserDetails(
13
                AuthMethod.DATABASE,
14
                storedUser.getUsername(),
15
                storedUser.getPasswordHash(),
16
                new LinkedList<>());
17
    }
18

          
19
    @Override
20
    public Object loadUserBySAML(SAMLCredential credential) throws UsernameNotFoundException {
21
        LOGGER.info("Loading UserDetails by SAMLCredentials: {}", credential.getNameID());
22
        StoredUser storedUser = lookupUser(credential.getNameID().getValue());
23
        return new CustomUserDetails(
24
                AuthMethod.SAML,
25
                storedUser.getUsername(),
26
                storedUser.getPasswordHash(),
27
                new LinkedList<>());
28
    }
29

          
30
    private StoredUser lookupUser(String username) {
31
        LOGGER.info("Loading UserDetails by username: {}", username);
32

          
33
        Optional<StoredUser> user = userRepository.findByUsernameIgnoreCase(username);
34

          
35
        if (!user.isPresent()) {
36
            LOGGER.error("User not found in database: {}", user);
37
            throw new UsernameNotFoundException(username);
38
        }
39

          
40
        return user.get();
41
    }
42
}


The resulting @Controller to handle DB authentication looks like this:

/src/main/java/com/okta/developer/controller/DbLoginController.java

Java
 




xxxxxxxxxx
1
34


 
1
package com.okta.developer.controller;
2

          
3
@Controller
4
public class DbLoginController {
5

          
6
    private final AuthenticationManager authenticationManager;
7

          
8
    ...
9

          
10
    @GetMapping("/form-login")
11
    public String formLogin(@RequestParam(required = false) String username, Model model) {
12
        ...
13
    }
14

          
15
    @PostMapping("/form-login")
16
    public String doLogin(@ModelAttribute DbAuthCredentials credentials,
17
                          RedirectAttributes redirectAttributes) {
18
        try {
19
            Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(
20
                    credentials.getUsername(), credentials.getPassword()));
21

          
22
            if (authentication.isAuthenticated()) {
23
                SecurityContextHolder.getContext().setAuthentication(authentication);
24
            } else {
25
                throw new Exception("Unauthenticated");
26
            }
27

          
28
            return "redirect:/landing";
29
        } catch (Exception e) {
30
            redirectAttributes.addFlashAttribute("error", "Login Failed");
31
            return "redirect:/form-login?username="+credentials.getUsername();
32
        }
33
    }
34
}


When doLogin() is called via POST, the AuthenticationManager handles the username and password authentication and redirects the user if successful.

For ease of use, two users are defined in the database: one for DB auth and one for SAML. Both users are defined in our database, but only one of them is authenticated against the database:

/src/main/resources/data.sql

Java
 




xxxxxxxxxx
1


 
1
INSERT INTO user (ID, USERNAME, PASSWORD_HASH) VALUES
2
('17e3d83c-6e09-41b8-b4ee-b4b14cb8a797', '[email protected]', '(bcrypted password)'),   /*DB AUTH*/
3
('17e3d83c-6e09-41b8-b4ee-b4b14cb8a798', '[email protected]', 'bcrypted password'); /*SAML AUTH*/


Learn More About SAML and Okta

Much of the complexity of this project comes from the need to combine both database and SAML authentication in one app. Normally you would choose one or the other. If you want to use only SAML for authentication (which is a fine idea, especially using Okta), visit this blog post using the standard Spring SAML DSL extension to integrate with Okta and SAML to secure your application.

The source code used in this example is on GitHub.

See a good primer on how SAML works here: What is SAML and How Does it Work?

Please provide comments, questions, and any feedback in the comments section below.

Follow us on social media (Twitter, Facebook, LinkedIn) to know when we’ve posted more articles like this, and please subscribe to our YouTube channel for tutorials and screencasts!

We’re also streaming on Twitch, follow us to be notified when we’re live.

authentication Spring Security Spring Framework Database application Spring Boot Java (programming language)

Published at DZone with permission of Joe Cavazos. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Enterprise RIA With Spring 3, Flex 4 and GraniteDS
  • How To Build Web Service Using Spring Boot 2.x
  • Authentication With Remote LDAP Server in Spring Web MVC
  • Introduction to Spring Boot and JDBCTemplate: JDBC Template

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook