Différence entre le rôle et GrantedAuthority dans Spring Security

228

Il existe des concepts et des implémentations dans Spring Security, tels que l' GrantedAuthorityinterface pour obtenir une autorité pour autoriser / contrôler un accès.

Je voudrais cela pour des opérations autorisées, telles que createSubUsers ou deleteAccounts , que j'autoriserais à un administrateur (avec un rôle ROLE_ADMIN).

Je deviens confus avec les tutoriels / démos que je vois en ligne. J'essaie de relier ce que j'ai lu, mais je pense que nous traitons les deux de manière interchangeable.

Je vois hasRoleconsommer une GrantedAuthoritychaîne? Je me trompe très certainement de compréhension. Que sont ces concepts dans Spring Security?

Comment stocker le rôle d'un utilisateur, distinct des autorités de ce rôle?

Je regarde également l' org.springframework.security.core.userdetails.UserDetailsinterface utilisée dans le DAO référencé par le fournisseur d'authentification, qui consomme un User(notez la dernière GrantedAuthority):

public User(String username, 
            String password, 
            boolean enabled, 
            boolean accountNonExpired,
            boolean credentialsNonExpired, 
            boolean accountNonLocked, 
            Collection<? extends GrantedAuthority> authorities)

Ou existe-t-il un autre moyen de différencier les deux autres? Ou n'est-il pas pris en charge et nous devons faire le nôtre?

Chinmay
la source

Réponses:

366

Considérez une GrantedAuthority comme une "permission" ou un "droit". Ces "autorisations" sont (normalement) exprimées sous forme de chaînes (avec la getAuthority()méthode). Ces chaînes vous permettent d'identifier les autorisations et de laisser vos électeurs décider s'ils accordent l'accès à quelque chose.

Vous pouvez accorder différentes GrantedAuthoritys (autorisations) aux utilisateurs en les plaçant dans le contexte de sécurité. Vous le faites normalement en implémentant votre propre UserDetailsService qui renvoie une implémentation UserDetails qui renvoie les GrantedAuthorities nécessaires.

Les rôles (tels qu'ils sont utilisés dans de nombreux exemples) ne sont que des "autorisations" avec une convention de dénomination qui dit qu'un rôle est une GrantedAuthority qui commence par le préfixe ROLE_. Il n'y a plus rien. Un rôle est juste une GrantedAuthority - une "permission" - un "droit". Vous voyez beaucoup d'endroits dans la sécurité du printemps où le rôle avec son ROLE_préfixe est géré spécialement comme par exemple dans le RoleVoter, où le ROLE_préfixe est utilisé par défaut. Cela vous permet de fournir les noms de rôle sans le ROLE_préfixe. Avant Spring security 4, cette gestion spéciale des «rôles» n'était pas suivie de manière très cohérente et les autorités et les rôles étaient souvent traités de la même manière (comme vous, par exemple,hasAuthority()hasRole()). Avec Spring Security 4, le traitement des rôles est plus cohérent et le code qui traite des "rôles" (comme RoleVoter, l' hasRoleexpression, etc.) ajoute toujours le ROLE_préfixe pour vous. Signifie donc hasAuthority('ROLE_ADMIN')la même chose que hasRole('ADMIN')parce que le ROLE_préfixe est ajouté automatiquement. Consultez le guide de migration Spring Security 3 à 4 pour plus d'informations.

Mais encore: un rôle est juste une autorité avec un ROLE_préfixe spécial . Ainsi, dans Spring, la sécurité 3 @PreAuthorize("hasRole('ROLE_XYZ')")est identique à @PreAuthorize("hasAuthority('ROLE_XYZ')")et dans Spring, la sécurité 4 @PreAuthorize("hasRole('XYZ')")est identique à @PreAuthorize("hasAuthority('ROLE_XYZ')").

Concernant votre cas d'utilisation:

Les utilisateurs ont des rôles et les rôles peuvent effectuer certaines opérations.

Vous pourriez vous retrouver GrantedAuthoritiespour les rôles auxquels appartient un utilisateur et les opérations qu'un rôle peut effectuer. Les GrantedAuthoritiespour les rôles ont le préfixe ROLE_et les opérations ont le préfixe OP_. Un exemple pour les autorités d'exploitation pourraient être OP_DELETE_ACCOUNT, OP_CREATE_USER, OP_RUN_BATCH_JOBetc. Les rôles peuvent être ROLE_ADMIN, ROLE_USER, ROLE_OWNERetc.

Vous pourriez finir par implémenter vos entités GrantedAuthoritycomme dans cet exemple (pseudo-code):

@Entity
class Role implements GrantedAuthority {
    @Id
    private String id;

    @ManyToMany
    private final List<Operation> allowedOperations = new ArrayList<>();

    @Override
    public String getAuthority() {
        return id;
    }

    public Collection<GrantedAuthority> getAllowedOperations() {
        return allowedOperations;
    }
}

@Entity
class User {
    @Id
    private String id;

    @ManyToMany
    private final List<Role> roles = new ArrayList<>();

    public Collection<Role> getRoles() {
        return roles;
    }
}

@Entity
class Operation implements GrantedAuthority {
    @Id
    private String id;

    @Override
    public String getAuthority() {
        return id;
    }
}

Les identifiants des rôles et opérations que vous créez dans votre base de données seraient la représentation GrantedAuthority, par exemple ROLE_ADMIN, OP_DELETE_ACCOUNTetc. Lorsqu'un utilisateur est authentifié, assurez-vous que toutes les GrantedAuthorities de tous ses rôles et les opérations correspondantes sont renvoyées par UserDetails.getAuthorities () méthode.

Exemple: Le rôle d' administrateur avec id ROLE_ADMINa les opérations OP_DELETE_ACCOUNT, OP_READ_ACCOUNT, OP_RUN_BATCH_JOBattribués. Le rôle d'utilisateur avec id ROLE_USERa l'opération OP_READ_ACCOUNT.

Si les journaux d'un administrateur dans le contexte de sécurité résultant auront les GrantedAuthorities: ROLE_ADMIN, OP_DELETE_ACCOUNT, OP_READ_ACCOUNT,OP_RUN_BATCH_JOB

Si un utilisateur se connecte, il devra: ROLE_USER,OP_READ_ACCOUNT

UserDetailsService prendrait soin de collecter tous les rôles et toutes les opérations de ces rôles et de les rendre disponibles par la méthode getAuthorities () dans l'instance UserDetails retournée.

James
la source
2
Je vous remercie! J'ai cherché partout pourquoi "hasRole ('rolename')" ne fonctionnait pas au printemps 4 -> Leur documentation n'était pas exactement rapide à parcourir. Juste un rapide "trouver et remplacer" et je suis de retour sur la bonne voie!
Jørgen Skår Fischer
12
C'est une excellente réponse. Une chose à clarifier pour expliquer un peu différemment, hasRole('xyz')dans Spring Security 4 attend que vous ayez le préfixe ROLE_ tandis que le hasAuthority('xyz')n'attend pas le préfixe et évalue exactement ce qui est passé. J'ai utilisé cette solution, mais rencontrais des problèmes avec hasRole('OP_MY_PERMISSION')depuis le préfixe ROLE_ était nécessaire. Au lieu de cela, j'aurais dû utiliser le hasAuthority('OP_MY_PERMISSION')car je n'avais pas le préfixe.
randal4
@james pouvez-vous regarder cette question stackoverflow.com/questions/41500031/…
saurabh kumar
1
Dans JSTL springframework.org/security/tags <sec:authorize access="hasRole('ADMIN')">est le même que<sec:authorize access="hasRole('ROLE_ADMIN')">
Alex78191
1
Oui. OP_ est juste un préfixe arbitraire. ROLE_ a sa signification particulière dans certaines implémentations de printemps.
James
9

AFAIK GrantedAutority et les rôles sont les mêmes dans la sécurité du printemps. La chaîne getAuthority () de GrantedAuthority est le rôle (selon l'implémentation par défaut SimpleGrantedAuthority).

Pour votre cas, vous pouvez utiliser des rôles hiérarchiques

<bean id="roleVoter" class="org.springframework.security.access.vote.RoleHierarchyVoter">
    <constructor-arg ref="roleHierarchy" />
</bean>
<bean id="roleHierarchy"
        class="org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl">
    <property name="hierarchy">
        <value>
            ROLE_ADMIN > ROLE_createSubUsers
            ROLE_ADMIN > ROLE_deleteAccounts 
            ROLE_USER > ROLE_viewAccounts
        </value>
    </property>
</bean>

Pas le sol exact que vous recherchez, mais j'espère que cela vous aidera

Edit : Répondre à votre commentaire

Le rôle est comme une autorisation dans la sécurité du printemps. utiliser intercept-url avec hasRole fournit un contrôle très fin de quelle opération est autorisée pour quel rôle / autorisation.

La façon dont nous traitons dans notre application est, nous définissons la permission (c'est-à-dire le rôle) pour chaque opération (ou l'url de repos) pour par exemple view_account, delete_account, add_account etc. Ensuite, nous créons des profils logiques pour chaque utilisateur comme admin, guest_user, normal_user. Les profils sont simplement un regroupement logique d'autorisations, indépendant de la sécurité du ressort. Lorsqu'un nouvel utilisateur est ajouté, un profil lui est attribué (disposant de toutes les autorisations autorisées). Désormais, chaque fois qu'un utilisateur essaie d'effectuer une action, l'autorisation / le rôle pour cette action est comparé aux autorisations accordées par l'utilisateur.

De même, le rôle par défaut RoleVoter utilise le préfixe ROLE_, donc toute autorité commençant par ROLE_ est considérée comme rôle, vous pouvez modifier ce comportement par défaut en utilisant un RolePrefix personnalisé dans role voter et en l'utilisant dans la sécurité du printemps.

codeur
la source
1
Merci de votre aide. La hiérarchie semble être autre chose. La façon dont je pense le faire maintenant (si je dois utiliser Spring Security) est de stocker à la fois le rôle et l'autorité dans la même liste et d'utiliser le prédicat hasRole pour effectuer les vérifications pour les deux. En y réfléchissant davantage, cela est probablement laissé intentionnel par les gars de Spring Security? Existe-t-il une possibilité d'utiliser l'URL d'interception autre que la vérification à l'aide de hasRole sur la liste complète des rôles ET privilèges / autorités - ou d'autres demandes d'autorisation. De plus, quelle est la nécessité de préfixer ROLE_? Est-ce une convention?
Chinmay
7

Une autre façon de comprendre la relation entre ces concepts est d'interpréter un RÔLE comme un conteneur d'autorités.

Les autorisations sont des autorisations précises ciblant une action spécifique couplées parfois à une portée ou un contexte de données spécifiques. Par exemple, Lire, Écrire, Gérer, peut représenter différents niveaux d'autorisations pour une étendue donnée d'informations.

En outre, les autorités sont appliquées profondément dans le flux de traitement d'une demande tandis que ROLE est filtré par filtre de demande avant d'atteindre le contrôleur. Les meilleures pratiques prescrivent la mise en œuvre de l'application des autorités après le contrôleur dans la couche métier.

D'un autre côté, les RÔLES sont une représentation à grain grossier d'un ensemble d'autorisations. Un ROLE_READER n'aurait que les droits de lecture ou de visualisation tandis qu'un ROLE_EDITOR aurait à la fois la lecture et l'écriture. Les rôles sont principalement utilisés pour un premier filtrage à la périphérie du traitement de la demande tel que http. ... .antMatcher (...). hasRole (ROLE_MANAGER)

Les autorités appliquées en profondeur dans le flux de processus de la demande permettent une application plus fine de l'autorisation. Par exemple, un utilisateur peut avoir l'autorisation de lecture / écriture pour le premier niveau d'une ressource, mais uniquement la lecture d'une sous-ressource. Avoir un ROLE_READER restreindrait son droit de modifier la ressource de premier niveau car il a besoin de l'autorisation d'écriture pour modifier cette ressource, mais un intercepteur @PreAuthorize pourrait bloquer sa tentative de modifier la sous-ressource.

Jake

softjake
la source
2

Comme d'autres l'ont mentionné, je pense aux rôles comme des conteneurs pour des autorisations plus granulaires.

Bien que je trouve que l'implémentation du rôle de hiérarchie manque d'un contrôle précis de ces autorisations granulaires.
J'ai donc créé une bibliothèque pour gérer les relations et injecter les autorisations en tant qu'autorités accordées dans le contexte de sécurité.

Je peux avoir un ensemble d'autorisations dans l'application, quelque chose comme CREATE, READ, UPDATE, DELETE, qui sont ensuite associées au rôle de l'utilisateur.

Ou des autorisations plus spécifiques comme READ_POST, READ_PUBLISHED_POST, CREATE_POST, PUBLISH_POST

Ces autorisations sont relativement statiques, mais la relation des rôles avec eux peut être dynamique.

Exemple -

@Autowired 
RolePermissionsRepository repository;

public void setup(){
  String roleName = "ROLE_ADMIN";
  List<String> permissions = new ArrayList<String>();
  permissions.add("CREATE");
  permissions.add("READ");
  permissions.add("UPDATE");
  permissions.add("DELETE");
  repository.save(new RolePermissions(roleName, permissions));
}

Vous pouvez créer des API pour gérer la relation de ces autorisations avec un rôle.

Je ne veux pas copier / coller une autre réponse, alors voici le lien vers une explication plus complète sur SO.
https://stackoverflow.com/a/60251931/1308685

Pour réutiliser mon implémentation, j'ai créé un dépôt. N'hésitez pas à contribuer!
https://github.com/savantly-net/spring-role-permissions

Jeremy
la source