web-dev-qa-db-fra.com

Comment utiliser des expressions personnalisées dans les annotations Spring Security @ PreAuthorize / @ PostAuthorize

Existe-t-il un moyen de créer des déclarations plus expressives dans les blocs @Preauthorize? Voici un exemple de quelque chose que je me retrouve à répéter, car @Preauthorize n'est pas terriblement intelligent dès le départ.

@RequestMapping(value = "{id}", method = RequestMethod.DELETE)
public void deleteGame(@PathVariable int id, @ModelAttribute User authenticatingUser) {
    Game currentGame = gameService.findById(id);
    if(authenticatingUser.isAdmin() || currentGame.getOwner().equals(authenticatingUser)) {
        gameService.delete(gameService.findById(id));
    } else {
        throw new SecurityException("Only an admin, or an owner can delete a game.");
    }
}

Ce que je préférerais, c'est quelque chose.

@RequestMapping(value = "{id}", method = RequestMethod.DELETE)
@Preauthorize(isAdmin(authenicatingUser) OR isOwner(authenicatingUser, id)
public void deleteGame(@PathVariable int id, @ModelAttribute User authenticatingUser, @ModelAttribute currentGame ) { //I'm not sure how to add this either :(
   gameService.delete(gameService.findById(id));
}

Une partie du problème est que je dois faire une requête dans la base de données pour récupérer certaines de ces choses pour vérifier les autorisations, telles que la requête dans la base de données pour obtenir une copie du jeu, puis en comparant le propriétaire du jeu à la personne qui crée la demande. Je ne sais pas vraiment comment tout cela fonctionne dans le contexte d'un processeur d'annotation @Preauthorize, ni comment j'ajoute des choses à la collection d'objets mis à disposition dans l'attribut de valeur @Preauthorize ("").

27
Jazzepi

1) Vous devez d'abord réimplémenter MethodSecurityExpressionRoot qui contient des fonctionnalités supplémentaires spécifiques à la méthode. L'implémentation Spring Security d'origine est un package privé et il n'est donc pas possible de simplement l'étendre. Je suggère de vérifier le code source de la classe donnée.

public class CustomMethodSecurityExpressionRoot extends SecurityExpressionRoot implements MethodSecurityExpressionOperations {

    // copy everything from the original Spring Security MethodSecurityExpressionRoot

    // add your custom methods

    public boolean isAdmin() {
        // do whatever you need to do, e.g. delegate to other components

        // hint: you can here directly access Authentication object 
        // via inherited authentication field
    }

    public boolean isOwner(Long id) {
        // do whatever you need to do, e.g. delegate to other components
    }
}

2) Ensuite, vous devez implémenter un MethodSecurityExpressionHandler personnalisé qui utilisera le CustomMethodSecurityExpressionRoot défini ci-dessus.

public class CustomMethodSecurityExpressionHandler extends DefaultMethodSecurityExpressionHandler {

    private final AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();

    @Override
    public void setReturnObject(Object returnObject, EvaluationContext ctx) {
        ((MethodSecurityExpressionRoot) ctx.getRootObject().getValue()).setReturnObject(returnObject);
    }

    @Override
    protected MethodSecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication,
        MethodInvocation invocation) {
        final CustomMethodSecurityExpressionRoot root = new CustomMethodSecurityExpressionRoot(authentication);
        root.setThis(invocation.getThis());
        root.setPermissionEvaluator(getPermissionEvaluator());
        root.setTrustResolver(this.trustResolver);
        root.setRoleHierarchy(getRoleHierarchy());

        return root;
    }
}

3) Définissez le bean du gestionnaire d'expression dans votre contexte, par ex. via XML, vous pouvez le faire comme suit

<bean id="methodSecurityExpressionHandler"
    class="my.package.CustomMethodSecurityExpressionHandler">
    <property name="roleHierarchy" ref="roleHierarchy" />
    <property name="permissionEvaluator" ref="permissionEvaluator" />
</bean>

4) Enregistrez le gestionnaire défini ci-dessus

<security:global-method-security pre-post-annotations="enabled">
    <security:expression-handler ref="methodSecurityExpressionHandler"/>
</security:global-method-security>

5) Ensuite, utilisez simplement les expressions définies dans vos annotations @PreAuthorize Et/ou @PostAuthorize

@PreAuthorize("isAdmin() or isOwner(#id)")
public void deleteGame(@PathVariable int id, @ModelAttribute currentGame) {
    // do whatever needed
}

Et encore une chose. Il n'est pas très courant d'utiliser la sécurité au niveau des méthodes pour sécuriser les méthodes de contrôleur, mais plutôt pour sécuriser les méthodes avec la logique métier (comme vos méthodes de couche de service). Ensuite, vous pouvez utiliser quelque chose comme ci-dessous.

public interface GameService {

    // rest omitted

    @PreAuthorize("principal.admin or #game.owner = principal.username")
    public void delete(@P("game") Game game);
}

Mais gardez à l'esprit que ce n'est qu'un exemple. Il s'attend à ce que le principal réel ait la méthode isAdmin() et que le jeu ait la méthode getOwner() retournant le nom d'utilisateur du propriétaire.

28
pgiecek

Puisque @PreAuthorize évalue SpEl - les expressions, la manière la plus simple est simplement de pointer sur un bean:

    @PreAuthorize("@mySecurityService.someFunction()")

MySecurityService.someFunction devrait avoir le type de retour boolean.

Spring-security fournira automatiquement une variable nommée authentication si vous voulez passer l'objet Authentication-. Vous pouvez également utiliser toutes les expressions SpEl valides pour accéder aux arguments passés à votre méthode sécurisée, évaluer les expressions régulières, appeler des méthodes statiques, etc. Par exemple:

    @PreAuthorize("@mySecurityService.someFunction(authentication, #someParam)")
50
gogstad

Vous pouvez écrire votre annotation quelque chose comme:

@PreAuthorize("hasRole('ROLE_ADMIN') and hasPermission(#id, 'Game', 'DELETE')")

Pour que la partie hasPermission fonctionne, vous devez implémenter l'interface PermissionEvaluator .

Définissez ensuite un bean de gestionnaire d'expression:

@Autowired
private PermissionEvaluator permissionEvaluator;

@Bean
public DefaultMethodSecurityExpressionHandler expressionHandler()
{
    DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();
    handler.setPermissionEvaluator(permissionEvaluator);
    return handler;
}

Et injectez dans votre configuration de sécurité:

<global-method-security pre-post-annotations="enabled">
  <expression-handler ref="expressionHandler" />
</global-method-security>
10
holmis83