Как проверить безопасность (@Secured или @PreAuthorize) перед проверкой (@Valid) в моем контроллере?
вот мой код контроллера:
@PreAuthorize("hasRole('CREATE_USER')")
@RequestMapping(method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public UserReturnRO createUser(@Valid @RequestBody UserRO userRO) throws BadParameterException{
return userService.createUser(userRO);
}
Моя потребность в том, что клиент, не имеющий соответствующей роли, пытается создать пользователя, контроллер отвечает "Не разрешен", даже если отправленные данные недействительны. Вместо этого, если клиент (без соответствующей роли) пытается создать пользователя с неправильными данными, мой контроллер отвечает сообщением @Valid (например: "пароль не может быть пустым" ), в то время как я хочу, чтобы он ответил "не авторизованным",
В интерфейсе PreAuthorized
мы можем найти это предложение:
Аннотации для указания выражения для контроля доступа к методу, которое будет оцениваться, чтобы решить, разрешен ли вызов метода или нет.
но кажется, что это не так.
Ответы
Ответ 1
Вы не можете сделать это напрямую, так как @Valid
обрабатывается перед вызовом фактического метода и в результате перед @PreAuthorize
.
Но вместо этого вы можете ввести BindingResult
сразу после вашей модели (userRO) и при этом - взять под контроль процесс проверки. Затем проверьте, есть ли у BindingResult
некоторые ошибки, и если да, то возвратите отрицательный запрос (похоже на то, что делает spring).
Пример:
@ResponseBody
@RequestMapping(method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasRole('CREATE_USER')")
public ResponseEntity<?> createUser(@RequestBody @Valid UserRO userRO, BindingResult result) {
if (result.hasErrors()) {
return ResponseEntity.badRequest().body(result.getAllErrors());
}
return ResponseEntity.ok(userService.createUser(userRO));
}
Ответ 2
Как уже говорилось, Spring Security @PreAuthorize
- это совет метода, который означает, что он не может участвовать, пока метод и его аргументы не будут уже разрешены.
Помимо уже полученного ответа, есть несколько способов переместить авторизацию до разрешения аргумента.
Фильтр безопасности
Во-первых, Spring Security проверяет URL-адреса перед сопоставлением запроса с методом. А поскольку это @Controller
, разумно предположить, что вместо этого можно сопоставить запрос роли на этом уровне вместо @PreAuthorize
:
http
.authorizeRequests()
.mvcMatchers(POST, "/somepath").hasRole("CREATE_USER")
Перехватчик-перехватчик
Во-вторых, Spring MVC поставляется с ограниченной поддержкой для проверки прав доступа перед анализом аргументов метода. Например, вы можете сделать:
@EnableWebMvc
public static class MvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
UserRoleAuthorizationInterceptor userRole =
new UserRoleAuthorizationInterceptor();
userRole.setAuthorizedRoles("CREATE_USER");
registry.addInterceptor(userRole);
}
}
Это гораздо более простой, чем @PreAuthorize
поскольку это глобальная настройка, но я включил ее для полноты.
Перехватчик-перехватчик, часть 2
В-третьих (предупреждение, некоторая неоправданность впереди), вы можете создать свой собственный HandlerInterceptor
.
Поток это:
-
FilterSecurityInterceptor
<== где .mvcMatchers(...).hasRole(...)
- Тогда
HandlerInterceptor
s - Тогда проверка аргумента
- Тогда
MethodSecurityInterceptor
<== где живет @PreAuthorize
Таким образом, ваш HandlerInterceptor
будет проверять до разрешения аргументов. Однако он не должен быть таким же вовлеченным, как MethodSecurityInterceptor
. Это может быть, например, просто:
static class AuthorizationInterceptor extends HandlerInterceptorAdapter {
SecurityMetadataSource securityMetadataSource;
AccessDecisionManager accessDecisionManager;
@Override
public void preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) {
Authentication authenticated = (Authentication) request.getUserPrincipal();
MethodInvocation mi = convert(handler);
Collection<ConfigAttribute> attributes =
this.securityMetadataSource.getAttributes(mi);
// throws AccessDeniedException
this.accessDecisionManager.decide(authenticated, mi, attributes);
return true;
}
}
Затем вы подключаете его вместе с:
@EnableGlobalMethodSecurity(prePostEnabled = true)
static class MethodConfig extends GlobalMethodSecurityConfiguration {
@Bean
HandlerInterceptor preAuthorize() throws Exception {
return new AuthorizationInterceptor(
accessDecisionManager(), methodSecurityMetadataSource());
}
}
@EnableWebMvc
public static class MvcConfig implements WebMvcConfigurer {
@Autowired
AuthorizationInterceptor authorizationInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authorizationInterceptor);
}
}
Это не MethodSecurityInterceptor
потому что MethodSecurityInterceptor
будет по-прежнему участвовать в авторизованных запросах, которые якобы составляют большинство.