Kernkomponenten von Spring Security

Dies ist das achte Kapitel der Tutorial-Beitragsreihe zu Spring Security. Dieser Beitrag soll einen Überblick über die Kernkomponenten von Spring Security schaffen. Zu den zentralen Klassen gehören dabei:

  • SecurityContextHolder
  • SecurityContext
  • Authentication
  • GrantedAuthority
  • UserDetails
  • UserDetailsService

Die Codebeispiele stammen aus Spring Boot in der Version 1.5.3.RELEASE.

Einen Überblick über das gesamte Tutorial, bereits veröffentlichte Kapitel sowie den Ausblick auf kommende Kapitel ist hier zu finden. Falls ihr Fragen zum Tutorial oder Source Code habt, meldet euch einfach über das LABS Kontaktformular oder wendet euch direkt über den Micromata Github Bereich an mich, Jürgen Fast (Micromata).

SecurityContextHolder

package org.springframework.security.core.context;

import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;

import java.lang.reflect.Constructor;

public class SecurityContextHolder {

    public static final String MODE_THREADLOCAL = "MODE_THREADLOCAL";
    public static final String MODE_INHERITABLETHREADLOCAL = "MODE_INHERITABLETHREADLOCAL";
    public static final String MODE_GLOBAL = "MODE_GLOBAL";
    public static final String SYSTEM_PROPERTY = "spring.security.strategy";
    private static String strategyName = System.getProperty(SYSTEM_PROPERTY);
    private static SecurityContextHolderStrategy strategy;
    private static int initializeCount = 0;

    static {
        initialize();
    }

    public static void clearContext() {
        strategy.clearContext();
    }

    public static SecurityContext getContext() {
        return strategy.getContext();
    }

    public static int getInitializeCount() {
        return initializeCount;
    }

    private static void initialize() {
        if (!StringUtils.hasText(strategyName)) {
            // Set default
            strategyName = MODE_THREADLOCAL;
        }

        if (strategyName.equals(MODE_THREADLOCAL)) {
            strategy = new ThreadLocalSecurityContextHolderStrategy();
        }
        else if (strategyName.equals(MODE_INHERITABLETHREADLOCAL)) {
            strategy = new InheritableThreadLocalSecurityContextHolderStrategy();
        }
        else if (strategyName.equals(MODE_GLOBAL)) {
            strategy = new GlobalSecurityContextHolderStrategy();
        }
        else {
            // Try to load a custom strategy
            try {
                Class<?> clazz = Class.forName(strategyName);
                Constructor<?> customStrategy = clazz.getConstructor();
                strategy = (SecurityContextHolderStrategy) customStrategy.newInstance();
            }
            catch (Exception ex) {
                ReflectionUtils.handleReflectionException(ex);
            }
        }

        initializeCount++;
    }

    public static void setContext(SecurityContext context) {
        strategy.setContext(context);
    }

    public static void setStrategyName(String strategyName) {
        SecurityContextHolder.strategyName = strategyName;
        initialize();
    }

    public static SecurityContextHolderStrategy getContextHolderStrategy() {
        return strategy;
    }

    public static SecurityContext createEmptyContext() {
        return strategy.createEmptyContext();
    }

    public String toString() {
        return "SecurityContextHolder[strategy='" + strategyName + "'; initializeCount="
                + initializeCount + "]";
    }
}

SecurityContextHolder ist die Basisklasse von Spring Security, welche den aktuellen SecurityContext der Applikation und somit auch Informationen zum aktuellen Nutzer enthält. Standardmäßig wird der SecurityContext im Threadlocal (mehr dazu hier) gehalten. Desweiteren bietet Spring noch INHERITABLETHREADLOCAL und GLOBAL als SecurityContextHolderStrategy an. Mit GLOABAL haben alle Threads einer JVM und mit INHERITABLETHREADLOCAL alle Threads, die durch den Security Thread gestartet wurden, den gleichen SecurityContext.

SecurityContext

package org.springframework.security.core.context;

import org.springframework.security.core.Authentication;
import java.io.Serializable;

public interface SecurityContext extends Serializable {

    Authentication getAuthentication();

    void setAuthentication(Authentication authentication);
}

Das Interface SecurityContext hält das Authentication Objekt. Die Standardimplementation ist SecurityContextImpl.

Authentication

package org.springframework.security.core;

import java.io.Serializable;
import java.security.Principal;
import java.util.Collection;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.context.SecurityContextHolder;


public interface Authentication extends Principal, Serializable {

    Collection<? extends GrantedAuthority> getAuthorities();

    Object getCredentials();

    Object getDetails();

    Object getPrincipal();

    boolean isAuthenticated();

    void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}

Das Interface Authentication ermöglicht unter anderem mit getPrincipal() den Zugriff auf den aktuellen Nutzer. Die Klasse des Nutzer Objektes implementiert dabei für gewöhnlich das Interface UserDetails. Nach der WebSecurityConfig aus dem ersten Kapitel Einstieg in Spring Security ist dies die Klasse org.springframework.security.core.userdetails.User. Ab dem dritten Kapitel JPA und Spring Security ist es die Klasse de.micromata.spring.security.example.security.user.AuthenticatedUser. Die Standardimplementation des Interfaces Authentication ist die abstrakte Klasse AbstractAuthenticationToken, welche wiederum von UsernamePasswordAuthenticationToken geerbt wird. Über getAuthorities() können die Rollen des Nutzers abgefragt werden.

Ein Nutzer gilt als authentifiziert, wenn der SecurityContextHolder ein vollständiges Authentication Objekt enthält. Dabei spielt es keine Rolle wie dieses Objekt gesetzt wurde, solange dies geschieht bevor der AbstractSecurityInterceptor greift. So ist es auch möglich die Authentifizierung außerhalb von Spring Security durchzuführen. Hier ist jedoch dann auch darauf zu achten, dass das Erzeugen und Setzen einer HTTP Session selbst implementiert werden muss, was normalerweise durch den Authentifizerungsprozess von Spring Security geschieht.

GrantedAuthority

package org.springframework.security.core;

import java.io.Serializable;
import org.springframework.security.access.AccessDecisionManager;

public interface GrantedAuthority extends Serializable {

    String getAuthority();
}

Dieses Interface enthält lediglich einen "Getter" für die Rolle, welche im Spring Kontext für gewöhnlich die Form ROLE_USER hat. Die Standardimplementation ist SimpleGrantedAuthority.

UserDetails

package org.springframework.security.core.userdetails;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import java.io.Serializable;
import java.util.Collection;

public interface UserDetails extends Serializable {

    Collection<? extends GrantedAuthority> getAuthorities();

    String getPassword();

    String getUsername();
    
    boolean isAccountNonExpired();

    boolean isAccountNonLocked();

    boolean isCredentialsNonExpired();

    boolean isEnabled();
}

Dieses Interface repräsentiert das User Objekt. Die bisher verwendeten Implementationen wurden oben unter Authentication bereits erwähnt. Es enthält die notwendigen Daten, um ein Authentication Objekt zu erzeugen.

UserDetailsService

package org.springframework.security.core.userdetails;

public interface UserDetailsService {

    UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

Dieses Interface wird benutzt, um anhand des username ein UserDetails Objekt zu erzeugen. Im ersten Kapitel Einstieg in Spring Security wurde dazu die Implementation org.springframework.security.provisioning.
InMemoryUserDetailsManager und ab dem dritten Kapitel JPA und Spring Security  de.micromata.spring.security.example.security.user.
AuthenticatedUserService
  verwendet.

Autor

Jürgen Fast arbeitet seit 2014 bei der Micromata GmbH als Softwareentwickler in verschiedenen Projekten der Logistik- und Automobilbranche. Sein Schwerpunkt liegt in der Entwicklung von javabasierten Webapplikationen.