Datenbankgestützte Authentifizierung mit Spring Security

veröffentlicht am 21. September 2017

Spring basierte Webanwendungen werden für gewöhnlich mit Spring Security abgesichert. Dieser Artikel zeigt, wie eine datenbankgestützte Authentifizierung mit diesem Framwork umgesetzt werden kann.

Benutzermodell

Zunächst wird eine Klasse zur Abbildung der Benutzer in der Datenbank benötigt. In diesem Artikel wird Spring Data JPA als Persistenzframework verwendet, es kann aber gegen jedes beliebige andere Persistenzframework ausgetauscht werden:

package example.entity;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "users")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "user_id")
    private Integer id;

    @Column(name = "username")
    private String username;

    @Column(name = "password")
    private String password;

    public Integer getId() {
        return this.id;
    }

    public void setId(final Integer id) {
        this.id = id;
    }

    public String getUsername() {
        return this.username;
    }

    public void setUsername(final String username) {
        this.username = username;
    }

    public String getPassword() {
        return this.password;
    }

    public void setPassword(final String password) {
        this.password = password;
    }

}

Repository

Für den Zugriff auf die Benutzereinträge in der Datenbank wird ein Spring Data JPA spezifisches Repository angelegt:

package example.repository;

import org.springframework.data.jpa.repository.JpaRepository;

import example.entity.User;

public interface UserRepository extends JpaRepository<User, Integer> {

    User findByUsername(String username);

}

Sofern ein anderes Persistenzframework verwendet wird, muss das Repository gegen eine entsprechende Klasse ersetzt werden.

UserDetails

Zum Verwalten von Benutzerinformationen benötigt Spring Security eine eigene UserDetails Implementierung. In diesem Beispiel verpacken wir unser Benutzerobjekt in der UserDetails Implementierung und delegieren die Methodenaufrufe an unser Objekt:

package example.security;

import java.util.Collection;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;

import example.entity.User;

public class MyUserDetails implements UserDetails {

    private static final long serialVersionUID = 1L;

    private final User user;

    public MyUserDetails(final User user) {
        this.user = user;
    }

    @Override
    public String getUsername() {
        return this.user.getUsername();
    }

    @Override
    public String getPassword() {
        return this.user.getPassword();
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return AuthorityUtils.createAuthorityList("ROLE_USER");
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }

}

Die Methoden getAuthorities, isAccountNonExpired, isAccountNonLocked, isCredentialsNonExpired und isEnabled liefern in in diesem Beispiel immer feste Werte zurück. Sollen diese Werte ebenfalls konfigurierbar sein, muss lediglich das Benutzermodell erweitert und die jeweilige Methode angepasst werden.

UserDetailsService

Zum Laden der Benutzerinformationen benötigt Spring Security einen UserDetailsService:

package example.security;

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 example.entity.User;
import example.repository.UserRepository;

@Service
public class MyUserDetailsService implements UserDetailsService {

    private final UserRepository userRepository;

    @Autowired
    public MyUserDetailsService(final UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public UserDetails loadUserByUsername(final String username) throws UsernameNotFoundException {

        final User user = this.userRepository.findByUsername(username);

        if (user == null) {
            throw new UsernameNotFoundException(username);
        }

        return new MyUserDetails(user);

    }

}

Konfiguration

Zuletzt werden alle Puzzlestücke über eine Spring Security Konfiguration zusammengesetzt:

package example.security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private MyUserDetailsService userDetailsService;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public DaoAuthenticationProvider authenticationProvider() {
        final DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
        authProvider.setUserDetailsService(this.userDetailsService);
        authProvider.setPasswordEncoder(this.passwordEncoder());
        return authProvider;
    }

    @Override
    protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(this.authenticationProvider());
    }

}

Zu beachten ist, dass BCrypt zum sicheren Ablegen von Passwörtern als Hash in der Datenbank verwendet wird. Passwörter müssen also zunächst mit BCrypt gehashed werden, bevor sie in die Datenbank eingetragen werden.

Fazit

Dieser Artikel hat die Umsetzung einer datenbankgestützten Authentifizierung mit Spring Security gezeigt. Die Umsetzung dauert nur wenige Minuten und kann flexibel an die eigenen Bedürfnisse angepasst werden. Die vollständige Implementierung findest du bei GitHub.