Wednesday, December 28, 2022

Spring Boot Security: Using SecurityFilterChain

spring framework,spring boot,java,spring security,programming,software development,technology
The goal of this tutorial is to show you how to use SecurityFilterChain to implement Spring Security in a Spring Boot web application.

To secure data and business logic, security is essential for any type of web application. We can secure our web pages and REST APIs with the Spring Security Framework and apply for roles with minimal configuration. We can also employ the Spring Security Framework to protect our Spring Boot applications from security flaws like CSRF and CORS.


So, first, we'll build a simple Spring Boot MVC Web application, and then we'll add Spring Security to it.

POM.XML

The Spring Boot project's pom.xml is shown below:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.7.6</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.raven</groupId>
	<artifactId>spring-boot-security-basic</artifactId>
	<version>1.0.0-SNAPSHOT</version>
	<name>spring-boot-security-basic</name>
	<description>Spring Boot  project to implement Security</description>
	<properties>
		<java.version>11</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-security</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<scope>runtime</scope>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<finalName>spring-boot-security-basic</finalName>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>
Spring Boot version 2.7.6 is what we use. This version of Spring Boot, Spring Framework, and Spring Security is 5.3.24 and 5.7.5, respectively. We have added the spring-boot-starter-security dependency to implement Spring Security in this application.

Controller

Create a package of name controller within the root package. Create two controller classes within this package: WelcomeController and UserController.

The following is a code snippet from the WelcomeController class:
package com.raven.springbootsecuritybasic.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class WelcomeController {
	@GetMapping("/welcome")
	public String welcome() {
		return "Available courses:" +
				"<ul>" +
				"<li>Learn JAVA : Beginner to Master</li>" +
				"<li>Full Stack JAVA Developer</li>" +
				"<li>Microservices with Spring Boot</li>" +
				"<li>Complete Web Development</li>" +
				"<li>Wordpress for Beginner</li>" +
				"<li>Complete Python Development</li>" +
				"<li>Docker guide : Beginner to Master</li>" +
				"<li>Node.js : Ultimate guide</li>" +
				"</ul>";
	}
}
This class is annotated with @RestController, which tells the Spring Container that it will be used for a REST-based service. The Spring Container is informed by the @GetMapping annotation that the HTTP endpoint /welcome is exposed as a REST service. As a result, when we call this HTTP endpoint from another application or a browser, the welcome() method is invoked.

Here is a code snippet for the UserController class:
package com.raven.springbootsecuritybasic.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {

	@GetMapping("/myCourses")
	public String myCourses() {
		return "Enrolled courses:" +
				"<ul>" +
				"<li>Full Stack JAVA Developer (85% done)</li>" +
				"<li>Microservices with Spring Boot (55 % done)</li>" +
				"<li>Docker guide : Beginner to Master (65% done)</li>" +
				"</ul>";
	}
}
This class is also annotated with @RestController and has exposed another HTTP endpoint - /myCourses.

The Magic

Run this application now. We'll call /welcome from the browser, so type http://localhost:8080/welcome into your browser. We are automatically redirected to a login page, as you can see. However, we have not created any login pages or written any Java code related to login.

This is one of the Spring Security Framework's out-of-the-box capabilities. Because we added spring-boot-starter-security as a dependency within our application, Spring Security Framework will secure all of our web applications by default. Anyone attempting to access this application's REST services will be prompted for credentials.

The question now is what my username and password are. Don't be concerned. The solution has been provided by Spring Security. The default username is user, and the password can be found in the console of your IDE (Eclipse/STS/IntelliJ/Visual Studio Code). After entering your username and password, simply click the "Sign in" button to be redirected to /welcome. When we run our other endpoint /myCourses, Spring Security will not prompt us to log in. Because Spring Security validates us based on a session id.

👉 But there is one problem with this approach: every time we restart our application, Spring Security generates a new password. To overcome this, we can configure a static username and password within our application.

Static Credentials

In the application.properties file, we can define our own username and password. Make the following changes to the application.properties file:
spring.security.user.name = admin
spring.security.user.password = admin*%#4321

Restart the application, and we can now log in with the aforementioned username and password. If you look at the IDE console, you'll notice that Spring Security doesn't generate a password for the username because we changed the default username and password in the application.properties file.

👉 By default, the Spring Security Framework will secure all of the endpoints defined in our application. If we change the requirement, we want to secure only the /myCourses endpoint, while the /welcome endpoint is open to all without authentication. To meet this requirement, we must customize our application's security configurations.

Custom Security Configuration

The Spring Security version for this application is 5.7.5. Before the 5.7 version, we used the WebSecurityConfigurerAdapter class to implement custom security configuration by overriding its configure method in our application. Spring deprecates the WebSecurityConfigureAdapter class beginning with version 5.7.

👉 To define custom security requirements for Spring Boot applications, we must use the component or bean style as of Spring Security 5.7. A class called SpringBootWebSecurityConfiguration exists within the Spring Security Framework. This class is primarily accountable for the Spring Security Framework's default security configuration. This class includes a SecurityFilterChain method called defaultSecurityFilterChain(HttpSecurity http).

To create our own SecurityFilterChain, we must override defaultSecurityFilterChain(HttpSecurity http) in our application.

Create another package of name configuration package under the root package. Create a class called SecurityConfiguration inside the configuration package and update it with the following code:
package com.raven.springbootsecuritybasic.configuration;
// imports are omitted

@Configuration
public class SecurityConfiguration {

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
        httpSecurity.authorizeHttpRequests().anyRequest().authenticated();
        httpSecurity.formLogin();
        httpSecurity.httpBasic();

        return httpSecurity.build();
    }
}
We must annotate this class with @Configuration so that Spring Framework recognizes it as a configuration class, and the IoC container will automatically create all of the beans that we have defined within it when we run this application.

You can rename the method defaultSecurityFilterChain to whatever you want. Spring Security will authenticate any request that comes to this application, according to the first line of this method. As a result, when we attempted to access the application's endpoints, Spring Security redirected us to the login page. Then it returns a SecurityFilterChain bean.


When we restart the application and try to access both endpoints: /welcome and /myCourses, we see that you must enter your credentials in both cases. So we've secured every endpoint. But we want some endpoints to be secure while others can be accessed without a username and password. Update the defaultSecurityFilterChain method with the following code snippet to meet the requirement:
package com.raven.springbootsecuritybasic.configuration;
// imports are omitted

@Configuration
public class SecurityConfiguration {
    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
        httpSecurity.authorizeHttpRequests()
                .antMatchers("/myCourses").authenticated()
                .antMatchers("/welcome").permitAll()
                .and().formLogin()
                .and().httpBasic();
        return httpSecurity.build();
    }
}
We mentioned our endpoints in the antMatchers() method. What exactly are antMatchers? It is an Ant-style path pattern implementation. A portion of this mapping code was graciously borrowed from Apache Ant. So we can pass multiple paths, which means multiple endpoints, inside the antMatchers() method. It tells Spring Security to configure httpSecurity based on the endpoints.    

So, in the first antMatchers, we mentioned the endpoint /myCourses and called the authenticated() method, indicating that the /myCourses endpoint will be secured by Spring Security. We specified the endpoint /welcome and invoked a method called permitAll() in the following antMatchers, so Spring Security will no longer authenticate this REST service, and anyone can access it.

Restart the application and try to connect to the endpoints again. You can see that the /welcome endpoint does not require authentication, but you must enter the credentials to access the /myCourses endpoint. This also meets our criteria.


👉 We now want to be able to access all of our application's endpoints without the need for authentication. To accomplish this, add the following code snippet to the defaultSecurityFilterChain() method:
package com.raven.springbootsecuritybasic.configuration;
// imports are omitted

@Configuration
public class SecurityConfiguration {

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
        httpSecurity.authorizeHttpRequests()
                .anyRequest().permitAll()
                .and().formLogin()
                .and().httpBasic();
        return httpSecurity.build();
    }
}
We've used the permitAll() method on anyRequest(), as you can see. As a result, Spring Security will no longer authenticate requests to the application.


👉 Similarly, if we use anyRequest() to invoke the denyAll() method, all of our application's endpoints will be inaccessible:
package com.raven.springbootsecuritybasic.configuration;
// imports are omitted

@Configuration
public class SecurityConfiguration {

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
          .authorizeHttpRequests()
                .anyRequest().denyAll();
                
        return httpSecurity.build();
    }
}
If you try to access /welcome or /myCourses now, you will receive a 403 error, indicating that you do not have the authorization to view these pages.

The source code is available for download here.
Happy coding!!! 😊
in


No comments:

Post a Comment

Popular posts