Comment créer des méthodes personnalisées à utiliser dans les annotations de langage d'expression de sécurité Spring

92

Je voudrais créer une classe qui ajoute des méthodes personnalisées à utiliser dans le langage d'expression de sécurité Spring pour l'autorisation basée sur une méthode via des annotations.

Par exemple, je voudrais créer une méthode personnalisée comme 'customMethodReturningBoolean' à utiliser d'une manière ou d'une autre comme ceci:

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

Ma question est la suivante. Si c'est possible, quelle classe dois-je sous-classer pour créer mes méthodes personnalisées, comment procéder pour la configurer dans les fichiers de configuration xml de printemps et que quelqu'un me donne un exemple d'une méthode personnalisée utilisée de cette manière?

Paul D. Eden
la source
1
Je n'ai pas le temps de taper une réponse pour le moment mais j'ai suivi ce guide et cela a fonctionné à merveille : baeldung.com/ ... J'utilise Spring Security 5.1.1.
Paul

Réponses:

35

Vous devrez sous-classer deux classes.

Tout d'abord, définissez un nouveau gestionnaire d'expression de méthode

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

myMethodSecurityExpressionHandlersera une sous-classe DefaultMethodSecurityExpressionHandlerdont les remplacements createEvaluationContext(), définissant une sous-classe de MethodSecurityExpressionRootsur le MethodSecurityEvaluationContext.

Par exemple:

@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
la source
Hmm, cela semble être une bonne idée, mais toutes les propriétés de DefaultMethodSecurityExpressionHandler sont privées sans accesseurs, donc j'étais curieux de savoir comment vous avez étendu la classe sans aucune réflexion laide. Merci.
Joseph Lust
1
Vous voulez dire trustResolver, etc.? Ceux-ci ont tous des setters dans DefaultMethodSecurityExpressionHandler (au moins dans Spring Security 3.0) Voir: static.springsource.org/spring-security/site/apidocs/org/…
sourcedelica
3
@ericacm Comment évitez-vous d' MethodSecurityExpressionRootêtre privé de paquets ?
C. Ross
175

Aucune des techniques mentionnées ne fonctionnera plus. Il semble que Spring ait fait de grands efforts pour empêcher les utilisateurs de remplacer SecurityExpressionRoot.

EDIT 11/19/14 Setup Spring pour utiliser les annotations de sécurité:

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

Créez un haricot comme celui-ci:

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

Ensuite, faites quelque chose comme ça dans votre jsp:

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

Ou annoter une méthode:

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

De plus, vous pouvez utiliser Spring Expression Language dans vos @PreAuthorizeannotations pour accéder à l'authentification actuelle ainsi qu'aux arguments de méthode.

Par exemple:

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

Ensuite, mettez @PreAuthorizeà jour votre pour qu'il corresponde à la nouvelle signature de méthode:

@PreAuthorize("@mySecurityService.hasPermission(authentication, #foo)")
public void doSpecialStuff(String foo) { ... }
James Watkins
la source
6
@Bosh dans votre méthode hasPermission, vous pouvez utiliser Authentication auth = SecurityContextHolder.getContext().getAuthentication();pour obtenir le jeton d'authentification actuel.
James Watkins
2
Merci James pour ta réponse. Dois-je définir mySecurityService dans le fichier de configuration de printemps?
WowBow
2
Vous n'avez pas besoin de définir mySecurityService dans un fichier XML si vous disposez d'une configuration d'analyse des composants pour le package dans lequel se trouve le service. Si vous n'avez pas d'analyse de composants correspondante, vous devez utiliser une définition de bean xml. @PreAuthorize vient de org.springframework.security
James Watkins
3
Vous devrez peut-être spécifier le nom du bean dans l'annotation comme ceci: @Component ("mySecurityService") ou utiliser l'annotation @Named.
James Watkins
1
@VJS Veuillez voir la modification que j'ai faite. Vous devrez configurer spring pour utiliser ces annotations. Je suis surpris que personne d'autre ne se soit plaint de cet important détail manquant :)
James Watkins
14

Merci ericacm , mais cela ne fonctionne pas pour plusieurs raisons:

  • Les propriétés de DefaultMethodSecurityExpressionHandler sont privées (les kludges de visibilité de réflexion ne sont pas souhaitables)
  • Au moins dans mon Eclipse, je ne peux pas résoudre un objet MethodSecurityEvaluationContext

Les différences sont que nous appelons la méthode createEvaluationContext existante , puis ajoutons notre objet racine personnalisé. Enfin, je viens de retourner un type d' objet StandardEvaluationContext car MethodSecurityEvaluationContext ne résoudrait pas dans le compilateur (ils proviennent tous deux de la même interface). C'est le code que j'ai actuellement en production.

Faites en sorte que MethodSecurityExpressionHandler utilise notre racine personnalisée:

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

Cela remplace la racine par défaut en étendant SecurityExpressionRoot . Ici, j'ai renommé hasRole en 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);
    }

}

Enfin, mettez à jour securityContext.xml (et assurez-vous qu'il est référencé à partir de votre 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" />

Remarque: l'annotation @Secured n'acceptera pas ce remplacement car elle s'exécute via un gestionnaire de validation différent. Donc, dans le xml ci-dessus, je les ai désactivés pour éviter toute confusion ultérieure.

Joseph Lust
la source