Erstellen benutzerdefinierter Methoden zur Verwendung in Sprachanmerkungen für Spring Security-Ausdruckssprachen

92

Ich möchte eine Klasse erstellen, die benutzerdefinierte Methoden zur Verwendung in der Spring Security-Ausdruckssprache für die methodenbasierte Autorisierung über Anmerkungen hinzufügt.

Zum Beispiel möchte ich eine benutzerdefinierte Methode wie 'customMethodReturningBoolean' erstellen, die irgendwie wie folgt verwendet wird:

  @PreAuthorize("customMethodReturningBoolean()")
  public void myMethodToSecure() { 
    // whatever
  }

Meine Frage ist dies. Wenn es möglich ist, welche Klasse sollte ich zum Erstellen meiner benutzerdefinierten Methoden unterordnen, wie würde ich vorgehen, um sie in den XML-Konfigurationsdateien von spring zu konfigurieren, und jemand würde mir ein Beispiel für eine benutzerdefinierte Methode geben, die auf diese Weise verwendet wird?

Paul D. Eden
quelle
1
Ich habe momentan keine Zeit, eine Antwort einzugeben, aber ich habe diese Anleitung befolgt und sie hat hervorragend funktioniert: baeldung.com/… Ich verwende Spring Security 5.1.1.
Paul

Antworten:

35

Sie müssen zwei Klassen unterordnen.

Legen Sie zunächst einen neuen Methodenausdruck-Handler fest

<global-method-security>
  <expression-handler ref="myMethodSecurityExpressionHandler"/>
</global-method-security>

myMethodSecurityExpressionHandlerwird eine Unterklasse sein, DefaultMethodSecurityExpressionHandlerdie überschreibt createEvaluationContext()und eine Unterklasse von MethodSecurityExpressionRootauf dem setzt MethodSecurityEvaluationContext.

Beispielsweise:

@Override
public EvaluationContext createEvaluationContext(Authentication auth, MethodInvocation mi) {
    MethodSecurityEvaluationContext ctx = new MethodSecurityEvaluationContext(auth, mi, parameterNameDiscoverer);
    MethodSecurityExpressionRoot root = new MyMethodSecurityExpressionRoot(auth);
    root.setTrustResolver(trustResolver);
    root.setPermissionEvaluator(permissionEvaluator);
    root.setRoleHierarchy(roleHierarchy);
    ctx.setRootObject(root);

    return ctx;
}
Sourcedelica
quelle
Hmm, hört sich nach einer guten Idee an, aber alle Eigenschaften von DefaultMethodSecurityExpressionHandler sind ohne Accessoren privat. Ich war gespannt, wie Sie die Klasse ohne hässliche Reflexion erweitert haben. Vielen Dank.
Joseph Lust
1
Du meinst trustResolver usw.? Diese haben alle Setter in DefaultMethodSecurityExpressionHandler (zumindest in Spring Security 3.0). Siehe: static.springsource.org/spring-security/site/apidocs/org/…
sourcedelica
3
@ericacm Wie bekommt man um MethodSecurityExpressionRootsein Paket-private ?
C. Ross
175

Keine der genannten Techniken funktioniert mehr. Es scheint, als hätte Spring große Anstrengungen unternommen, um zu verhindern, dass Benutzer den SecurityExpressionRoot überschreiben.

EDIT 19.11.14 Setup Spring zur Verwendung von Sicherheitsanmerkungen:

<beans ... xmlns:sec="http://www.springframework.org/schema/security" ... >
...
<sec:global-method-security pre-post-annotations="enabled" />

Erstellen Sie eine Bohne wie folgt:

@Component("mySecurityService")
public class MySecurityService {
    public boolean hasPermission(String key) {
        return true;
    }
}

Dann mach so etwas in deinem jsp:

<sec:authorize access="@mySecurityService.hasPermission('special')">
    <input type="button" value="Special Button" />
</sec:authorize>

Oder kommentieren Sie eine Methode:

@PreAuthorize("@mySecurityService.hasPermission('special')")
public void doSpecialStuff() { ... }

Darüber hinaus können Sie in Ihren Anmerkungen Spring Expression Language verwenden,@PreAuthorize um auf die aktuelle Authentifizierung sowie auf Methodenargumente zuzugreifen.

Beispielsweise:

@Component("mySecurityService")
public class MySecurityService {
    public boolean hasPermission(Authentication authentication, String foo) { ... }
}

Aktualisieren Sie dann Ihre @PreAuthorize, um sie an die neue Methodensignatur anzupassen:

@PreAuthorize("@mySecurityService.hasPermission(authentication, #foo)")
public void doSpecialStuff(String foo) { ... }
James Watkins
quelle
6
@Bosh in Ihrer hasPermission-Methode können Sie Authentication auth = SecurityContextHolder.getContext().getAuthentication();das aktuelle Authentifizierungstoken abrufen.
James Watkins
2
Danke James für deine Antwort. Muss ich mySecurityService in der Spring-Konfigurationsdatei definieren?
WowBow
2
Sie müssen mySecurityService in keiner XML-Datei definieren, wenn Sie ein Komponentenscan-Setup für das Paket haben, in dem sich der Dienst befindet. Wenn Sie keinen passenden Komponentenscan haben, müssen Sie eine XML-Bean-Definition verwenden. @ PreAuthorize kommt von org.springframework.security
James Watkins
3
Möglicherweise müssen Sie den Namen der Bean für die Annotation wie folgt angeben: @Component ("mySecurityService") oder die Annotation @Named verwenden.
James Watkins
1
@VJS Bitte beachten Sie die von mir vorgenommene Bearbeitung. Sie müssen spring konfigurieren, um diese Anmerkungen zu verwenden. Ich bin überrascht, dass sich sonst niemand über dieses wichtige fehlende Detail beschwert hat :)
James Watkins
14

Danke ericacm , aber es funktioniert aus einigen Gründen nicht:

  • Die Eigenschaften von DefaultMethodSecurityExpressionHandler sind privat (Reflexionssichtbarkeit kludges unerwünscht)
  • Zumindest in meiner Eclipse kann ich ein MethodSecurityEvaluationContext- Objekt nicht auflösen

Die Unterschiede bestehen darin, dass wir die vorhandene Methode createEvaluationContext aufrufen und dann unser benutzerdefiniertes Stammobjekt hinzufügen. Schließlich habe ich gerade einen StandardEvaluationContext- Objekttyp zurückgegeben, da MethodSecurityEvaluationContext im Compiler nicht aufgelöst werden würde (beide stammen von derselben Schnittstelle). Dies ist der Code, den ich jetzt in der Produktion habe.

Lassen Sie MethodSecurityExpressionHandler unser benutzerdefiniertes Stammverzeichnis verwenden:

public class CustomMethodSecurityExpressionHandler extends DefaultMethodSecurityExpressionHandler  {

    // parent constructor
    public CustomMethodSecurityExpressionHandler() {
        super();
    }

    /**
     * Custom override to use {@link CustomSecurityExpressionRoot}
     * 
     * Uses a {@link MethodSecurityEvaluationContext} as the <tt>EvaluationContext</tt> implementation and
     * configures it with a {@link MethodSecurityExpressionRoot} instance as the expression root object.
     */
    @Override
    public EvaluationContext createEvaluationContext(Authentication auth, MethodInvocation mi) {
        // due to private methods, call original method, then override it's root with ours
        StandardEvaluationContext ctx = (StandardEvaluationContext) super.createEvaluationContext(auth, mi);
        ctx.setRootObject( new CustomSecurityExpressionRoot(auth) );
        return ctx;
    }
}

Dies ersetzt das Standardstammverzeichnis durch die Erweiterung von SecurityExpressionRoot . Hier habe ich hasRole in hasEntitlement umbenannt:

public class CustomSecurityExpressionRoot extends SecurityExpressionRoot  {

    // parent constructor
    public CustomSecurityExpressionRoot(Authentication a) {
        super(a);
    }

    /**
     * Pass through to hasRole preserving Entitlement method naming convention
     * @param expression
     * @return boolean
     */
    public boolean hasEntitlement(String expression) {
        return hasRole(expression);
    }

}

Aktualisieren Sie abschließend securityContext.xml (und stellen Sie sicher, dass in Ihrer applcationContext.xml darauf verwiesen wird):

<!-- setup method level security using annotations -->
<security:global-method-security
        jsr250-annotations="disabled"
        secured-annotations="disabled"
        pre-post-annotations="enabled">
    <security:expression-handler ref="expressionHandler"/>
</security:global-method-security>

<!--<bean id="expressionHandler" class="org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler">-->
<bean id="expressionHandler" class="com.yourSite.security.CustomMethodSecurityExpressionHandler" />

Hinweis: Die Annotation @Secured akzeptiert diese Überschreibung nicht, da sie einen anderen Validierungshandler durchläuft. Also habe ich sie in der obigen XML deaktiviert, um spätere Verwirrung zu vermeiden.

Joseph Lust
quelle