Loading...

Vermeidung des statischen Zugriffs auf den SecurityContext mit @AuthenticationPrincipal in Spring

Spring
16. August 2023
3 Minuten Lesezeit
Beitrag teilen:
Gefällt dir der Beitrag?
Du wirst den Newsletter lieben!

In nahezu jeder Webanwendung muss Logik mit Bezug auf den User ausgeführt werden. Um auf diesen zuzugreifen werden seine Daten anhand seiner Authentifizierung zugeordnet. Mit Spring Security, dem Authentication-Framework im Spring Stack, haben wir verschiedene Möglichkeiten, diese essenzielle Aufgabe zu bewältigen.

Statische Zugriffe und seine Nachteile

In vielen Anwendungen und Beispielen ist es üblich, den SecurityContext in Spring statisch abzurufen, um auf die aktuellen UserDetails zuzugreifen. Das hatte jedoch einige Nachteile:

  • Kopplung: Statischer Zugriff führt zu einer engen Kopplung zwischen dem Code und dem Spring Security Framework. Tests werden schwieriger, und das Code-Design leidet darunter.
  • Thread-Sicherheit: Da die Informationen im Thread gespeichert sind, besteht die Gefahr, dass sie in einem multithreaded Environment überschrieben werden.
  • Casting: Es ist notwendig die Implementierung zur Runtime zu casten. Dadurch werden potenzielle Fehler nicht vom Compiler entdeckt.
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
UserDetails user = (UserDetails) authentication.getPrincipal();

Der elegante Weg: @AuthenticationPrincipal

Spring bietet eine bessere Lösung, mit der du eine engere Verbindung zwischen deiner Controller-Klasse und dem SecurityContext vermeiden kannst. Die @AuthenticationPrincipal Annotation hilft dir typsicher deine UserDetails Implementierung abzurufen. Das sieht dann folgendermaßen aus:

@Controller
public class UserController {
    @RequestMapping("/user")
    public String getUser(@AuthenticationPrincipal User user) {
        // ...
    }
}

Wann kann ich @AuthenticationPrincipal benutzen?

Um die @AuthenticationPrincipal Annotation erfolgreich zu nutzen, musst du das UserDetails Interface implementieren und in Spring Security registrieren. Hier sind die Schritte, die du befolgen musst:

1. Implementierung des UserDetails Interface

Die UserPrincipal Klasse muss das UserDetails Interface implementieren. Dadurch wird sichergestellt, dass Spring Security die Klasse korrekt erkennt und die Authenticated User Details korrekt abruft.

Wenn du JPA zur Persistierung nutzt, könntest du deine User Klasse als Entität modellieren:

@Entity
public class User implements UserDetails {
    
    @Id
    private UUID id = UUID.randomUUID();
    private String username;
    private String password;
    private String role;
    private boolean enabled;
    
    // Implementiere die Methoden des UserDetails Interface
    
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return Collections.singletonList(() -> role);
    }
    
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }
    
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }
    
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }
    
    @Override
    public boolean isEnabled() {
        return enabled;
    }
    
    @Override
    public String getUsername() {
        return username;
    }
    
    @Override
    public String getPassword() {
        return password;
    }
}

2. Konfiguration eines UserDetailsService

Spring Security verwendet ein UserDetailsService, um die UserDetails Klasse zu instanziieren. Du kannst einen eigenen Service erstellen, der das UserDetailsService Interface implementiert, und ihn als Bean konfigurieren.

@Service
public class CustomUserDetailsService implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        return userRepository.findByUsername(username)
                .orElseThrow(() -> new UsernameNotFoundException("User not found"));
    }
}

3. Integration mit Spring Security Configuration

Zuletzt musst du Spring Security mitteilen, dass es deinen CustomUserDetailsService verwenden soll. Dabei implementierst du WebSecurityConfigurerAdapter und registrierst deinen UserDetailsService in der configure() Methode.

@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

  @Autowired
  private CustomUserDetailsService userDetailsService;

  @Override
  protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(userDetailsService);
  }
}

Warum dieser Ansatz besser ist

Mit diesem Ansatz:

  • Erhältst du einfachen Zugriff auf die User Information, ohne den SecurityContext direkt zu verwenden.
  • Erhöhst du die Testbarkeit deines Codes, da du die Controller-Methoden einfacher mocken kannst.
  • Hältst du dich das Single-Responsibility-Prinzip und vermeidest casting im API Layer.

Kennst du schon Marcus' Backend Newsletter?

Neue Ideen. 2x pro Woche!
Top