Secure your Spring Boot Application with WSO2 Identity Server
Spring Security is a powerful and highly customizable authentication and access-control framework. It is the de-facto standard for securing Spring-based applications. [1].
“Spring Boot is basically an extension of the Spring framework which eliminated the boilerplate configurations required for setting up a Spring application.” [2]
It will take only 5 minutes to create a simple spring-boot web application using Intellij Idea and secure it using the OIDC protocol for authenticating the users. WSO2 Identity Server provides several authentication and authorization mechanisms with different Standards.
This blog will guild you on how to create a simple spring-boot web application and make a secure authentication using WSO2 Identity Server.
To integrate your spring-boot app, you need to follow the below steps
- Create a Spring Boot Project
- Create an OIDC Application in Identity Server
- Secure your app with IS (Add login, Get user information, Add logout)
Now let’s get started!
1. Create a Spring Boot Project
In this blog, I have used Intellij Idea to create the spring-boot project. Instead of Intellij Idea, you can directly go to https://start.spring.io/ and add the relevant dependencies and create the project. You can check the quickstart of spring.io https://spring.io/quickstart
- Create a new Project with Intellij Idea
1.1. Create a New Project with Spring Initializer. The Initializr offers a fast way to pull in all the dependencies you need for an application and does a lot of the setup for you.
1.2. Give the GroupdID and Artifact ID of the project
1.3. Select the relevant dependencies. Select the following dependencies:
Developer Tools: Spring-boot-dev
Web: spring-boot-web
Template Engines: Thymeleaf
Security: spring-security, oauth2-client
Now you have created the skeleton for a spring-boot-web application. The project skeleton will look like as below:
2. Create A OIDC Application in Identity Server
2 .1. Start the WSO2 Identity Server and Go to the management console of Identity Server (https://localhost:9443/carbon)
2.2 Create An OAuth Application
2.3 Configure the OAuth Inbound properties.
Call back url: regexp=(https://localhost:8080/login/outh2/code/wso2|https://localhost:8080)
The spring-boot supports default call-back url to be as {base-url}/login/oauth2/code/{registration-id}
2.4. Get the client-id and client-secret of the OAuth Application.
3. Add Secure Authentication to your Spring Boot Application with WSO2 IS
3.1. Add the following configs in the <PROJECT_HOME>/src/resources/application.properties file.
Optional: If you want to simplify the application.properties file further
Instead of having all endpoints of WSO2 Identity server, you can simplify further by configuring oidc-discovery endpoint of IS.
You can remove all these and configure only the oidc-discovery endpoint as follows.
spring.security.oauth2.client.provider.wso2.issuer-uri=https://localhost:9443/oauth2/token
Please find the whole source code in the following GitHub location:
3.2. Import the public certificate of WSO2 IS into cacerts file.
During the access token flow, there will be an SSL handshake between the web-app and the IS server. In that flow, the web-app should trust the certificate of IS. In order to build the trust, a public certificate of WSO2 Identity server into Java cacerts file. Else you may end up in following error during the token exchange flow.
[invalid_token_response] An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response: I/O error on POST request for “https://localhost:9443/oauth2/token": sun.security.validator.ValidatorException: PKIX path validation failed: java.security.cert.CertPathValidatorException: signature check failed; nested exception is javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path validation failed: java.security.cert.CertPathValidatorException: signature check failed
Try adding the certificate of the server to java ca. Basically you need to add the certificate of the WSO2 server to the java cacerts. Bellow post[3] explains how to add the certificate of the super tenant to java cacerts.
01. You can find the default wso2carbon.pem in following location.
<CARBON_SERVER>/repository/resources/security/
If not please use this command to create it
keytool -exportcert -alias wso2carbon -keystore wso2carbon.jks -rfc -file wso2carbon.pem02. Go to the CA Certificate sore location, there you can find the cacerts keystore in following location.
JAVA_HOME/jre/lib/security/03. List the all installed certificated
keytool -list -keystore cacerts
Then you will have to enter the password, ‘changeit’ is the default password for cacerts
Enter keystore password: changeit04. Import new certificate
sudo keytool -import -alias wso2Carbon -file wso2carbon.pem -keystore cacerts
Secure your spring boot application
Creating a Spring boot application and securing it is very simple. Now let’s see the important default files.
1. Java class files in src/main/java: Contains DemoApplication.java- The entry point for the entire app and Loads the Spring Boot framework
2. Thymeleaf template files in src/main/resources/templates
3. src/main/resources/application.properties
1. Add Login
Step1: Now let’s create AppController.java in the src/main/java directory. It is our basic controller class and it maps requests to Thymeleaf template files.
- @Controller to enable this class as a REST controller and allow it to intercept requests to the server
- @GetMapping to enable this method to intercept a call to the server, in this case to /
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;@Controller
public class AppController {
@GetMapping("/")
public String getProfile(Model model, Authentication authentication) {
return "home";
}
}
Step2: Now create a home.html file inside src/main/resources/templates folder to get the responses and map them in the UI.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Spring-boot Demo App</title>
</head>
<body>
<div th:unless="${#authorization.expression('isAuthenticated()')}">
<p>Hello!</p>
<p>You will need to log in before viewing Profile page.</p>
<div style="float:left">
<form method="post" th:action="@{/login}" >
<button id="login-button" type="submit" >Login</button>
</form>
</div>
</div>
</body>
</html>
To run your webapp:
You can run the spring boot web application using the embedded tomcat. You can click on Run Application Button in intellij and run the webapp.
You can run by the follwing command as well. Navigate to http://localhost:8080 address
mvn clean spring-boot:run
To customize callback URL in spring boot app instead of using the default {base-url}/login/oauth2/code/{registration-id}, refer to the document https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.security.oauth2.client
To customize the login page without using the default /login path, refer to https://is.docs.wso2.com/en/latest/develop/spring-boot/
2. View user information
Step1: Get subject and user information from the Authentication object in the AppController.
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class AppController {
@GetMapping("/")
public String getProfile(Model model, Authentication authentication) {
if (authentication != null && authentication.isAuthenticated()) {
DefaultOidcUser userDetails = (DefaultOidcUser) authentication.getPrincipal();
model.addAttribute("isAuthenticated", true);
model.addAttribute("userName", userDetails.getName());
model.addAttribute("IDTokenClaims", userDetails);
}
return "home";
}
}
Step2: Add the below html code block in the home.html file to view the user information.
<div th:if="${#authorization.expression('isAuthenticated()')}" class="text container">
<div>
You are successfully logged in as
<span style="font-weight:bold" th:text="${userName}"></span>
</div>
<div>
<p>Claims found in the ID token<p>
<table>
<tr th:each="instance : ${IDTokenClaims.getClaims()}">
<td style="font-weight:bold" th:text="${instance.key}">key</td>
<td th:text="${instance.value}">value</td>
</tr>
</table>
</div>
</div>
3. Add Logout
By default, spring-boot app can be logged out when by redirecting to /logout. Once the app is logged out, it will be redirected to /login?logout.
In Spring Boot , if we want our own security configuration, we can simply add a custom WebSecurityConfigurerAdapter. This will disable the default auto-configuration and enable our custom security configuration.
Step1: Create a ConfigSecurity.java class and Add the below code segment
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.oauth2.client.oidc.web.logout.OidcClientInitiatedLogoutSuccessHandler;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
@EnableWebSecurity
public class ConfigSecurity extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
// allow anonymous access to the root page
.antMatchers("/").permitAll()
// all other requests
.anyRequest().authenticated()
// Replace with logoutSuccessHandler(oidcLogoutSuccessHandler()) to support OIDC RP-initiated logout
.and().logout().logoutSuccessHandler(oidcLogoutSuccessHandler())
// enable OAuth2/OIDC
.and().oauth2Login();
}
//Inject the ClientRegistrationRepository which stores client registration information
@Autowired
private ClientRegistrationRepository clientRegistrationRepository;
/**
* Create a OidcClientInitiatedLogoutSuccessHandler
*
* @return OidcClientInitiatedLogoutSuccessHandler
*/
private LogoutSuccessHandler oidcLogoutSuccessHandler() {
OidcClientInitiatedLogoutSuccessHandler oidcLogoutSuccessHandler =
new OidcClientInitiatedLogoutSuccessHandler(
this.clientRegistrationRepository);
oidcLogoutSuccessHandler.setPostLogoutRedirectUri(
"http://localhost:8080"); //Need to give the post-rediret-uri here
return oidcLogoutSuccessHandler;
}
}
Step2: Add a logout button in the home.html page
<div style="float:left">
<form method="post" th:action="@{/logout}" class="left">
<button id="logout-button" type="submit">Logout</button>
</form>
</div>
You can find the sample app here:
https://github.com/piraveena/spring-boot-with-is
Now you can run the application using
mvn clean spring-boot:run
Else you can deploy the web app into an existing tomcat server.
Here I will guide on how to deploy the application into a tomcat server.
4. Deploy your spring boot application in tomcat
4.1. Create a WAR file
Now create a WAR file from your Spring Boot application. Add the following just after the <description>
node in your pom.xml
.
<packaging>war</packaging>
4.2. Remove the embedded Tomcat server by adding the following to your dependencies list:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
4.3. Finally, enable your application as a servlet by extending your main class with SpringBootServletInitializer
:
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;@SpringBootApplication
public class DemoApplication extends SpringBootServletInitializer {
...
}
4.4. Now clean and package your application with the following command:
./mvnw clean package
5. Try out the sample
5.1. Deploy the web-app into the tomcat.
5.2. Rename the web-app as demo-app.
5.3. Start the tomcat server.
5.4. Go to http://localhost:8080/demo-app/login (You need to change the callback URL accordingly)
5.5. Click on WSO2 Identity Server. You will be redirected to the login page of the WSO2 Identity Server. Login as admin:admin
5.6. You will be redirected back to the application. I have requested Country and email-address claims
Now you did secure authentication with WSO2 Identity Server!