Shiro est un cadre de contrôle d'autorisation léger avec une large gamme d'applications. L'objectif de cet article est d'introduire l'intégration de Spring de Shiro et de permettre aux paramètres dynamiques tels que @RequiresRoles d'être pris en charge par l'extension des expressions EL de Spring. L'introduction à Shiro n'est pas dans le cadre de cet article. Si les lecteurs ne savent pas grand-chose sur Shiro, ils peuvent apprendre les informations correspondantes via son site officiel. Il existe également un article sur InfoQ qui fournit une introduction complète à Shiro, et il est également officiellement recommandé. Son adresse est https://www.infoq.com/articles/apache-hiro.
Shiro intègre le printemps
Tout d'abord, vous devez ajouter Shiro-Spring-xxx.jar à votre projet. Si vous utilisez Maven pour gérer votre projet, vous pouvez ajouter les dépendances suivantes à vos dépendances. Voici la dernière version 1.4.0 que j'ai sélectionnée.
<dependency> <proupId> org.apache.shiro </rompuprid> <letfactId> shiro-spring </ artifactid> <version> 1.4.0 </-version> </Dependance>
Ensuite, vous devez définir un shirofilter dans votre web.xml et l'appliquer à l'interception de toutes les demandes qui nécessitent un contrôle d'autorisation, généralement configuré comme / *. De plus, le filtre doit être ajouté à l'avant pour s'assurer que la demande est d'abord contrôlée via les autorisations de Shiro après son arrivée. La classe de filtre correspondante ici est configurée en déléguant FilterProxy, qui est un proxy de filtre fourni par Spring. Vous pouvez utiliser un haricot dans le conteneur de haricots à ressort comme instance de filtre actuel, et le haricot correspondant prendra le haricot correspondant au nom de filtre. Ainsi, la configuration suivante recherchera un shirofilter de haricot dans le conteneur de haricots.
<filter> <filter-name> shirofilter </filter-name> <filter-class> org.springframework.web.filter.delegatingFilterProxy </filter-class> <IniT-Value> true> TargetFilterLifyCycle </ param-name> <param-value> true </ param-value> </nitt-param> </filter> <mappage filtrant> <hilter-name> shirofilter </filter-name> <Url-potern> / * </url-potern> </filter-mapping>
Lorsque vous utilisez Shiro indépendamment, vous définissez généralement un org.apache.shiro.web.servlet.shirofilter pour faire quelque chose de similaire.
Vient ensuite de définir notre shirofilter dans le récipient de haricots. Comme suit, nous définissons un ShirofilterFactoryBean, qui produira un haricot de type abstracthirofilter. Grâce à ShirofilterFactoryBean, nous pouvons spécifier un Sec par du SecurityManager. La par défautwebsecurityManager utilisée ici doit spécifier un royaume. Si plusieurs royaumes doivent être spécifiés, il est spécifié via des royaumes. Pour plus de simplicité, nous utilisons directement TextConfigurationRealm en fonction de la définition de texte. Utilisez LoginUrl pour spécifier l'adresse de connexion, SuccessUrl pour spécifier l'adresse qui doit être redirigé après le succès de la connexion et non autorisée pour spécifier la page d'invite lorsque les autorisations sont insuffisantes. FilterChainDefinitions définit la relation entre l'URL et le filtre à utiliser. L'alias de filtre sur le côté droit du signe égal est l'alias du filtre. Les alias par défaut sont définis dans la classe d'énumération org.apache.shiro.web.filter.mgt.defaultfilter.
<bean id = "shirofilter"> <propriété name = "SecurityManager" ref = "SecurityManager" /> <propriété name = "Loginurl" value = "/ login.jsp" /> <propriété name = "SuccessUrl" value = "/ home.jsp" /> <propriété name = "unAutherizedUrl" Value = "/ unAutorized.jsp" /> <propriété = "unaTorizedl Value = "/ Unauthorized.jsp" /> <propriété name = "FilterChainDefinitions"> <value> / admin / ** = authc, rôles [admin] / logout = logout # Autres adresses exigent que l'utilisateur ait enregistré dans / ** = Authc, Logger </ Value> "Realm" Ref = "Realm" /> </ Bean> <Bean Id = "LifecycleBeanPostProcessor" />
<! - Pour plus de simplicité, nous utiliserons l'implémentation du royaume basé sur le texte ici -> <bean id = "royaume"> <propriété name = "userDefinitions"> <value> user1 = pass1, role1, role2 user2 = pass2, role2, role3 admin = admin, admin </value> </ property> </pan>
Si vous avez besoin d'utiliser un filtre personnalisé dans la définition de FilterChainDefinitions, vous pouvez spécifier le filtre personnalisé et sa relation de mappage d'alias via les filtres de ShirofilterFactoryBean. Par exemple, comme indiqué ci-dessous, nous avons ajouté un filtre avec un enregistreur d'alias et un filtre spécifié / ** avec enregistreur d'alias dans FilterChainDefinitions.
<bean id = "shirofilter"> <propriété name = "SecurityManager" ref = "SecurityManager" /> <propriété name = "Loginurl" value = "/ login.jsp" /> <propriété name = "SuccessUrl" value = "/ home.jsp" /> <propriété name = "unAutherizedUrl" Value = "/ unAutorized.jsp" /> <propriété = "unaTorizedl value = "/ unauthorized.jsp" /> <propriété name = "filters"> <util: map> <entrée key = "logger"> <bean /> </ entrée> </ util: map> </ propriété> <propriété name = "FilterChainDefinitions"> <value> / admin / ** = Authc, rôles [admin] / logout = logout # authc, logger </value> </prophed> </ank>
En fait, la définition d'alias de filtre que nous devons appliquer peut également être définie directement par SetFilters de ShirofilterFactoryBean (), mais définissez directement le haricot correspondant correspondant dans le récipient de haricot correspondant. Parce que par défaut, ShirofilterFactoryBean enregistrera tous les haricots de type filtre dans le conteneur de haricots avec leur alias ID dans les filtres. Par conséquent, la définition ci-dessus est équivalente à ce qui suit.
<bean id = "shirofilter"> <propriété name = "SecurityManager" ref = "SecurityManager" /> <propriété name = "Loginurl" value = "/ login.jsp" /> <propriété name = "SuccessUrl" value = "/ home.jsp" /> <propriété name = "unAutherizedUrl" Value = "/ unAutorized.jsp" /> <propriété = "unaTorizedl Value = "/ Unauthorized.jsp" /> <propriété name = "FilterChainDefinitions"> <value> / admin / ** = authc, rôles [admin] / logout = logout # Autres adresses exigent que l'utilisateur ait enregistré dans / ** Logger "/>
Une fois les étapes ci-dessus, l'intégration du shiro et du ressort est terminée. Pour le moment, tout chemin que nous avons demandé pour le projet nous obligera à nous connecter, et passera automatiquement sur le chemin spécifié par Loginurl et nous permettra de saisir le nom d'utilisateur / le mot de passe pour vous connecter. Pour le moment, nous devons fournir un formulaire pour obtenir le nom d'utilisateur via le nom d'utilisateur, le mot de passe via le mot de passe, puis lors de la soumission de la demande de connexion, la demande de la demande doit être soumise à l'adresse spécifiée par Loginurl, mais la méthode de la demande doit être changée pour être envoyée pour la position. Le nom d'utilisateur / mot de passe utilisé lors de la connexion est le nom d'utilisateur / mot de passe que nous avons défini dans TextConfigurationRealm. En fonction de notre configuration ci-dessus, vous pouvez utiliser user1 / pass1, admin / admin, etc. Après la connexion avec succès, il sautera à l'adresse spécifiée par le paramètre SuccessUrl. Si nous sommes connectés à l'utilisation user1 / pass1, nous pouvons également essayer d'accéder / admin / index, et à ce moment, nous passerons à non autorisé.jsp en raison d'autorisations insuffisantes.
Activer le support basé sur l'annotation
L'intégration de base nous oblige à définir tous les contrôles d'autorisation que l'URL doit appliquer dans les refinitions de filterchain de ShirofilterFactoryBean. Ce n'est parfois pas si flexible. Shiro nous fournit des annotations qui peuvent être utilisées après l'intégration du printemps. Il nous permet d'ajouter des annotations correspondantes à la classe ou à la méthode qui nécessitent un contrôle d'autorisation pour définir les autorisations requises pour accéder à la classe ou à la méthode. S'il est en classe dans la définition, cela signifie que l'appel de toutes les méthodes de la classe nécessite des autorisations correspondantes (notez qu'il doit être un appel externe, qui est la limitation de la procuration dynamique). Pour utiliser ces annotations, nous devons ajouter les deux définitions de bean suivantes au conteneur de bean de Spring, afin que nous puissions déterminer si l'utilisateur a les autorisations correspondantes en fonction de la définition d'annotation au moment de l'exécution. Ceci est réalisé grâce au mécanisme AOP du printemps. Si vous ne savez rien de Spring AOP, vous pouvez vous référer à la "colonne d'introduction de Spring AOP" de l'auteur écrit par l'auteur. Les deux définitions de haricots suivantes, AutorisationAtButesourceadvisor définit un conseiller, qui interceptera et vérifiera les autorisations basées sur la méthode de configuration d'annotation fournie par Shiro. DefaultAdVisorAutOproxyCreator fournit la fonction de création d'objets proxy pour la classe marquée avec des annotations de contrôle d'autorisation fournies par Shiro et de l'application d'autorisationAttributesourceadvisor lors de l'interception de l'appel de méthode cible. Lorsqu'une demande de l'utilisateur est interceptée et que l'utilisateur n'a pas d'autorisation marquée sur la méthode ou la classe correspondante, une exception org.apache.shiro.authz.authorizationException sera lancée.
<Bean Depend-on = "LifecycleBeanPostProcessor" /> <an Bean> <Property Name = "SecurityManager" Ref = "SecurityManager" // </ Bean>
Si <aop:config/>或<aop:aspectj-autoproxy/> est déjà défini dans notre conteneur de bean, alors le par défaut ADVisorAutoproxyCreator ne peut plus être défini. Parce que les deux cas précédents ajouteront automatiquement des fèves similaires à DefaultAdvisorAutoproxyCreator. Pour plus d'informations sur DefaultAdvisorAutoproxyCreator, vous pouvez également vous référer au principe de l'auteur de créer automatiquement des objets proxy dans Spring AOP.
Les annotations de contrôle de l'autorisation fournies par Shiro sont les suivantes:
OBLICATION AUTHENTIFICATION: L'utilisateur doit être authentifié dans la session en cours, c'est-à-dire qu'il doit se connecter avec le nom d'utilisateur / mot de passe et n'inclut pas la connexion automatique de RememberMe.
DIBIRESSER: L'utilisateur doit être authentifié. Il peut être authentifié en se connectant avec le nom d'utilisateur / mot de passe de cette session, ou il peut être automatiquement connecté à RememberMe.
CONCETTERGEST: L'utilisateur n'est pas connecté.
Obligations requises: L'utilisateur exige que le rôle spécifié soit détenu.
Requiertpermissions: l'utilisateur nécessite les autorisations spécifiées.
Les trois premiers sont faciles à comprendre, tandis que les deux derniers sont similaires. Ici, j'utilise @RequiresPerMissions à titre d'exemple. Tout d'abord, changeons le domaine défini ci-dessus et ajoutons des autorisations au rôle. De cette façon, notre User1 aura des autorisations sur Perm1, Perm2 et Perm3, et User2 aura des autorisations sur Perm1, Perm3 et Perm4.
<bean id = "royaume"> <propriété name = "userDefinitions"> <value> user1 = pass1, role1, role2 user2 = pass2, role2, role3 admin = admin, admin </value> </ propriété> <propriété name = "roleDefinitions"> <value> role1 = perm1, perm2 role2 = perm1, perm3 role3 = perm3, perm4 </value> </prea
@RequiresPerMissions peut être ajouté sur une méthode pour spécifier les autorisations qui doivent être réclamées lors de l'appel de la méthode. Dans le code suivant, nous spécifions que l'autorisation de perm1 doit être possédée lors de l'accès / perm1. À l'heure actuelle, User1 et User2 sont accessibles.
@RequestMapping ("/ perm1") @ requiredperMissions ("perm1") Public Object Permission1 () {return "perm1";}Si vous devez spécifier que vous devez avoir plusieurs autorisations en même temps pour accéder à une méthode, vous pouvez spécifier les autorisations dont vous avez besoin pour spécifier sous la forme d'un tableau (lorsque vous spécifiez un seul attribut de tableau sur l'annotation, vous ne pouvez pas ajouter d'occasion, mais lorsque vous devez spécifier plusieurs autorisations, les accolades sont requises). Par exemple, comme suit, nous spécifions que lors de l'accès / perm1andperm4, l'utilisateur doit avoir les autorisations Perm1 et Perm4. Pour le moment, seul User2 peut y accéder, car seul il a Perm1 et Perm4 en même temps.
@RequestMapping ("/ perm1andPerm4") @ requiredperMissions ({"perm1", "perm4"}) Objet public perm1andperm4 () {return "perm1andperm4";}Lorsque plusieurs autorisations sont spécifiées en même temps, la relation entre plusieurs autorisations est la relation, c'est-à-dire que toutes les autorisations spécifiées en même temps sont nécessaires. Si vous n'avez besoin que de l'une des multiples autorisations spécifiées pour être accessibles, nous pouvons spécifier la relation entre les autorisations ou entre plusieurs autorisations via logical = logical.or. Par exemple, comme suit, nous spécifions que lors de l'accès / perm1orperm4, il vous suffit d'avoir des autorisations PERM1 ou PERM4, afin que User1 et User2 puissent accéder à cette méthode.
@RequestMapping ("/ perm1orperm4") @ requiredperMissions (value = {"perm1", "perm4"}, logical = logical.or) objet public perm1orperm4 () {return "perm1orperm4";}@RequiresperMissions peut également être marqué en classe, indiquant que lorsque vous accédez à des méthodes en classe en externe, vous devez avoir des autorisations correspondantes. Par exemple, dans ce qui suit, nous spécifions que nous devons avoir la permission perm2 au niveau de la classe, tandis que la méthode index () ne spécifie pas que nous avons besoin de permis, mais nous devons toujours avoir des autorisations spécifiées au niveau de la classe lors de l'accès à cette méthode. Pour le moment, seul User1 peut y accéder.
@ RestController @ requestmapping ("/ foo") @ requiredperMissions ("perm2") classe publique FOOController {@RequestMapping (méthode = requestMethod.get) public objet index () {map <string, object> map = new hashmap <> (); map.put ("ABC", 123); carte de retour; }}Lorsque les niveaux de classe et de méthode ont @RequiresperMissions, le niveau de méthode a une priorité plus élevée et seules les autorisations requises par le niveau de méthode seront vérifiées pour le moment. Comme suit, nous spécifions que l'autorisation perm2 est requise au niveau de la classe et que l'autorisation PERM3 est requise au niveau de la méthode. Ensuite, lorsque vous accédez / FOO, vous n'avez qu'à disposer des autorisations PERM3 pour accéder à la méthode index (). Ainsi, pour le moment, User1 et User2 peuvent accéder / FOO.
@ RestController @ requestmapping ("/ foo") @ requiredperMissions ("perm2") classe publique FOOController {@RequestMapping (méthode = requestMethod.get) @RequiresPerMissions ("perm3") public index () {map <string, object> map = new hashmap <> (); map.put ("ABC", 123); carte de retour; }}Cependant, si nous ajoutons @Requiresroles ("Role1") à la classe à l'heure actuelle pour spécifier que nous devons avoir un rôle Role1, puis lors de l'accès / FOO, nous devons avoir le Role1 spécifié par @RequiresperMissions ("Perm3") sur le Role1 sur la méthode index () sur la classe. Étant donné que les obligations et les permissions sont appartenant aux définitions d'autorisation de différentes dimensions, Shiro les vérifiera une fois pendant la vérification, mais si la classe et la méthode ont des annotations du même type de définition de contrôle d'autorisation, la définition de la méthode ne sera que sur la définition.
@ RestController @ requestmapping ("/ foo") @ requiredperMissions ("perm2") @ requiredRoles ("role1") public class foocontroller {@RequestMapping (méthode = requestMethod.get) @requireSperMissions ("perm3") public index () {map <string, object> map = newshmap <> ();); map.put ("ABC", 123); carte de retour; }}Bien que l'exemple n'utilise que des permissions requises, l'utilisation d'autres annotations de contrôle d'autorisation est également similaire. Veuillez utiliser d'autres annotations par des amis intéressés.
Principe des autorisations de contrôle de l'annotation
Les autorisations que nous avons spécifiées ci-dessus sont statiques à l'aide de @RequiresPerMissions. L'un des principaux objectifs de cet article est d'introduire une méthode pour rendre les autorisations spécifiées dynamiques en étendant l'implémentation. Mais avant de nous développer, nous devons savoir comment cela fonctionne, c'est-à-dire le principe de mise en œuvre, avant de nous développer. Voyons donc comment Shiro intègre le printemps avec @RequiresperMissions. Lors de la prise en charge de @RequiresPerMissions, nous définissons le Bean suivant, qui est un conseiller, qui est hérité de StaticMethodMatcherPointCutAdvisor. Sa logique de correspondance de méthode est que tant que la classe ou la méthode a plusieurs annotations de contrôle d'autorisation de Shiro, la logique de traitement après l'interception est spécifiée par les conseils correspondants.
<ean> <propriété name = "SecurityManager" ref = "SecurityManager" /> </bant>
Ce qui suit est le code source de l'autorisationAtButesourceadVisor. Nous pouvons voir que dans sa méthode du constructeur, AOPALLIANCEANNOTATIONSAUTHORINGSMETHODINTERCITEUR, est spécifié par SetAdvice (), qui est basé sur la mise en œuvre de MethodInterceptor.
classe publique AutorisationAtTributesourceadVisor étend StaticMethodMatcherPointCutAdvisor {private static final logger log = loggerfactory.getLogger (AutorisationAtButesourceCeadVisor.class); classe finale statique privée <? étend l'annotation> [] authz_annotation_classes = new class [] {requiredperMissions.class, requillage.class, requierSer.class, requeskest.class, requireAuthentication.class}; Sécurité de SecurityManager protégé = NULL; Autorisation publiqueAtTributesourceAdVisor () {setAdvice (Nouveau AOPALLIANCEAnNOTATIONAUTHORIZingMethodInterceptor ()); } public SecurityManager GetSecurityManager () {return SecurityManager; } public void setSecurityManager (org.apache.shiro.mgt.securityManager Sec partementsManager) {this.securityManager = SecurityManager; } public Boolean Matches (méthode Méthode, classe TargetClass) {méthode m = méthode; if (isAuthzannotationPresent (m)) {return true; } // Le paramètre 'Method' pourrait provenir d'une interface qui n'a pas l'annotation. // Vérifiez si l'implémentation l'a. if (TargetClass! = null) {try {m = cibleClass.getMethod (m.getName (), m.getParameterTypes ()); retourner IsauthzannotationPresent (M) || IsauthzannotationPresent (TargetClass); } catch (NosuchMethodexception ignoré) {// La valeur de retour par défaut est fausse. Si nous ne trouvons pas la méthode, alors évidemment // il n'y a pas d'annotation, alors utilisez simplement la valeur de retour par défaut. }} return false; } privé booléen isauthzannotationPresent (classe <?> TargetClazz) {pour (class <? étend annotation> annclass: authz_annotation_classes) {annotation a = annotationutils.findannotation (TargetClazz, annclass); if (a! = null) {return true; }} return false; } private booléen isAuthZannotationPresent (méthode méthode) {for (class <? étend annotation> annclass: authz_annotation_classes) {annotation a = annotationutils.findannotation (méthode, annclass); if (a! = null) {return true; }} return false; }}Le code source d'AopallianceAntations AuthorizingMethodInterceptor est le suivant. La méthode invoquée de l'interface MethodInterceptor implémentée dans l'informatique appelle la méthode invoquée de la classe parent. Dans le même temps, nous devons voir que certaines implémentations AutorizingannotationMethodInterceptor ont été créées dans sa méthode de constructeur. Ces implémentations sont au cœur de la mise en œuvre du contrôle d'autorisation. Plus tard, nous sélectionnerons la classe d'implémentation PermissionAnnotationMethodInterceptor pour voir sa logique d'implémentation spécifique.
classe publique AOPALLIANCEANNOTATIONSAUTHORINISEMETHODINTERBITEUR étend AnnotationsAuthorizingMethodInterceptor implémente MethodInterceptor {public aopallianceAnnotations AuthorizingMethodInterceptor () {listannotationMethoTationMethodInterceptor> Interceptors = New ArrayList <AutorizingannotationMethodInterNinter> (5); // Utilisez un résolveur d'annotation spécifique au printemps - AnnotationUtils de Spring est plus agréable que le processus de résolution JDK // brut. AnnotationResolver Resolver = new SpringannotationResolver (); // Nous pouvons réutiliser la même instance de résolveur - elle ne conserve pas l'état: interceptors.add (new RoleanNotationMethodInterceptor (Resolver)); Interceptors.ADD (nouvelle permissionannotationMethodInterceptor (Resolver)); interceptors.add (nouveau userAnnotationMethodInterceptor (Resolver)); Interceptors.Add (nouveau GuenotationMethodInterceptor (Resolver)); setMethodInterceptors (intercepteurs); } Org.apache.shiro.aop.MethodInvocation CreateThodInvocation (Objet implSpecificMethodInvocation) {Final Methodinvocation Mi = (méthodinvocation) implSpecificMethodInvocation; return new org.apache.shiro.aop.methodinvocation () {Méthode publique getMethod () {return mi.getMethod (); } objet public [] getArguments () {return mi.getArguments (); } public String toString () {return "méthode invocation [" + mi.getMethod () + "]"; } public objet procédure () lance Throwsable {return mi.proceed (); } Objet public getTHis () {return mi.getThis (); }}; } Objet protégé continuinvocation (objet aopallianceMethodInvocation) lève le throwable {méthodinvocation mi = (méthodinvocation) aopallianceMethodInvocation; retour mi.proceed (); } Objet public Invoke (méthodinvocation methodinvocation) lève le throwable {org.apache.shiro.aop.methodinvocation mi = CreateMethodInvocation (méthodinvocation); return super.invoke (mi); }}En examinant la mise en œuvre de la méthode invoquée de la classe parent, nous verrons enfin que la logique principale est d'appeler la méthode affirmée, et l'implémentation de cette méthode (le code source est le suivant) est de déterminer si l'autorisation configurée de l'améthodiette prend en charge la vérification de l'autorisation de la méthode actuelle (en jugeant si elle a une annotation prise en charge sur la classe ou la méthode). Lorsqu'il est pris en charge, sa méthode AssertAuthorisée sera appelée pour la vérification de l'autorisation, et l'autorisation de laMéthodonTinterceptor appellera à son tour la méthode AssertAuthorisée pour AuthorizingannotationHandler.
Protected void AssertAuthorized (méthodinvocation methodinvocation) lève AuthorizationException {// La mise en œuvre par défaut garantit qu'aucun vote de refus n'est exprimé: collection <AutorizationannotationMethodInterceptor> aamis = getMethodInterceptor (); if (aamis! = null &&! aamis.isempty ()) {for (AuthorizingAnnotationMethodInterceptor aami: aamis) {if (aami.supports (méthodinvocation)) {aami.AssertAuthorized (méthodinVocation); }}}}Ensuite, regardons en arrière la permissionnotationMethodInterceptor définie par AOPALLIANCEAnNotations AuthorizingMethodInterceptor, le code source est le suivant. En combinant le code source d'AopallianceAnnotations AuthorizingMethodInterceptor et le code source de PermissionannotationMethodInterceptor, nous pouvons voir que PermissionAnnotationHandler et SpringannotationResolver sont spécifiées dans PermissionannotationMethodInterceptor. PermissionAnnotationHandler est une sous-classe d'autoriserannotationHandler. Ainsi, notre contrôle d'autorisation final est déterminé par la mise en œuvre affirmée de PermissionAnnotationHandler.
Classe publique PermiteMannotationMethodInterceptor étend l'autorisation de SinotationMethodInterceptor {public PermissionAnnotationMethodInterceptor () {super (new PermissionAnnotationHandler ()); } Public PermissionAnnotationMethodInterceptor (AnnotationResolver Resolver) {super (new PermissionAnnotationHandler (), Resolver); }}Ensuite, examinons la mise en œuvre de la méthode AssertAuthorized de PermiteSinannotationHandler, et le code complet est le suivant. D'après l'implémentation, nous pouvons voir qu'il obtiendra la valeur d'autorisation configurée à partir de l'annotation, et l'annotation ici est l'annotation de Permissions. De plus, lors de la vérification de l'autorisation, nous utilisons directement la valeur du texte spécifiée lors de la définition de l'annotation. Nous commencerons à partir d'ici lorsque nous l'agrandirons plus tard.
Classe publique PermitegingAnnotationHandler étend AuthorizingAnnotationHandler {public permissionannotationHandler () {super (requiredperMissions.class); } String protégé [] GetAnnotationValue (Annotation a) {DealSperMissions rpannotation = (DealSperMissions) a; return Rpannotation.Value (); } public void assertAuthorized (Annotation a) lève AuthorizationException {if (! (Une instance de requiertPermissions)) return; Requiertpermisessions rpannotation = (requiredPermissions) a; String [] perms = getAnnotationValue (a); Sujet sujet = getSubject (); if (perms.length == 1) {sujet .CheckPermission (perms [0]); retour; } if (logical.and.equals (rpannotation.logical ())) {getSubject (). checkPerMissions (perms); retour; } if (logical.or.equals (rpannotation.logical ())) {// Éviter le traitement des exceptions sans cesse - "Delay" lançant l'exception en appelant Hasrole First Boolean HasatleastOnePermission = false; pour (autorisation de chaîne: perms) if (getUbject (). Ispermited (permission)) HasatleastOnePermission = true; // provoque l'exception si aucune ne correspond à un rôle, notez que le message d'exception sera un peu trompeur si (! HasatleastOnePermission) getSubject (). CheckPermission (perms [0]); }}}Grâce à l'introduction précédente, nous savons que l'annotation du paramètre de méthode ASSERTATHORISE de PermissionAnnotationHandler est adoptée en autorisant laManthodInterceptor lors de l'appel de la méthode AssertAuthorisée pour autorisation de SinotationHandler. Le code source est le suivant. À partir du code source, nous pouvons voir que l'annotation est obtenue via la méthode Getannotation.
public void assertAuthorized (méthodinvocation mi) lève AuthorizationException {try {((AuthorizingAnnotationHandler) Gethandler ()). AssertAuthorized (getAnnotation (mi)); } catch (AuthorizationException ae) {if (ae.getcause () == null) ae.initcause (new AuthorizationException ("Non autorisé à invoquer la méthode:" + mi.getMethod ())); lancer ae; }}En marchant dans cette direction, nous trouverons éventuellement la mise en œuvre de la méthode Getannotation de SpringannotationResolver, qui est mise en œuvre comme suit. Comme on peut le voir à partir du code suivant, il est préféré de rechercher la méthode lors de la recherche d'annotations. S'il n'est pas trouvé sur la méthode, il recherchera l'annotation correspondante de la classe de l'appel de méthode actuel. De là, nous pouvons également voir pourquoi celui qui prend effet sur la méthode lorsque nous définissons le même type d'annotation de contrôle d'autorisation sur la classe et la méthode avant, et lorsqu'il existe seul, celui qui est défini prend effet.
Classe publique SpringannotationResolver implémente AnnotationResolver {public annotation getAnnotation (méthodinvocation mi, class <? étend l'annotation> Clazz) {Method M = mi.getMethod (); Annotation a = annotationutils.findannotation (M, Clazz); if (a! = null) renvoie a; // L'objet de méthode de Methodinvocation pourrait être une méthode définie dans une interface. // Cependant, si l'annotation existait dans l'implémentation de l'interface (et non // l'interface elle-même), elle ne sera pas sur l'objet de méthode ci-dessus. Au lieu de cela, nous devons // acquérir la représentation de la méthode à partir de TargetClass et vérifier directement sur // implémentation lui-même: classe <?> TargetClass = mi.getThis (). GetClass (); m = classutils.getostSpecificMethod (M, TargetClass); a = annotationutils.findannotation (M, Clazz); if (a! = null) renvoie a; // Voir si la classe a le même retour d'annotation annotationutils.findannotation (mi.getThis (). GetClass (), Clazz); }} Grâce à la lecture du code source ci-dessus, je crois que les lecteurs ont une compréhension plus approfondie du principe des annotations de contrôle de l'autorisation soutenues par Shiro après avoir intégré le printemps. Le code source publié ci-dessus n'est que certains des principaux que l'auteur pense être relativement fondamentaux. Si vous souhaitez connaître le contenu complet en détail, veuillez lire le code complet par vous-même le long des idées mentionnées par l'auteur.
Après avoir compris ce principe de contrôle de l'autorisation en fonction des annotations, les lecteurs peuvent également se développer en conséquence en fonction des besoins réels de l'entreprise.
Étendu à l'aide d'expressions Spring EL
Supposons qu'il existe maintenant une interface comme celle suivante, qui a une méthode de requête qui reçoit un type de paramètre. Simplifions-le ici, en supposant que tant qu'un tel paramètre est reçu et que différentes valeurs seront renvoyées.
Interface publique RealService {Object Query (int type); }Cette interface est ouverte au monde extérieur. Cette méthode peut être demandée via l'URL correspondante. Nous définissons la méthode du contrôleur correspondant comme suit:
@RequestMapping ("/ Service / {type}") Public Object Query (@PathVariable ("Type") int type) {return this.realService.Query (type);}Le service d'interface ci-dessus a des autorisations de type lors de la réalisation de requête. Tous les utilisateurs ne peuvent pas utiliser chaque type pour interroger et cela nécessite des autorisations correspondantes. Par conséquent, pour la méthode du processeur ci-dessus, nous devons ajouter un contrôle d'autorisation et les autorisations requises pendant le contrôle changent dynamiquement avec le type de paramètre. Supposons que la définition de chaque autorisation de type soit la forme de requête: type. Par exemple, l'autorisation requise lorsque Type = 1 est une requête: 1, et l'autorisation requise lorsque le type = 2 est la requête: 2. Lorsqu'il n'est pas intégré au printemps, nous le faisons comme suit:
@RequestMapping ("/ Service / {type}") Public Object Query (@PathVariable ("Type") int type) {SecurityUtils.getSubject (). CheckPermision ("Query:" + Type); Renvoie ce.realservice.query (type);}Cependant, après l'intégration avec le printemps, les pratiques ci-dessus sont très couplées, et nous préférons utiliser les annotations intégrées pour contrôler les autorisations. Pour le scénario ci-dessus, nous préférons spécifier les autorisations requises via @RequiresPerMissions, mais les autorisations définies dans @RequiresPermissions sont du texte statique et fixe. Il ne peut pas répondre à nos besoins dynamiques. Pour le moment, vous pouvez penser que nous pouvons diviser la méthode de traitement du contrôleur en plusieurs et contrôler les autorisations séparément. Par exemple, ce qui suit est:
@RequestMapping ("/ Service / 1") @ requiredPerMissions ("Query: 1") public Object Service1 () {return this.realservice.query (1);} @ requiredperMissions ("Query: 2") @ requestmapping ("/ service / 2") public Object Service2 () {return: this.realService.Query (2);} //...@ RequestMapping ("/ Service / 200") @ requiredperMissions ("Query: 200") public Object Service200 () {return this.realservice.query (200);};C'est OK lorsque la plage de valeur de type est relativement faible, mais s'il y a 200 valeurs possibles comme celles ci-dessus, il sera un peu difficile de les énumérer de manière exhaustive pour définir une méthode de processeur distincte et effectuer un contrôle d'autorisation. De plus, si la valeur du type change à l'avenir, nous devons ajouter une nouvelle méthode de processeur. La meilleure façon est donc de faire de @RequiresPerMissions Support Dynamic Permission Definitions, tout en maintenant le support de définition statique. Grâce à l'analyse précédente, nous savons que le point d'entrée est PermissionAnnotationHandler, et il ne fournit pas d'extensions pour la vérification de l'autorisation. Si nous voulons l'étendre, le moyen simple est de le remplacer dans son ensemble. Cependant, les autorisations dont nous avons besoin pour traiter dynamiquement sont liées aux paramètres de la méthode, et les paramètres de la méthode ne peuvent pas être obtenus dans le PermitemSannotationHandler. Pour cette raison, nous ne pouvons pas remplacer directement le PermitegingAnnotationHandler. PermissionAnnotationHandler est appelée par PermissionAnnotationMethodInterceptor. Les paramètres de la méthode peuvent être obtenus lorsque PermissionAnnotationHandler est appelée dans la méthode assertAuthorisée de sa classe parent, autorisant laMéthodonTinterceptor. Pour cette raison, notre point d'extension est sélectionné sur la classe de permissionnotationMethodInterceptor, et nous devons également le remplacer dans son ensemble. Les expressions EL de Spring peuvent prendre en charge les valeurs des paramètres de la méthode d'analyse. Ici, nous choisissons d'introduire les expressions EL de Spring. Lorsque @RequiresPerMissions définissent les autorisations, vous pouvez utiliser les expressions Spring EL pour introduire des paramètres de méthode. Dans le même temps, afin de prendre en compte le texte statique. Voici un modèle d'expression Spring EL. Pour le modèle d'expression EL de Spring, veuillez vous référer à ce billet de blog. Nous définissons notre propre AutorisationnotationMethodInterceptor, les hériter de la permissionnotationMethodInterceptor, et remplacer la méthode affirmative. La logique d'implémentation de la méthode fait référence à la logique dans PermitemSannotationHandler, mais la définition d'autorisation dans @RequiresPerMissions utilisées est le résultat de notre utilisation de l'expression de Spring EL basée sur la méthode actuellement appelée en raison de l'analyse de l'évaluation Context. Ce qui suit est notre propre définition de la mise en œuvre de la mise en œuvre de l'autorisation de laMéthodInterceptor.
classe publique SelfPermissionAnnotationMethodInterceptor étend la permissionnotationMethodInterceptor {private final SpelexpressionPaSer parser = new SpexpressionPaSer (); Private Final ParametterNameDiscoverer ParamNameDiscoverer = new DefaultParameterNameDiscoverer (); TemplatePlatePaSerConText privé TemplatePaRSerConText = new TemplatePaSerConText (); public SelfPermissionAnnotationMethodInterceptor(AnnotationResolver resolver) { super(resolver); } @Override public void assertAuthorized(MethodInvocation mi) throws AuthorizationException { Annotation annotation = super.getAnnotation(mi); RequiresPermissions permAnnotation = (RequiresPermissions) annotation; String[] perms = permAnnotation.value(); EvaluationContext evaluationContext = new MethodBasedEvaluationContext(null, mi.getMethod(), mi.getArguments(), paramNameDiscoverer); for (int i=0; i<perms.length; i++) { Expression expression = this.parser.parseExpression(perms[i], templateParserContext); //Replace the original permission definition with the permission definition parsed by Spring EL expression perms[i] = expression.getValue(evaluationContext, String.class); } Subject subject = getSubject(); if (perms.length == 1) { subject.checkPermission(perms[0]); retour; } if (Logical.AND.equals(permAnnotation.logical())) { getSubject().checkPermissions(perms); retour; } if (Logical.OR.equals(permAnnotation.logical())) { // Avoid processing exceptions unnecessarily - "delay" throwing the exception by calling hasRole first boolean hasAtLeastOnePermission = false; for (String permission : perms) if (getSubject().isPermitted(permission)) hasAtLeastOnePermission = true; // Cause the exception if none of the role match, note that the exception message will be a bit misleading if (!hasAtLeastOnePermission) getSubject().checkPermission(perms[0]); }}}定义了自己的PermissionAnnotationMethodInterceptor后,我们需要替换原来的PermissionAnnotationMethodInterceptor为我们自己的PermissionAnnotationMethodInterceptor。根据前面介绍的Shiro整合Spring后使用@RequiresPermissions等注解的原理我们知道PermissionAnnotationMethodInterceptor是由AopAllianceAnnotationsAuthorizingMethodInterceptor指定的,而后者又是由AuthorizationAttributeSourceAdvisor指定的。为此我们需要在定义AuthorizationAttributeSourceAdvisor时通过显示定义AopAllianceAnnotationsAuthorizingMethodInterceptor的方式显示的定义其中的AuthorizingAnnotationMethodInterceptor,然后把自带的PermissionAnnotationMethodInterceptor替换为我们自定义的SelfAuthorizingAnnotationMethodInterceptor。替换后的定义如下:
<bean> <property name="securityManager" ref="securityManager"/> <property name="advice"> <bean> <property name="methodInterceptors"> <util:list> <bean c:resolver-ref="springAnnotationResolver"/> <!-- 使用自定义的PermissionAnnotationMethodInterceptor --> <bean c:resolver-ref="springAnnotationResolver"/> <bean c:resolver-ref="springAnnotationResolver"/> <bean c:resolver-ref="springAnnotationResolver"/> <bean c:resolver-ref="springAnnotationResolver"/> </util:list> </property> </bean> </property></bean><bean id="springAnnotationResolver"/>
为了演示前面示例的动态的权限,我们把角色与权限的关系调整如下,让role1、role2和role3分别拥有query:1、query:2和query:3的权限。此时user1将拥有query:1和query:2的权限。
<bean id="realm"> <property name="userDefinitions"> <value> user1=pass1,role1,role2 user2=pass2,role2,role3 admin=admin,admin </value> </property> <property name="roleDefinitions"> <value> role1=perm1,perm2,query:1 role2=perm1,perm3,query:2 role3=perm3,perm4,query:3 </value> </property></bean>
此时@RequiresPermissions中指定权限时就可以使用Spring EL表达式支持的语法了。因为我们在定义SelfPermissionAnnotationMethodInterceptor时已经指定了应用基于模板的表达式解析,此时权限中定义的文本都将作为文本解析,动态的部分默认需要使用#{前缀和}后缀包起来(这个前缀和后缀是可以指定的,但是默认就好)。在动态部分中可以使用#前缀引用变量,基于方法的表达式解析中可以使用参数名或p参数索引的形式引用方法参数。所以上面我们需要动态的权限的query方法的@RequiresPermissions定义如下。
@RequestMapping("/service/{type}")@RequiresPermissions("query:#{#type}")public Object query(@PathVariable("type") int type) { return this.realService.query(type);}这样user1在访问/service/1和/service/2是OK的,但是在访问/service/3和/service/300时会提示没有权限,因为user1没有query:3和query:300的权限。
Résumer
The above is a detailed explanation of Spring integrating Shiro and expanding the use of EL expressions introduced by the editor. I hope it will be helpful to everyone. Si vous avez des questions, veuillez me laisser un message et l'éditeur vous répondra à temps. Merci beaucoup pour votre soutien au site Web Wulin.com!