资讯专栏INFORMATION COLUMN

基于shiro的自定义注解的扩展

YuboonaZhang / 601人阅读

摘要:的自身注解的用法。所以自定义注解的作用很广。但是在这里,我仅仅基于的来实现适用于它的自定义注解。其他的自定义的注解的编写思路和这个也是类似的。

基于shiro的自定义注解的扩展

根据我的上一篇文章,权限设计的杂谈中,涉及到了有关于前后端分离中,页面和api接口断开表与表层面的关联,另辟蹊径从其他角度找到方式进行关联。这里我们主要采取了shiro的自定义注解的方案。本篇文章主要解决以下的问题。

如何通过逻辑进行页面与api接口的关联。

shiro的自身注解的用法。

如何编写自定义注解。

如何通过逻辑进行页面与api接口的关联

在表与表的结构关系中,页面和接口表最终都是与权限表进行的关联(详情请查看我的上一篇文章《权限设计的杂谈》)。

我们现在希望用另一种方案去替代他,实现一个低成本同时兼顾一定程度的权限控制。这里我们引入两个概念。业务模块操作类型

业务模块

概念:将系统中的业务模块抽象成一种数据,我们可以用字符串的形式去表示,例如:角色管理对应是role-manage、用户管理对应是user-manage等等。我们将系统中所存在的业务模块通过“最小特权原则”进行划分,最终形成一批可分配的数据。

使用原则:api接口和页面以及功能从本质上来说,都和业务模块有逻辑关系,于是,我们可以对api接口与页面(以及功能点)进行逻辑匹配,来判断页面与接口的关系。

操作类型

概念:将系统中的所有的操作类型抽象成一种数据,我们也可以用字符串的形式去表示,例如:新增对应的是add、分配对应的是allot等等。我们将系统中所有的操作类型根据业务模块通过“数据许可证”进行划分,最终形成一批可分配的数据。

使用原则:页面是展示,功能点是动作,而接口是最终动作的资源提供,通过“业务模块”确定了调取的资源,通过“操作类型”确定了资源的使用方式。通过两者可以大致无误的判断页面的功能点触发的接口是否在鉴权之内。

现在提出了这两个概念,他们最终的实际的使用方式是什么,我们先从以下几个角度去思考一下。

数据库中的页面表或的api接口表中的数据就是真实有效吗?

页面或接口的实际使用,是以功能存在为前提,还是以数据库表中的数据存在为前提。

权限结构中,“控制对象”的存储只有数据库这一种途径吗?

我们从结论出发来看这几个问题,首先“控制对象”的存储除了在数据库中也可以代码中,也可以在配置文件中,并不一定非得在数据库;那么接着回答第二个问题,当数据库存在的接口信息,而服务端并没有开发这个接口的时候,数据库的信本身就有问题,亦或者,数据库里新增的接口必定是服务端上已经部署的接口才能生效;接着就是第一个问题,那么数据库中关于“控制对象”的表中的数据并不一定是真实有效的。所以我们可以得出以下的解决方案

我们可以在接口上用注解的形式补充“业务模块”和“操作类型”的数据信息,这两类信息都可以存于常量类中,

在数据库添加创建页面表结构和页面功能表结构的时候,添加“业务模块”和“操作类型”字段。

可以将“业务模块”和“操作类型”的信息存于数据库的字典表中。

模块的新增或操作的新增,必定带来了接口的新增,那么就会带来一次系统部署活动,这个运维成本是无法减少的,并不能通过表结构来减少。

但是这种方案仅适用于非强控制接口型的项目,在强控制型的接口项目仍然要将页面与接口进行绑定,虽然这会带来巨大的运维成本。另外也可以通过接口路由规则进行划分,例如:/api/page/xxxx/(仅对页面使用),/api/mobile/xxxxx(仅对移动端使用)将仅供页面使用的接口进行分类,这类接口仅做认证不做授权,也可以达到目的。
shiro的自身注解的用法

通过一个理论上的思路认可之后,剩下的则是付诸技术上的实践,我们这边采用的是Apache Shiro的安全框架,在Spring Boot的环境下应用。简要说明以下几个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 "恭喜你,拿到了参数信息";
    }
    
    

在我们的实际使用过程中,实际上只需要使用@RequiresPermissions和@RequiresAuthentication就可以了这一个注解就可以了,在上一小节的结尾,我们采取了业务模块与操作的结合方案来解耦页面和api接口的关系,和apache Shiro的这种方式正好一致。但是@RequiresRoles这个我们尽可能不采用,因为角色的组合形式太多,角色名没有办法在接口中具象唯一化(很难指定接口归某个角色调用,但是一定能知道接口归属于某些业务模块的某些操作。)

现在我们来回顾一下整个运转的流程。

如何编写自定义注解

但是仅仅是拥有shiro中的这5个注解肯定是不够使用的。在实际的使用过程中,根据需求,我们会在权限认证中加入我们自己特有的业务逻辑的,我们为了便捷则可以采用自定义注解的方式进行使用。这种方法不仅仅适用于Apache Shiro,很多其他的框架如:Hibernate Validator、SpringMVC、甚至我们可以写一套校验体系,在aop中去验证权限,这都是没问题的。所以自定义注解的作用很广。但是在这里,我仅仅基于shiro的来实现适用于它的自定义注解。

定义注解类

/**
 * 用于认证的接口的注解,组合形式默认是“或”的关系
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Auth {
    /**
     * 业务模块
     * @return
     */
    String[] module();
    /**
     * 操作类型
     */
    String[] action();

}

定义注解的处理类

/**
 * 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("没有访问此接口的权限");
            }

        }
    }
}

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

定义shiro的aop切面类

/**
 * shiro的aop切面
 */
public class AuthAopInterceptor extends AopAllianceAnnotationsAuthorizingMethodInterceptor {
    public AuthAopInterceptor() {
        super();
        // 添加自定义的注解拦截器
        this.methodInterceptors.add(new AuthMethodInterceptor(new SpringAnnotationResolver()));
    }
}

定义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);
    }
}
总体的思路顺序:定义注解类(定义业务可使用的变量)->定义注解处理类(通过注解中的变量做业务逻辑处理)->定义注解的拦截器->定义aop的切面类->最后定义shiro的自定义注解启用类。其他的自定义的注解的编写思路和这个也是类似的。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/76662.html

相关文章

  • Shiro入门这篇就够了【Shiro基础知识、回顾URL拦截】

    摘要:细粒度权限管理就是数据级别的权限管理。张三只能查看行政部的用户信息,李四只能查看开发部门的用户信息。比如通过的拦截器实现授权。 前言 本文主要讲解的知识点有以下: 权限管理的基础知识 模型 粗粒度和细粒度的概念 回顾URL拦截的实现 Shiro的介绍与简单入门 一、Shiro基础知识 在学习Shiro这个框架之前,首先我们要先了解Shiro需要的基础知识:权限管理 1.1什...

    chenjiang3 评论0 收藏0
  • 基于Shiro,JWT实现微信小程序登录完整例子

    摘要:小程序官方流程图如下,官方地址如果此图理解不清楚的地方也可参看我的博客本文是对接微信小程序自定义登录的一个完整例子实现,技术栈为。调用微信接口获取和根据和自定义登陆态返回自定义登陆态给小程序端。 小程序官方流程图如下,官方地址 : https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login....

    cfanr 评论0 收藏0
  • Shiro统一认证授权

    摘要:的统一认证授权是下面的一个简单,易用的权限框架,对于单体应用来讲,完全能够极好的,快速的满足权限的需求,所以一般在做项目的时候,都会成为开发者的首选。 Shiro的统一认证授权 Shiro是Apache下面的一个简单,易用的Java权限框架,对于单体应用来讲,Shiro完全能够极好的,快速的满足权限的需求,所以一般在做项目的时候,Shiro都会成为开发者的首选。 可是,如果你需要做第二...

    cocopeak 评论0 收藏0
  • 不用 Spring Security 可否?试试这个小而美安全框架

    摘要:写在前面在一款应用的整个生命周期,我们都会谈及该应用的数据安全问题。用户的合法性与数据的可见性是数据安全中非常重要的一部分。 写在前面 在一款应用的整个生命周期,我们都会谈及该应用的数据安全问题。用户的合法性与数据的可见性是数据安全中非常重要的一部分。但是,一方面,不同的应用对于数据的合法性和可见性要求的维度与粒度都有所区别;另一方面,以当前微服务、多服务的架构方式,如何共享Sessi...

    toddmark 评论0 收藏0
  • 《 Kotlin + Spring Boot : 下一代 Java 服务端开发 》

    摘要:下一代服务端开发下一代服务端开发第部门快速开始第章快速开始环境准备,,快速上手实现一个第章企业级服务开发从到语言的缺点发展历程的缺点为什么是产生的背景解决了哪些问题为什么是的发展历程容器的配置地狱是什么从到下一代企业级服务开发在移动开发领域 《 Kotlin + Spring Boot : 下一代 Java 服务端开发 》 Kotlin + Spring Boot : 下一代 Java...

    springDevBird 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<