Benutzerdefinierte AccessDecisionVoters in Spring Security

Benutzerdefinierte AccessDecisionVoters in Spring Security

1. Einführung

Meistens reichen die von Spring Security bereitgestellten Tools zum Sichern einer Spring-Webanwendung oder einer REST-API aus, aber manchmal suchen wir nach einem spezifischeren Verhalten.

In diesem Lernprogramm schreiben wir ein benutzerdefiniertesAccessDecisionVoter und zeigen, wie es verwendet werden kann, um die Autorisierungslogik einer Webanwendung zu abstrahieren und von der Geschäftslogik der Anwendung zu trennen.

2. Scenario

Um zu demonstrieren, wieAccessDecisionVoter funktioniert, implementieren wir ein Szenario mit zwei Benutzertypen,USER undADMIN,, in demUSER nur mit gerader Nummer auf das System zugreifen können Minuten, während einemADMIN immer Zugriff gewährt wird.

3. AccessDecisionVoter Implementierungen

Zunächst werden einige der von Spring bereitgestellten Implementierungen beschrieben, die zusammen mit unserem benutzerdefinierten Wähler an der endgültigen Entscheidung über die Autorisierung beteiligt sein werden. Dann schauen wir uns an, wie Sie einen benutzerdefinierten Wähler implementieren.

3.1. Die Standardimplementierungen vonAccessDecisionVoter

Spring Security bietet mehrereAccessDecisionVoter Implementierungen. Einige davon werden wir hier als Teil unserer Sicherheitslösung verwenden.

Lassen Sie uns einen Blick darauf werfen, wie und wann diese Standardwählerimplementierungen abstimmen.

DieAuthenticatedVoter geben eine Stimme ab, die auf der Authentifizierungsstufe desAuthentication-Objekts basiert - insbesondere auf der Suche nach einem vollständig authentifizierten Pricipal, einem mit Remember-Me authentifizierten oder schließlich einem anonymen.

DasRoleVoter stimmt ab, wenn eines der Konfigurationsattribute mit dem String“ROLE beginnt. ”In diesem Fall wird nach der Rolle in derGrantedAuthority-Liste desAuthentication-Objekts gesucht .

MitWebExpressionVoter können wir SpEL (Spring Expression Language) verwenden, um die Anforderungen mithilfe der Annotation@PreAuthorize zu autorisieren.

Wenn wir beispielsweise die Java-Konfiguration verwenden:

@Override
protected void configure(final HttpSecurity http) throws Exception {
    ...
    .antMatchers("/").hasAnyAuthority("ROLE_USER")
    ...
}

Oder verwenden Sie eine XML-Konfiguration - wir können SpEL innerhalb einesintercept-url-Tags imhttp-Tag verwenden:


    
    ...

3.2. BenutzerdefinierteAccessDecisionVoter Implementierung

Jetzt erstellen wir einen benutzerdefinierten Wähler - indem wir dieAccessDecisionVoter-Schnittstelle implementieren:

public class MinuteBasedVoter implements AccessDecisionVoter {
   ...
}

Die erste von drei Methoden, die wir bereitstellen müssen, ist dievote-Methode. Dievote-Methode ist der wichtigste Teil des benutzerdefinierten Wählers und dort, wo unsere Autorisierungslogik hingeht.

Die Methodevote kann drei mögliche Werte zurückgeben:

  • ACCESS_GRANTED - Der Wähler gibt eine positive Antwort

  • ACCESS_DENIED - Der Wähler gibt eine negative Antwort

  • ACCESS_ABSTAIN - Der Wähler enthält sich der Stimme

Implementieren wir nun dievote-Methode:

@Override
public int vote(
  Authentication authentication, Object object, Collection collection) {
    return authentication.getAuthorities().stream()
      .map(GrantedAuthority::getAuthority)
      .filter(r -> "ROLE_USER".equals(r)
        && LocalDateTime.now().getMinute() % 2 != 0)
      .findAny()
      .map(s -> ACCESS_DENIED)
      .orElseGet(() -> ACCESS_ABSTAIN);
}

In unserervote-Methode prüfen wir, ob die Anforderung von einemUSER stammt. Wenn ja, geben wirACCESS_GRANTED zurück, wenn es sich um eine gerade Zahl handelt, andernfalls geben wirACCESS_DENIED. zurück. Wenn die Anfrage nicht vonUSER, stammt, enthalten wir uns der Stimme und gebenACCESS_ABSTAINzurück ) s.

Die zweite Methode gibt zurück, ob der Wähler ein bestimmtes Konfigurationsattribut unterstützt. In unserem Beispiel benötigt der Wähler kein benutzerdefiniertes Konfigurationsattribut, daher geben wirtrue zurück:

@Override
public boolean supports(ConfigAttribute attribute) {
    return true;
}

Die dritte Methode gibt zurück, ob der Wähler für den gesicherten Objekttyp stimmen kann oder nicht. Da sich unser Wähler nicht mit dem gesicherten Objekttyp befasst, geben wirtrue zurück:

@Override
public boolean supports(Class clazz) {
    return true;
}

4. DieAccessDecisionManager

Die endgültige Autorisierungsentscheidung wird vonAccessDecisionManager getroffen.

DieAbstractAccessDecisionManager enthalten eine Liste vonAccessDecisionVoters, die dafür verantwortlich sind, ihre Stimmen unabhängig voneinander abzugeben.

Es gibt drei Implementierungen für die Verarbeitung der Stimmen, um die häufigsten Anwendungsfälle abzudecken:

  • AffirmativeBased - gewährt Zugriff, wenn einer derAccessDecisionVoters eine positive Stimme abgibt

  • ConsensusBased - gewährt Zugriff, wenn mehr positive als negative Stimmen vorliegen (Benutzer, die sich enthalten, werden ignoriert)

  • UnanimousBased - gewährt Zugang, wenn sich jeder Wähler der Stimme enthält oder eine positive Stimme abgibt

Natürlich können Sie Ihre eigenenAccessDecisionManager mit Ihrer benutzerdefinierten Entscheidungslogik implementieren.

5. Aufbau

In diesem Teil des Tutorials werden wir uns mit Java- und XML-basierten Methoden zum Konfigurieren unserer benutzerdefiniertenAccessDecisionVoter mitAccessDecisionManager befassen.

5.1. Java-Konfiguration

Erstellen wir eine Konfigurationsklasse für Spring Web Security:

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
...
}

Definieren wir eineAccessDecisionManager-Bean, die einenUnanimousBased-Manager verwendet, mit unserer benutzerdefinierten Wählerliste:

@Bean
public AccessDecisionManager accessDecisionManager() {
    List> decisionVoters
      = Arrays.asList(
        new WebExpressionVoter(),
        new RoleVoter(),
        new AuthenticatedVoter(),
        new MinuteBasedVoter());
    return new UnanimousBased(decisionVoters);
}

Schließlich konfigurieren wir Spring Security so, dass die zuvor definierte Bean als StandardAccessDecisionManagerverwendet wird:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
    ...
    .anyRequest()
    .authenticated()
    .accessDecisionManager(accessDecisionManager());
}

5.2. XML-Konfiguration

Wenn Sie die XML-Konfiguration verwenden, müssen Sie diespring-security.xml-Datei (oder die Datei, die Ihre Sicherheitseinstellungen enthält) ändern.

Zunächst müssen Sie das<http>-Tag ändern:


  
  ...

Fügen Sie als Nächstes eine Bean für den benutzerdefinierten Wähler hinzu:

Fügen Sie dann eine Bohne für dieAccessDecisionManager hinzu:


    
        
            
            
            
            
        
    

Hier ist ein Beispiel für das<authentication-manager>-Tag, das unser Szenario unterstützt:


    
        
            
            
        
    

Wenn Sie eine Kombination aus Java- und XML-Konfiguration verwenden, können Sie das XML in eine Konfigurationsklasse importieren:

@Configuration
@ImportResource({"classpath:spring-security.xml"})
public class XmlSecurityConfig {
    public XmlSecurityConfig() {
        super();
    }
}

6. Conclusion

In diesem Tutorial haben wir uns mit der Möglichkeit befasst, die Sicherheit für eine Spring-Webanwendung mithilfe vonAccessDecisionVoters anzupassen. Wir haben einige Wähler von Spring Security gesehen, die zu unserer Lösung beigetragen haben. Anschließend haben wir besprochen, wie ein benutzerdefiniertesAccessDecisionVoter implementiert wird.

Dann diskutierten wir, wie dieAccessDecisionManager die endgültige Autorisierungsentscheidung treffen, und wir zeigten, wie die von Spring bereitgestellten Implementierungen verwendet werden können, um diese Entscheidung zu treffen, nachdem alle Wähler ihre Stimmen abgegeben haben.

Dann haben wir eine Liste vonAccessDecisionVoters mitAccessDecisionManager über Java und XML konfiguriert.

Die Implementierung finden Sie inGithub project.

Wenn das Projekt lokal ausgeführt wird, kann auf die Anmeldeseite zugegriffen werden unter:

http://localhost:8080/spring-security-custom-permissions/login

Die Anmeldeinformationen fürUSERind "Benutzer" und "Bestanden", und die Anmeldeinformationen fürADMINind "Administrator" und "Bestanden".