Практически каждое веб-приложение должно иметь несколько пользователей, и у каждого пользователя есть некоторые данные — посты, документы, сообщения, что угодно. И самое очевидное, что нужно сделать, это защитить эти объекты от получения пользователями, которые не являются законными владельцами этих ресурсов.
К сожалению, это не самая простая вещь. Я не имею в виду, что это сложно, просто это не так интуитивно понятно, как простой возврат ресурсов. Когда вы являетесь конечной точкой /record/{recordId} , запрос к базе данных для recordId — это то, что вы делаете немедленно. Только тогда возникает проблема проверки, принадлежит ли эта запись текущему пользователю, прошедшему проверку подлинности.
Фреймворки здесь не помогают, потому что логика управления доступом и владения зависит от конкретного домена. Там нет очевидного общего способа определения владельца. Это зависит от модели объекта и отношений между объектами. В некоторых случаях это может быть довольно сложно, включая поиск в таблице соединений (для отношений «многие ко многим»).
Но вы должны автоматизировать это по двум причинам. Во-первых, ручное выполнение этих проверок для каждого метода конечной точки / контроллера утомительно и делает код ужасным. Во-вторых, легче забыть добавить эти проверки, особенно если есть новые разработчики.
Вы можете выполнять эти проверки в нескольких местах, вплоть до DAO, но в целом вы должны потерпеть неудачу как можно раньше, поэтому эти проверки должны выполняться на уровне контроллера (обработчика конечной точки). В случае Java и Spring вы можете использовать аннотации и HandlerInterceptor для автоматизации этого. В случае любого другого языка или структуры, существуют похожие подходы — какой-то подключаемый способ описания отношений собственности, которые необходимо проверить.
Ниже приведен пример аннотации для каждого метода контроллера:
|
1
2
3
4
|
public @interface VerifyEntityOwnership { String entityIdParam() default "id"; Class<?> entityType();} |
Затем вы определяете перехватчик (который, конечно, должен быть настроен для выполнения)
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
|
@Componentpublic class VerifyEntityOwnershipInterceptor extends HandlerInterceptorAdapter { private static final Logger logger = LoggerFactory.getLogger(VerifyEntityOwnershipInterceptor.class); @Autowired private OrganizationService organizationService; @Autowired private MessageService MessageService; @Autowired private UserService userService; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); // assuming spring-security with a custom authentication token type if (authentication instanceof ApiAuthenticationToken) { AuthenticationData authenticationData = ((ApiAuthenticationToken) authentication).getAuthenticationData(); UUID clientId = authenticationData.getClientId(); HandlerMethod handlerMethod = (HandlerMethod) handler; VerifyEntityOwnership annotation = handlerMethod.getMethodAnnotation(VerifyEntityOwnership.class); if (annotation == null) { logger.warn("No VerifyEntityOwnership annotation found on method {}", handlerMethod.getMethod().getName()); return true; } String entityId = getParam(request, annotation.entityIdParam()); if (entityId != null) { if (annotation.entityType() == User.class) { User user = userService.get(entityId); if (!user.getClientId().equals(clientId)) { return false; } } else if (annotation.entityType() == Message.class) { Message record = messageService.get(entityId); if (!message.getClientId().equals(clientId) { return false; } } // .... more } } return true; } @SuppressWarnings("unchecked") private String getParam(HttpServletRequest request, String paramName) { String value = request.getParameter(paramName); if (value != null) { return value; } Map<String, String> pathVariables = (Map<String, String>) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE); return pathVariables.get(paramName); }} |
Вы видите, что это предполагает необходимость настраиваемой логики для каждого типа. Если ваша модель проста, вы можете сделать это универсальным — заставить все ваши сущности реализовывать некоторый Owned интерфейс с getClientId() который все они определяют. Тогда просто имейте dao.get(id, entityClass); и избегайте логики, специфичной для сущности.
Обратите внимание на предупреждение, которое выводится на печать, когда в методе нет аннотации — это означает, что вы могли забыть добавить его. Некоторые конечные точки могут не требовать проверки прав собственности — для них вы можете иметь специальную аннотацию @IgnoreEntityOwnership . Смысл в том, чтобы принять сознательное решение не проверять право собственности, а не забывать об этом и представлять проблему безопасности.
То, что я говорю, может быть очевидным. Но я видел много примеров этого упущения, включая правительственные проекты производства. И, как я сказал, фреймворки не заставляют вас рассматривать этот аспект, потому что они не могут сделать это в общем виде — веб-фреймворки обычно не имеют отношения к вашей модели сущностей, а ваш ORM не имеет отношения к вашим контроллерам. Существуют всеобъемлющие структуры, которые обрабатывают все эти аспекты, но даже у них нет общих механизмов для этого (по крайней мере, я не знаю).
Безопасность включает в себя применение ряда передовых методов и принципов к системе. Но это также включает процедуры и автоматизацию, которые помогают разработчикам и администраторам не пропускать то, о чем они обычно знают, но время от времени забывают. И чем менее утомительным является принцип безопасности, тем более вероятно, что он будет последовательно применяться.
| Опубликовано на Java Code Geeks с разрешения Божидара Божанова, партнера нашей программы JCG . См. Оригинальную статью здесь: Автоматизация контроля доступа для пользовательских объектов
Мнения, высказанные участниками Java Code Geeks, являются их собственными. |