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

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

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

  1. 如何经过逻辑进行页面与api接口的关联。
  2. shiro的自身注解的用法。
  3. 如何编写自定义注解。

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

在表与表的结构关系中,页面和接口表最终都是与权限表进行的关联(详情请查看个人上一篇文章《权限设计的杂谈》)。
权限实体图
咱们如今但愿用另外一种方案去替代他,实现一个低成本同时兼顾必定程度的权限控制。这里咱们引入两个概念。业务模块操做类型数据库

  • 业务模块apache

    • 概念:将系统中的业务模块抽象成一种数据,咱们能够用字符串的形式去表示,例如:角色管理对应是role-manage、用户管理对应是user-manage等等。咱们将系统中所存在的业务模块经过“最小特权原则”进行划分,最终造成一批可分配的数据。
    • 使用原则:api接口和页面以及功能从本质上来讲,都和业务模块有逻辑关系,因而,咱们能够对api接口与页面(以及功能点)进行逻辑匹配,来判断页面与接口的关系。
  • 操做类型segmentfault

    • 概念:将系统中的全部的操做类型抽象成一种数据,咱们也能够用字符串的形式去表示,例如:新增对应的是add、分配对应的是allot等等。咱们将系统中全部的操做类型根据业务模块经过“数据许可证”进行划分,最终造成一批可分配的数据。
    • 使用原则:页面是展现,功能点是动做,而接口是最终动做的资源提供,经过“业务模块”肯定了调取的资源,经过“操做类型”肯定了资源的使用方式。经过二者能够大体无误的判断页面的功能点触发的接口是否在鉴权以内。

如今提出了这两个概念,他们最终的实际的使用方式是什么,咱们先从如下几个角度去思考一下。后端

  1. 数据库中的页面表或的api接口表中的数据就是真实有效吗?
  2. 页面或接口的实际使用,是以功能存在为前提,仍是以数据库表中的数据存在为前提。
  3. 权限结构中,“控制对象”的存储只有数据库这一种途径吗?

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

  1. 咱们能够在接口上用注解的形式补充“业务模块”和“操做类型”的数据信息,这两类信息均可以存于常量类中,
  2. 在数据库添加建立页面表结构和页面功能表结构的时候,添加“业务模块”和“操做类型”字段。
  3. 能够将“业务模块”和“操做类型”的信息存于数据库的字典表中。
  4. 模块的新增或操做的新增,一定带来了接口的新增,那么就会带来一次系统部署活动,这个运维成本是没法减小的,并不能经过表结构来减小。

业务模块与页面和接口的关系

可是这种方案仅适用于非强控制接口型的项目,在强控制型的接口项目仍然要将页面与接口进行绑定,虽然这会带来巨大的运维成本。另外也能够经过接口路由规则进行划分,例如:/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这个咱们尽量不采用,由于角色的组合形式太多,角色名没有办法在接口中具象惟一化(很难指定接口归某个角色调用,可是必定能知道接口归属于某些业务模块的某些操做。)app

如今咱们来回顾一下整个运转的流程。框架

shiro权限的验证流程

如何编写自定义注解

可是仅仅是拥有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的自定义注解启用类。其余的自定义的注解的编写思路和这个也是相似的。
相关文章
相关标签/搜索