Ici, nous adoptons principalement la solution d'annotation personnalisée de Shiro. Cet article résout principalement les problèmes suivants.
Comment associer la page à l'interface API par la logique.
Utilisation de l'auto-annotation de Shiro.
Comment rédiger des annotations personnalisées.
Dans la relation structurelle entre les tables, les tables de page et d'interface sont finalement associées à la table des autorisations (Pour plus de détails, veuillez consulter mon article précédent « Discussions diverses sur la conception des autorisations »).
Nous espérons désormais la remplacer par une autre solution permettant d'atteindre un faible coût tout en prenant en compte un certain degré de contrôle des autorisations. Nous introduisons ici deux concepts. Module métier, Type d'opération.
Module métier
Concept : résumer le module métier dans le système en une sorte de données, on peut l'exprimer sous forme de chaîne, par exemple : la gestion des rôles correspond à role-manage, la gestion des utilisateurs correspond à user-manage, etc. Nous divisons les modules métiers existants dans le système selon le « principe du moindre privilège » et formons enfin un lot de données distribuables.
Principes d'utilisation : L'interface API, la page et la fonction sont essentiellement logiquement liées au module métier. Par conséquent, nous pouvons comparer l'interface API et la page ( et. points de fonction) pour effectuer une correspondance logique afin de déterminer la relation entre la page et l'interface.
Type d'opération
Concept : Résumer tous les types d'opérations du système en un seul. peut aussi exprimer ce type de données sous forme de chaînes, par exemple : add correspond à un nouvel ajout, allot correspond à une allocation, etc. Nous divisons tous les types d'opérations dans le système via des « licences de données » en fonction des modules métier, et formons enfin un lot de données distribuables.
Principes d'utilisation : La page est l'affichage, le point de fonction est l'action et l'interface est la mise à disposition des ressources pour l'action finale. à appeler sont déterminés via le "module métier", le "type d'opération" détermine la manière dont la ressource est utilisée. Grâce aux deux, vous pouvez déterminer approximativement et précisément si l'interface déclenchée par le point de fonction de la page se situe dans la plage d'authentification.
Maintenant que ces deux concepts sont proposés, quelle est leur utilisation réelle finale Réfléchissons-y d'abord sous les perspectives suivantes ?
Les données de la table des pages de la base de données ou de la table de l'interface API sont-elles réelles et valides ?
L'utilisation réelle de la page ou de l'interface repose sur l'existence de fonctions ou l'existence de données dans la table de la base de données.
Dans la structure des autorisations, le seul moyen de stocker des « objets de contrôle » est-il la base de données ?
Examinons ces problèmes depuis la conclusion. Tout d'abord, le stockage des "objets de contrôle" peut être dans la base de données, dans le code ou dans le fichier de configuration. il ne faut pas nécessairement être dans la base de données ; alors pour répondre à la deuxième question, lorsque les informations d'interface existent dans la base de données mais que le serveur n'a pas développé cette interface, il y a un problème avec les informations de la base de données elle-même, ou avec la nouvelle interface dans la base de données. doit être côté serveur Seules les interfaces qui ont été déployées peuvent prendre effet ; alors il y a la première question, alors les données de la table "objet de contrôle" dans la base de données ne sont pas forcément vraies et valides. Nous pouvons donc proposer la solution suivante
Nous pouvons utiliser le formulaire d'annotation pour compléter les informations de données du "module métier" et du "type d'opération" sur le interface, les deux types d'informations peuvent être stockés dans des classes constantes.
Lors de l'ajout et de la création de la structure de la table de page et de la structure de la table de fonction de page dans la base de données, ajoutez le "Module Business" et " Champs "Type d'opération".
Vous pouvez stocker les informations « Module métier » et « Type d'opération » dans la table du dictionnaire de la base de données.
L'ajout de modules ou d'opérations entraînera inévitablement l'ajout d'interfaces, ce qui entraînera une activité de déploiement du système. Ce coût d'exploitation et de maintenance ne peut être réduit. structure des tableaux.
Cependant, cette solution ne convient qu'aux projets d'interface de contrôle non forte. Dans les projets d'interface de contrôle forte, la page et l'interface doivent toujours être liées, même si cela entraînera d'énormes coûts d'exploitation et de maintenance. De plus, il peut également être divisé par règles de routage d'interface, par exemple : /api/page/xxxx/ (utilisé uniquement pour les pages), /api/mobile/xxxxx (utilisé uniquement pour les terminaux mobiles) classera les interfaces qui ne sont utilisées que par pages.Cette interface de classe effectue uniquement l'authentification mais pas l'autorisation, ce qui peut également atteindre cet objectif.
Après avoir été approuvé par une idée théorique, le reste est mis en pratique technique Nous utilisons le framework de sécurité d'Apache Shiro, appliqué dans l'environnement Spring Boot. Expliquez brièvement les annotations suivantes de Shiro.
注解名 | 作用 |
---|---|
@RequiresAuthentication | 作用于的类、方法、实例上。调用时,当前的subject是必须经过了认证的。 |
@RequiresGuest | 作用于的类、方法、实例上。调用时,subject可以是guest状态。 |
@RequiresPermissions | 作用于的类、方法、实例上。调用时,需要判断suject中是否包含当前接口中的Permission(权限信息)。 |
@RequiresRoles | 作用于的类、方法、实例上。调用时,需要判断subject中是否包含当前接口中的Role(角色信息)。 |
@RequiresUser | 作用于的类、方法、实例上。调用时,需要判断subject中是否当前应用中的用户。 |
/** * 1.当前接口需要经过"认证"过程 * @return */ @RequestMapping(value = "/info",method = RequestMethod.GET) @RequiresAuthentication public String test(){ return "恭喜你,拿到了参数信息"; } /** * 2.1.当前接口需要经过权限校验(需包含 角色的查询 或 菜单的查询) * @return */ @RequestMapping(value = "/info",method = RequestMethod.GET) @RequiresPermissions(value={"role:search","menu":"search"},logical=Logical.OR) public String test(){ return "恭喜你,拿到了参数信息"; } /** * 2.2.当前接口需要经过权限校验(需包含 角色的查询 与 菜单的查询) * @return */ @RequestMapping(value = "/info",method = RequestMethod.GET) @RequiresPermissions(value={"role:search","menu":"search"},logical=Logical.OR) public String test(){ return "恭喜你,拿到了参数信息"; } /** * 3.1.当前接口需要经过角色校验(需包含admin的角色) * @return */ @RequestMapping(value = "/info",method = RequestMethod.GET) @RequiresRoles(value={"admin"}) public String test(){ return "恭喜你,拿到了参数信息"; } /** * 3.2.当前接口需要经过角色与权限的校验(需包含admin的角色,以及角色的查询 或 菜单的查询) * @return */ @RequestMapping(value = "/info",method = RequestMethod.GET) @RequiresRoles(value={"admin"}) @RequiresPermissions(value={"role:search","menu":"search"},logical=Logical.OR) public String test(){ return "恭喜你,拿到了参数信息"; }
Dans notre processus d'utilisation réel, nous n'avons en fait besoin d'utiliser que @RequiresPermissions et @RequiresAuthentication. Cette annotation est suffisante à la fin de la section précédente, nous avons pris La combinaison des modules métier. et les opérations visant à découpler la relation entre les pages et les interfaces API sont exactement les mêmes que l'approche d'Apache Shiro. Mais nous essayons de ne pas utiliser @RequiresRoles autant que possible, car il y a trop de combinaisons de rôles et le nom du rôle ne peut pas être représenté de manière unique dans l'interface (il est difficile de spécifier que l'interface appartient à un certain rôle, mais vous peut certainement savoir que l'interface appartient à certains modules métier) Quelques opérations. )
Passons maintenant en revue l'ensemble du processus de fonctionnement.
Mais avoir ces 5 annotations dans shiro n'est certainement pas suffisant. Dans le processus d'utilisation réel, en fonction des besoins, nous ajouterons notre propre logique métier unique à l'authentification des autorisations. Pour plus de commodité, nous pouvons utiliser des annotations personnalisées. Cette méthode n'est pas seulement applicable à Apache Shiro, mais aussi à de nombreux autres frameworks tels que : Hibernate Validator, SpringMVC, et même nous pouvons écrire un système de vérification pour vérifier les autorisations dans aop, ce qui ne pose aucun problème. Par conséquent, les annotations personnalisées ont un large éventail d’utilisations. Mais ici, je n'implémente que des annotations personnalisées adaptées basées sur shiro.
Définir la classe de traitement des annotations
/** * 用于认证的接口的注解,组合形式默认是“或”的关系 */ @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface Auth { /** * 业务模块 * @return */ String[] module(); /** * 操作类型 */ String[] action(); }
Définir la classe de traitement des annotations
/** * Auth注解的操作类 */ public class AuthHandler extends AuthorizingAnnotationHandler { public AuthHandler() { //写入注解 super(Auth.class); } @Override public void assertAuthorized(Annotation a) throws AuthorizationException { if (a instanceof Auth) { Auth annotation = (Auth) a; String[] module = annotation.module(); String[] action = annotation.action(); //1.获取当前主题 Subject subject = this.getSubject(); //2.验证是否包含当前接口的权限有一个通过则通过 boolean hasAtLeastOnePermission = false; for(String m:module){ for(String ac:action){ //使用hutool的字符串工具类 String permission = StrFormatter.format("{}:{}",m,ac); if(subject.isPermitted(permission)){ hasAtLeastOnePermission=true; break; } } } if(!hasAtLeastOnePermission){ throw new AuthorizationException("没有访问此接口的权限"); } } } }
Définir la classe de traitement d'interception de Shiro
/** * 拦截器 */ public class AuthMethodInterceptor extends AuthorizingAnnotationMethodInterceptor { public AuthMethodInterceptor() { super(new AuthHandler()); } public AuthMethodInterceptor(AnnotationResolver resolver) { super(new AuthHandler(), resolver); } @Override public void assertAuthorized(MethodInvocation mi) throws AuthorizationException { // 验证权限 try { ((AuthHandler) this.getHandler()).assertAuthorized(getAnnotation(mi)); } catch (AuthorizationException ae) { if (ae.getCause() == null) { ae.initCause(new AuthorizationException("当前的方法没有通过鉴权: " + mi.getMethod())); } throw ae; } } }
Définir la classe d'aspect AOP de Shiro
/** * shiro的aop切面 */ public class AuthAopInterceptor extends AopAllianceAnnotationsAuthorizingMethodInterceptor { public AuthAopInterceptor() { super(); // 添加自定义的注解拦截器 this.methodInterceptors.add(new AuthMethodInterceptor(new SpringAnnotationResolver())); } }
Définir la classe de démarrage d'annotation personnalisée de Shiro
/** * 启动自定义注解 */ public class ShiroAdvisor extends AuthorizationAttributeSourceAdvisor { public ShiroAdvisor() { // 这里可以添加多个 setAdvice(new AuthAopInterceptor()); } @SuppressWarnings({"unchecked"}) @Override public boolean matches(Method method, Class targetClass) { Method m = method; if (targetClass != null) { try { m = targetClass.getMethod(m.getName(), m.getParameterTypes()); return this.isFrameAnnotation(m); } catch (NoSuchMethodException ignored) { } } return super.matches(method, targetClass); } private boolean isFrameAnnotation(Method method) { return null != AnnotationUtils.findAnnotation(method, Auth.class); } }
Séquence globale d'idées : définir une classe d'annotation (Définir variables pouvant être utilisées par l'entreprise) -> Définir classe de traitement d'annotations (effectuer le traitement de la logique métier via des variables dans les annotations) -> Définir intercepteur d'annotations -> ;Définir Classe d'aspect d'aop->Définissez enfin la classe d'activation d'annotations personnalisée de Shiro. Les idées d'écriture pour d'autres annotations personnalisées sont similaires à celle-ci.
Recommandations associées :
À propos des annotations personnalisées en Java Détaillées introduction
Explication détaillée de la mise en œuvre de l'autorisation shiro
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!