задний план
Проверка разрешений в нашей системе — это функция, не имеющая ничего общего с бизнес-логикой, но тесно связанная с бизнесом.
Представьте, что мы разрабатываем систему членства, адаптированную для малого и среднего бизнеса. Эта система может предоставлять услуги для предприятия 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
Просто аннотируйте. Без вторжения кода, не связанного с бизнесом, реализация может считаться элегантной.
Если у вас есть более элегантное решение, идеи приветствуются.