Как проверить безопасность (@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.

Поток это:

  1. FilterSecurityInterceptor <== где .mvcMatchers(...).hasRole(...)
  2. Тогда HandlerInterceptor s
  3. Тогда проверка аргумента
  4. Тогда 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 будет по-прежнему участвовать в авторизованных запросах, которые якобы составляют большинство.