2021
Spring Security

Spring Security

Translated from German using DeepL.

Date: Mai 2021
Reading time: 11 minutes


I've been working on my Baseball Spring Boot project for a few weeks now. The web application should later display data about different teams.

It must also be possible to add, delete and change new data. However, it must of course be ensured that not all visitors can change this data. That's why my task was to secure the application with Spring Security.

This blog post is intended to show the essential steps in securing a website.

Introduction

Spring Security is a Java framework that can be used to authenticate and authorize an application. This means that you can use to check the identity of a user as well as grant or deny access to certain pages. The framework also makes the entire application more secure against attacks.

Instructions

In order to follow these instructions easily, it is an advantage if you already have previous knowledge of Spring Boot. For this reason, I recommend reading this short article (opens in a new tab) first.

Spring Initializr

First you need a Spring Boot project. I work with Visual Studio Code and can therefore initialize a project using the p shortcut.

initializr

However, you can also set up a Maven project via the Spring website (opens in a new tab).

Login form

With just a few lines of code you already have a login mask. This is the outstanding feature of Spring Security.
And this is how you create one:

In the root directory of the project you will find a file containing all dependencies. You now need to add the Spring Security Framework to it.

pom.xml
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-security</artifactId>
</dependency>

You can create an index.html file under resources/templates.

index.html
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
    </head>
    <body>
        <h1>Hier stehen schützenswerte Daten</h1>
    </body>
</html>

Under src/main/java/com/example/demo you now only need to activate Spring Security.

WebSecurityConfig.java
package com.example.demo;
 
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
 
@EnableWebSecurity
	public class WebSecurityConfiguration {
}

The web application is now much more secure than before.
To start it, you must execute the application file. This should already be present after correct installation.

Application.java
package com.example.demo;
 
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
 
@SpringBootApplication
public class DemoApplication {
	public static void main(String[] args) {
		SpringApplication.run(DemoApplication.class, args);
	}
}

After starting, a user name and password are requested when accessing the start page.

login

Spring Security has already created a user. You can now log in with the user name "user" and the password, which can be found in the debug console.

GetMapping

It makes sense to create a controller file in the controller folder under src/main/java/com/example/demo. You can define redirects in it.

For example, you can return the index.html page when the user calls localhost:8080/home. The annotation GetMapping is used for this.

Controller.java
package com.example.demo.controller;
 
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
 
@Controller
public class Controller {
	@GetMapping(value="/home")
	public String index() {
		return "/index";
	}
}

User

I used the h2 database to create and manage users. To ensure that this database works smoothly later on, I configured the properties as follows.

application.properties
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect

For a web application like the one I was planning, I needed users and roles. After all, several people would later be allowed to edit the page.
That's why I created a user entity.
Please note: In these instructions, I save the password as plain text. In a real project, however, it must always be stored as a hash in encrypted form This is even required by law.

User.java
package com.example.demo.model;
 
import javax.persistence.*;
 
@Entity
@Table(name = "User")
public class User {
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private int id;
 
	@Column(name = "name")
	private String userName;
 
	@Column(name = "password")
	private String password;
 
	@Column(name = "active")
	private boolean active;
 
	@Column(name = "roles")
	private String roles;
 
	public int getId() {
		return id;
	}
 
	public void setId(int id) {
		this.id = id;
	}
 
	public String getUserName() {
		return userName;
	}
 
	public void setUserName(String userName) {
		this.userName = userName;
	}
 
	public String getPassword() {
		return password;
	}
 
	public void setPassword(String password) {
		this.password = password;
	}
 
	public boolean isActive() {
		return active;
	}
 
	public void setActive(boolean active) {
		this.active = active;
	}
 
	public String getRoles() {
		return roles;
	}
 
	public void setRoles(String roles) {
		this.roles = roles;
	}
}

The following interface is used to retrieve users from the database.

UserRepository.java
package com.example.demo.repository;
 
import com.example.demo.model.User;
import org.springframework.data.repository.CrudRepository;
import java.util.Optional;
 
public interface UserRepository extends CrudRepository<User, Integer> {
	Optional<User> findByUserName(String userName);
}

The UserRepository class is used to store users as local objects.

MyUsersDetailsService.java
package com.example.demo;
 
import com.example.demo.model.MyUserDetails;
import com.example.demo.model.User;
import com.example.demo.repository.UserRepository;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
 
import java.util.Optional;
 
@Service
public class MyUserDetailsService implements UserDetailsService {
	@Autowired
	UserRepository userRepository;
 
	@Override
	public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
		Optional<User> user = userRepository.findByUserName(userName);
		user.orElseThrow(() -> new UsernameNotFoundException("Not found: " + userName));
		return user.map(MyUserDetails::new).get();
	}
}

To get the individual attributes of a user, I used the MyUserDetails class. This has a method that returns all the data about a user.

MyUserDetails.java
package com.example.demo.model;
 
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
 
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
 
public class MyUserDetails implements UserDetails {
	private String userName;
	private String password;
	private boolean active;
	private List<GrantedAuthority> authorities;
 
	public MyUserDetails(User user) {
		this.userName = user.getUserName();
		this.password = user.getPassword();
		this.active = user.isActive();
		this.authorities = Arrays.stream(user.getRoles().split(","))
			.map(SimpleGrantedAuthority::new)
			.collect(Collectors.toList());
	}
 
	@Override
	public Collection<? extends GrantedAuthority> getAuthorities() {
		return authorities;
	}
 
	@Override
	public String getPassword() {
		return password;
	}
 
	@Override
	public String getUsername() {
		return userName;
	}
 
	@Override
	public boolean isAccountNonExpired() {
		return true;
	}
 
	@Override
	public boolean isAccountNonLocked() {
		return true;
	}
 
	@Override
	public boolean isCredentialsNonExpired() {
		return true;
	}
 
	@Override
	public boolean isEnabled() {
		return active;
	}
}

Now everything can be defined in the configuration file. On line 26, it is defined here that only administrators can open the previously defined link.

WebSecurityConfig.java
package com.example.demo;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
 
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
	@Autowired
	UserDetailsService userDetailsService;
 
	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		auth.userDetailsService(userDetailsService);
	}
 
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.authorizeRequests()
			.antMatchers("/").hasRole("ADMIN")
			.and().formLogin();
 
		//disable safeties from attacks
		http.csrf().disable();
		http.headers().frameOptions().disable();
	}
 
	@Bean
	public PasswordEncoder getPasswordEncoder() {
		return NoOpPasswordEncoder.getInstance();
	}
}

In this file, lines 30 and 31 are still conspicuous. As I mentioned above, I work with the h2 console as a database. I had to deactivate this security. However, you should never do this on a live web application. You would no longer be protected from attacks (opens in a new tab).

Add user

In the h2 console, I can now add new users and roles with an SQL command. To do this, you must first log into the console. To do this, use the login data defined in the application.properties file. In this case:

h2

(URL h2-console: http://localhost:8080/h2-console)

The following users can now be added to the existing user table.

h2
INSERT INTO user (name, password, active, roles) VALUES
	('admin', 'admin', true, 'ROLE_ADMIN'),
	('kay', 'pw', true, 'ROLE_USER');

Alternatively, you can also execute this code via the SQL file in the project.
When executing this statement in the h2 console, you should then see a table with the users.

h2
SELECT * FROM USER

Done

The application now grants the admin accounts access to the website.

Conclusion

I found it relatively difficult to configure Spring Security correctly at the beginning. This was because I read through several tutorials and each had a different approach. So there is not just one concrete way to make an application secure. However, I find it amazing how quickly you can implement a functional login form on a website.