Как элегантно использовать аспекты и аннотации для реализации проверки авторизации

Java

задний план

Проверка разрешений в нашей системе — это функция, не имеющая ничего общего с бизнес-логикой, но тесно связанная с бизнесом.
Представьте, что мы разрабатываем систему членства, адаптированную для малого и среднего бизнеса. Эта система может предоставлять услуги для предприятия A, предприятия B и других предприятий. Структура таблицы в базе данных часто бывает такой (следующее — просто демонстрация, на практике поля должны быть все более и более сложными):

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 следующим образом (при условии физического удаления):
уровень контроллера:

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

Последний оператор SQL, вызываемый в контроллере, выглядит следующим образом:

delete from member where id = 1;

На первый взгляд, что может быть не так в таком простом операторе SQL? На самом деле, чем проще проблема, тем труднее ее отпустить.
Из приведенной выше таблицы видно, что информация об участнике с id = 1 принадлежит компании business-a. Следовательно, необходимо удалить информацию об участнике с id = 1 с учетной записи business-a. В это время, если бизнес-б изменит идентификатор параметра на 1 при удалении участника, будет казаться, что бизнес-б удаляет участника бизнес-а. В это время настроение бизнес-а рухнуло.

Так что проверка разрешения очень необходима. Как насчет проверки разрешения?

Менее элегантно: решение для взлома бизнес-кода

Добавьте логическое суждение на уровень контроллера: определите, принадлежит ли удаленный идентификатор текущей учетной записи, если да, удалите его, в противном случае вернитесь напрямую. код показывает, как показано ниже:

@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;
        }
        // 第二步:调用删除逻辑
    }
}

Приведенный выше код действительно решает проблему несанкционированного доступа, но он вторгнется в нашу бизнес-логику, не относящуюся к бизнесу, что не очень элегантно. Тогда давайте выберем более элегантный способ завершения проверки разрешений.

Элегантность: аннотация + решение аспекта

В первую очередь нам нужно четко ориентироваться на поля информации: id и business. Среди них бизнес может хранить ThreadLocal в фильтре, поэтому нам нужно обратить внимание только на идентификатор поля.
Шаг 1: Создайте пользовательскую аннотацию (к методу)

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

Шаг 2. Используйте аннотации к методам

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

Шаг 3: Реализуйте Аспекты

// 通过注解可以看到,我们该方法切的是controller层带有Auth注解的方法
@Before(value = "execution(public * com.demo.controller..*.*(..))"
      + " && @annotation(auth)", argNames = "pjp, auth")
public void before4Auth(JoinPoint pjp, Auth auth) {
    // 1、通过ThreadLocal获取business
    String business = context.get();
    // 2、通过注解解析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) {
        // 抛异常提示越权
    }
    // 否则的正常执行下面的业务逻辑
}

Таким образом, нам нужно только добавить метод на уровень контроллера.@AuthПросто аннотируйте. Без вторжения кода, не связанного с бизнесом, реализация может считаться элегантной.
Если у вас есть более элегантное решение, идеи приветствуются.