【问题标题】:Custom Error message with @Preauthorize and @@ControllerAdvice带有@Preauthorize 和@@ControllerAdvice 的自定义错误消息
【发布时间】:2017-12-17 00:28:45
【问题描述】:

我们使用的是 spring 和 spring-security-3.2。最近我们正在向 RestAPIs 添加注解 @PreAuthorize(之前它是基于 URL 的)。

     @PreAuthorize("hasPermission('salesorder','ViewSalesOrder')")
  @RequestMapping(value = "/restapi/salesorders/", method = RequestMethod.GET)
  public ModelAndView getSalesOrders(){}

我们已经有了带有注释的全局异常处理程序 - @ControllerAdvice 和自定义 PermissionEvaluator,除了错误消息之外一切正常。

假设某个用户正在访问 API 目前没有“ViewSalesOrder”权限,那么 spring 默认会抛出异常“访问被拒绝”,但没有说明缺少哪个权限(我们要求提及缺少哪个权限)。

是否可以抛出包含权限名称的异常,所以最终的错误消息应该类似于“访问被拒绝,您需要 ViewSalesOrder 权限”(这里的权限名称应该来自 @PreAuthorize 注释)?

请注意,我们有 100 个这样的 restAPI,因此我们非常感谢通用解决方案。

【问题讨论】:

  • 您采用了什么解决方案?我仍然找不到设置自定义错误消息的方法

标签: spring-mvc spring-security annotations


【解决方案1】:

我已经实现了 Mert Z 提到的第二种可能的解决方案。我的解决方案仅适用于 API 层中使用的 @PreAuthorize 注释(例如,使用 @RequestMapping)。我已经注册了一个自定义的 AccessDeniedHandler bean,我在其中获取了被禁止的 API 方法的 @PreAuthorize 注释的值并将其填充到错误消息中。

public class CustomAccessDeniedHandler implements AccessDeniedHandler {

    private DispatcherServlet dispatcherServlet;

    public void handle(HttpServletRequest request, HttpServletResponse response,
                       AccessDeniedException accessDeniedException) throws IOException,
            ServletException {
        if (!response.isCommitted()) {
            List<HandlerMapping> handlerMappings = dispatcherServlet.getHandlerMappings();
            if (handlerMappings != null) {
                HandlerExecutionChain handler = null;
                for (HandlerMapping handlerMapping : handlerMappings) {
                    try {
                        handler = handlerMapping.getHandler(request);
                    } catch (Exception e) {}
                    if (handler != null)
                        break;
                }
                if (handler != null && handler.getHandler() instanceof HandlerMethod) {
                    HandlerMethod method = (HandlerMethod) handler.getHandler();
                    PreAuthorize methodAnnotation = method.getMethodAnnotation(PreAuthorize.class);
                    if (methodAnnotation != null) {
                        response.sendError(HttpStatus.FORBIDDEN.value(),
                                "Authorization condition not met: " + methodAnnotation.value());
                        return;
                    }
                }
            }
            response.sendError(HttpStatus.FORBIDDEN.value(),
                    HttpStatus.FORBIDDEN.getReasonPhrase());
        }
    }

    @Inject
    public void setDispatcherServlet(DispatcherServlet dispatcherServlet) {
        this.dispatcherServlet = dispatcherServlet;
    }
}

处理程序在 WebSecurityConfigurerAdapter 中注册:

@EnableGlobalMethodSecurity(jsr250Enabled = true, prePostEnabled = true)
@EnableWebSecurity
public abstract class BaseSecurityInitializer extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        ...
        http.exceptionHandling().accessDeniedHandler(accessDeniedHandler());
        ...
    }

    @Bean
    public AccessDeniedHandler accessDeniedHandler() {
        return new CustomAccessDeniedHandler();
    }
}

请注意,如果还有一个带有 @ControllerAdvice 的全局资源异常处理程序,则不会执行 CustomAccessDeniedHandler。我通过在全局处理程序中重新抛出异常解决了这个问题(这里建议https://github.com/spring-projects/spring-security/issues/6908):

@ControllerAdvice
public class ResourceExceptionHandler {
    @ExceptionHandler(AccessDeniedException.class)
    public ResponseEntity accessDeniedException(AccessDeniedException e) throws AccessDeniedException {
        log.info(e.toString());
        throw e;
    }
}

【讨论】:

    【解决方案2】:

    由于PermissionEvaluator 接口不允许您将缺少的权限与评估结果一起传递,因此没有什么好方法可以实现您的期望。
    此外,AccessDecisionManager 决定对AccessDecisionVoter 实例的投票的最终授权,其中一个是PreInvocationAuthorizationAdviceVoter,它对@PreAuthorize 值的评估进行投票。

    长话短说,当您的自定义 PermissionEvaluatorfalse 返回到 hasPermission 调用时,PreInvocationAuthorizationAdviceVoter 投票反对请求(给予请求 –1 分)。如您所见,无法传播此流程中的失败原因。

    另一方面,您可以尝试一些变通方法来实现您想要的。

    当权限检查失败时,一种方法可以是在您的自定义PermissionEvaluator 中引发异常。您可以使用此异常将缺少的权限传播到您的全局异常处理程序。在那里,您可以将缺少的权限作为参数传递给您的消息描述符。请注意,这将停止AccessDecisionManager 的执行过程,这意味着不会执行连续的投票者(默认为RoleVoterAuthenticatedVoter)。如果你选择走这条路,你应该小心。

    另一种更安全但更笨拙的方法是实现自定义 AccessDeniedHandler 并在响应 403 之前自定义错误消息。AccessDeniedHandler 为您提供当前的HttpServletRequest,可用于检索请求 URI。但是,在这种情况下,坏消息是,您需要一个权限映射的 URI 才能找到丢失的权限。

    【讨论】:

      猜你喜欢
      • 2012-05-08
      • 1970-01-01
      • 2018-01-08
      • 2015-04-10
      • 2011-09-05
      • 2019-12-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多