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.
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.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
You can create an index.html
file under resources/templates
.
<!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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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:
(URL h2-console: http://localhost:8080/h2-console)
The following users can now be added to the existing user table.
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.
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.