Spring Security
Datum: Mai 2021
Lesedauer: 11 Minuten
Seit ein paar Wochen arbeite ich an meinem Baseball Spring Boot Projekt. Die Webapplikation soll später Daten über verschiedene Teams anzeigen.
Zudem müssen neue Daten hinzugefügt, gelöscht und verändert werden können. Dabei muss aber natürlich sichergestellt werden, dass nicht alle Besucher diese Daten ändern können. Deshalb war meine Aufgabe, die Applikation mit Spring Security zu sichern.
Dieser Blogpost soll die essenziellen Schritte beim Sichern einer Webseite aufzeigen.
Einführung
Spring-Security ist ein Java Framework, welches man für die Authentifizierung und Autorisierung einer Anwendung verwenden kann. Man kann damit also sowohl die Identität eines Nutzers prüfen als auch Zugriff auf bestimmte Seiten gewähren oder verweigern. Zudem macht das Framework die gesamte Applikation sicherer vor Angriffen.
Anleitung
Um dieser Anleitung mühelos zu folgen, ist es von Vorteil, wenn man schon Spring Boot Vorkenntnisse hat. Aus diesem Grund empfehle ich, zuerst diesen kurzen Artikel (opens in a new tab) durchzulesen.
Spring Initializr
Zuerst benötigt man ein Spring Boot Projekt. Ich arbeite mit Visual Studio Code und kann somit ein Projekt über den p
Shortcut initialisieren.
Jedoch kann man sich auch über die Spring Webseite (opens in a new tab) ein Maven Projekt aufsetzen.
Anmeldeformular
Mit nur ein paar Zeilen Code hat man schon eine Login Maske. Dies ist das hervorragende an Spring Security.
Und so erstellt man eine solche:
Im root Verzeichnis des Projekts findet man ein File, welches alle dependencies enthaltet. Darin muss man nun das Spring Security Framework hinzufügen.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
Unter resources/templates
kann man ein index.html
File anlegen.
<!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>
Unter src/main/java/com/example/demo
muss man nun nur noch Spring Security aktivieren.
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 {
}
Die Webapplikation ist nun schon viel sicherer als zuvor.
Um sie zu starten, muss man die Application Datei ausführen. Diese sollte nach korrektem aufsetzen bereits vorhanden sein.
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);
}
}
Nach dem Start wird beim Zugriff auf die Startseite nach einem Benutzernamen und Passwort gefragt.
Ein Benutzer hat Spring Security schon erstellt. Mit dem Benutzernamen "user" und dem Passwort, welches man in der Debug Konsole findet, kann man sich nun anmelden.
GetMapping
Unter src/main/java/com/example/demo
ergibt es Sinn, eine Controller Datei im Controller Ordner anzulegen. Darin kann man Weiterleitungen definieren.
Man kann z.B. die index.html
Seite zurückgeben, wenn der User localhost:8080/home
aufruft. Dazu verwendet man die Annotation GetMapping
.
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
Zur Erstellung und Verwaltung von Nutzern verwendete ich die h2 Datenbank. Damit später diese Datenbank reibungslos funktioniert, konfigurierte ich die Properties folgendermassen.
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
Für eine Webapplikation, wie ich sie in Planung hatte, benötigte ich Benutzer und Rollen. Schliesslich dürfen später mehrere Personen die Seite
bearbeiten.
Deshalb erstellte ich ein User Entity.
Achtung: In dieser Anleitung speichere ich das Passwort als Klartext. In einem richtigen Projekt muss es aber immer als Hash verschlüsselt abgelegt
werden. Dies ist sogar gesetzlich vorgegeben.
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;
}
}
Folgendes Interface ist dazu da, die User aus der Datenbank zu holen.
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);
}
Mit Hilfe der UserRepository Klasse werden User als lokale Objekte gespeichert.
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();
}
}
Um die einzelnen Attribute eines Benutzers zu bekommen, verwendete ich die MyUserDetails
Klasse. Diese hat eine Methode, welche einem alle Daten über einen User zurückgibt.
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;
}
}
Nun kann alles im Konfigurationsfile definiert werden. Auf Zeile 26 wird hier definiert, dass nur Administratoren den zuvor definierten Link öffnen können.
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 diesem File sind noch Zeile 30 und 31 auffallend. Da ich, wie oben schon erwähnt, mit der h2 Konsole als Datenbank arbeite. Musste ich diese Sicherheiten deaktivieren. Dies sollte man aber auf einer Live Webapplikation auf keinem Fall tun. So wäre man nicht mehr vor Angriffen (opens in a new tab) geschützt.
User hinzufügen
In der h2 Konsole kann ich nun mit einem SQL Befehl neue Benutzer und Rollen hinzufügen. Dazu muss man sich zuerst in die Konsole einloggen. Dazu verwendet man die Login Daten, welche im application.properties
File definiert wurden. In diesem Fall:
(URL h2-console: http://localhost:8080/h2-console)
In die Vorhandene User Tabelle kann man nun folgende User einfügen.
INSERT INTO user (name, password, active, roles) VALUES
('admin', 'admin', true, 'ROLE_ADMIN'),
('kay', 'pw', true, 'ROLE_USER');
Alternativ kann man diesen Code auch über die SQL Datei im Projekt ausführen.
Beim ausführen dieses Statements, in der h2 Konsole, sollte man dann eine Tabelle mit den Usern sehen.
SELECT * FROM USER
Done
Nun gewährt die Applikation den Admin Accounts Zugriff auf die Webseite.
Fazit
Ich fand es zu beginn relativ schwierig, Spring Security korrekt zu konfigurieren. Dies lag daran, das ich mehrere Tutorials durchgelesen habe und dabei jedes eine andere Herangehensweise hatte. Es gibt also nicht nur einen konkreten Weg, eine Applikation sicher zu machen. Jedoch finde ich es erstaunlich, wie schnell man ein funktionsfähiges Login Formular auf einer Webseite eingebaut hat.