Jak tworzyć niestandardowe metody do użycia w adnotacjach języka Spring Security Expressions

92

Chciałbym utworzyć klasę, która dodaje niestandardowe metody do użycia w języku wyrażeń bezpieczeństwa Spring w celu autoryzacji opartej na metodach za pośrednictwem adnotacji.

Na przykład chciałbym utworzyć niestandardową metodę, taką jak `` customMethodReturningBoolean '', która będzie używana w następujący sposób:

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

Moje pytanie brzmi: Jeśli to możliwe, jaką klasę powinienem utworzyć podklasę, aby utworzyć własne metody, jak zabrać się za konfigurowanie jej w plikach konfiguracyjnych XML wiosny i czy ktoś poda mi przykład niestandardowej metody używanej w ten sposób?

Paul D. Eden
źródło
1
Nie mam teraz czasu na pisanie odpowiedzi, ale postępowałem zgodnie z tym przewodnikiem i zadziałało znakomicie: baeldung.com/… Używam Spring Security 5.1.1.
Paweł

Odpowiedzi:

35

Musisz podklasować dwie klasy.

Najpierw ustaw nową procedurę obsługi wyrażenia metody

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

myMethodSecurityExpressionHandlerbędzie podklasą, DefaultMethodSecurityExpressionHandlerktórej nadpisuje createEvaluationContext(), ustawiając podklasę MethodSecurityExpressionRootna MethodSecurityEvaluationContext.

Na przykład:

@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
źródło
Hmm, brzmi to jak dobry pomysł, ale wszystkie właściwości DefaultMethodSecurityExpressionHandler są prywatne bez akcesorów, więc byłem ciekaw, jak rozszerzyłeś klasę bez brzydkiej refleksji. Dzięki.
Joseph Lust
1
Masz na myśli trustResolver itp.? Te wszystkie mają setery w DefaultMethodSecurityExpressionHandler (przynajmniej w Spring Security 3.0) Zobacz: static.springsource.org/spring-security/site/apidocs/org/…
sourcedelica
3
@ericacm Jak można ominąć MethodSecurityExpressionRootbędąc pakiet-prywatnego ?
C. Ross
176

Żadna z wymienionych technik już nie zadziała. Wygląda na to, że Spring przeszedł wiele starań, aby uniemożliwić użytkownikom zastąpienie SecurityExpressionRoot.

EDIT 11/19/14 Setup Spring, aby używać adnotacji bezpieczeństwa:

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

Utwórz fasolę w ten sposób:

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

Następnie zrób coś takiego w swoim jsp:

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

Lub opisz metodę:

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

Dodatkowo możesz użyć języka Spring Expression Language w swoich @PreAuthorizeadnotacjach, aby uzyskać dostęp do bieżącego uwierzytelnienia, a także argumentów metody.

Na przykład:

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

Następnie zaktualizuj swój, @PreAuthorizeaby pasował do nowej sygnatury metody:

@PreAuthorize("@mySecurityService.hasPermission(authentication, #foo)")
public void doSpecialStuff(String foo) { ... }
James Watkins
źródło
6
@Bosh w swojej metodzie hasPermission, możesz użyć Authentication auth = SecurityContextHolder.getContext().getAuthentication();do uzyskania aktualnego tokena uwierzytelniania.
James Watkins
2
Dzięki James za odpowiedź. Czy muszę definiować usługę mySecurityService w pliku konfiguracyjnym wiosny?
WowBow
2
Nie musisz definiować mySecurityService w żadnym pliku XML, jeśli masz konfigurację skanowania komponentów dla pakietu, w którym znajduje się usługa. Jeśli nie masz pasującego skanowania komponentów, musisz użyć definicji komponentu bean xml. @PreAuthorize pochodzi z org.springframework.security
James Watkins
3
Może być konieczne określenie nazwy ziarna w adnotacji w następujący sposób: @Component ("mySecurityService") lub użycie adnotacji @Named.
James Watkins,
1
@VJS Zobacz moją edycję. Będziesz musiał skonfigurować sprężynę, aby używała tych adnotacji. Jestem zaskoczony, że nikt inny nie narzekał na ten ważny brakujący szczegół :)
James Watkins
14

Dzięki ericacm , ale to nie działa z kilku powodów:

  • Właściwości DefaultMethodSecurityExpressionHandler są prywatne (widoczność odbicia jest niepożądana)
  • Przynajmniej w moim Eclipse nie mogę rozwiązać obiektu MethodSecurityEvaluationContext

Różnice polegają na tym, że wywołujemy istniejącą metodę createEvaluationContext , a następnie dodajemy nasz niestandardowy obiekt główny. Na koniec właśnie zwróciłem typ obiektu StandardEvaluationContext, ponieważ MethodSecurityEvaluationContext nie zostałoby rozwiązane w kompilatorze (oba pochodzą z tego samego interfejsu). To jest kod, który mam teraz w produkcji.

Spraw, aby MethodSecurityExpressionHandler używał naszego niestandardowego katalogu głównego:

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;
    }
}

Spowoduje to zastąpienie domyślnego katalogu głównego przez rozszerzenie SecurityExpressionRoot . Tutaj zmieniłem nazwę hasRole na hasEntitlement:

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);
    }

}

Na koniec zaktualizuj securityContext.xml (i upewnij się, że odwołuje się do niego w pliku applcationContext.xml):

<!-- 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" />

Uwaga: adnotacja @Secured nie zaakceptuje tego zastąpienia, ponieważ przechodzi przez inną procedurę obsługi walidacji. Tak więc w powyższym xml wyłączyłem je, aby zapobiec późniejszym nieporozumieniom.

Joseph Lust
źródło