如何优雅的使用切面和注解实现权限验证

背景

权限验证在咱们系统中是一个与业务逻辑无关可是又与业务息息相关的一个功能。
设想咱们开发了一款为中小型企业定制的会员系统。这款系统能够为企业A、企业B等多种企业提供服务。数据库中的表结构每每是这样的(如下只是一个demo,实际状况中字段必定会更多、更复杂):java

id memberCardCode userName card_status business
1 a564456578 zhangsan 0 business-a
2 b678688643 lisi 1 businsss-b
3 a775445667 wangwu 0 businsss-a
4 b943578978 zhaoliu 1 businsss-b
5 c657688799 sunqi 1 businsss-c

基于上表,咱们删除id = 1的会员每每是这样操做的(假设是物理删除):
controller层:
sql

@RestController("/member")
public class MemberController {
    @PostMapping("/delete")
    public void deleteById(int id) {
        // 此处省略删除代码
    }
}
复制代码

最终在controller中调用的SQL语句是这样的:数据库

delete from member where id = 1;
复制代码

乍一看,就这样一条简单sql语句能有什么问题呢?其实越是简单的问题,越不能放过。
经过上表咱们看到id = 1的会员信息是属于business-a的。因此理应是business-a的帐号才能删除id = 1的会员信息。那此时若是business-b在删除会员的时候将参数id改成1,此时就会出现business-b删除了business-a的会员。此时business-a的心情是崩溃的。
app

因此权限验证是很是必要。那权限验证怎么作呢?

不太优雅:侵入业务代码的方案

在controller层加入逻辑判断:判断删除的id是否属于当前帐号,若是属于则删除;不然直接返回。代码以下:spa

@RestController("/member")
public class MemberController {
    @PostMapping("/delete")
    public void deleteById(int id) {
        // 第一步:权限验证
        Integer id = selectByIdAndBusiness(id, "business-a");
        if (id == null) {
            // 说明id不属于business-a不能删除
            return;
        }
        // 第二步:调用删除逻辑
    }
}
复制代码

上面的代码确实解决了越权的问题,可是会将一些业务无关的代码侵入到咱们的业务逻辑,这样的实现逻辑不太优雅。那咱们来选择一个更优雅的方式来完成权限验证。code

优雅:注解 + 切面的方案

首先咱们须要明确关注的字段信息:id和business。其中business能够在filter中存入一个ThreadLocal,因此咱们只须要关注字段id便可。
第一步:建立自定义注解(做用于方法)cdn

@Documented  
@Retention(RetentionPolicy.RUNTIME)  
@Target(ElementType.METHOD)  
public @interface Auth {
    // 方法的参数名称,以防参数名称不是id,因此提供paramName
    String paramName() default "id";
}
复制代码

第二步:在方法上使用注解blog

package com.demo.controller;
@RestController("/member")
public class MemberController {
    @PostMapping("/delete")
    @Auth(paramId = "deleteId")
    public void deleteById(int deleteId) {
        // 调用删除逻辑
    }
}
复制代码

第三步:实现切面开发

// 经过注解能够看到,咱们该方法切的是controller层带有Auth注解的方法
@Before(value = "execution(public * com.demo.controller..*.*(..))"
      + " && @annotation(auth)", argNames = "pjp, auth")
public void before4Auth(JoinPoint pjp, Auth auth) {
    // 一、经过ThreadLocal获取business
    String business = context.get();
    // 二、经过注解解析id
    // 2.1 获取参数值
    Object[] args = pjp.getArgs();
    // 2.2 获取参数名
    MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
    String[] parameterNames = methodSignature.getParameterNames();
    // 2.3 获取注解中paramName的下标
    int index = ArrayUtils.indexOf(parameterNames, auth.paramName());
    // 2.4 根据下标获取id对应的值
    int val = (int) args[index];
    // 2.5 鉴权逻辑
    Integer id = selectByIdAndBusiness(val, business);
    if (id == null) {
        // 抛异常提示越权
    }
    // 不然的正常执行下面的业务逻辑
}
复制代码

经过这种方式,咱们只需在controller层的方法加上@Auth注解便可。没有非业务代码的侵入,实现方式可算优雅。
若是您有更优雅的解决方案,欢迎提供思路。get

相关文章
相关标签/搜索