Spring mvc 带有@Preauthorize和@ControllerAdvice的自定义错误消息
我们正在使用spring和spring-security-3.2。最近,我们在RestAPIs中添加了注解@PreAuthorize(之前它是基于URL的) 我们已经有了用-@ControllerAdvice注释的全局异常处理程序和定制的PermissionEvaluator,除了错误消息之外,一切正常 假设某个用户在没有“ViewSalesOrder”权限的情况下访问API,那么spring在默认情况下抛出异常“Access is denied”,但没有指出缺少哪个权限(我们的要求是提及缺少哪个权限) 是否可能引发也包含权限名称的异常,因此最终的错误消息应该是“访问被拒绝,您需要ViewSalesOrder权限”(此处权限名称应该来自@PreAuthorize annotation)Spring mvc 带有@Preauthorize和@ControllerAdvice的自定义错误消息,spring-mvc,spring-security,annotations,Spring Mvc,Spring Security,Annotations,我们正在使用spring和spring-security-3.2。最近,我们在RestAPIs中添加了注解@PreAuthorize(之前它是基于URL的) 我们已经有了用-@ControllerAdvice注释的全局异常处理程序和定制的PermissionEvaluator,除了错误消息之外,一切正常 假设某个用户在没有“ViewSalesOrder”权限的情况下访问API,那么spring在默认情况下抛出异常“Access is denied”,但没有指出缺少哪个权限(我们的要求是提及缺少哪
请注意,我们已经准备了100个这样的restAPI,因此我们非常感谢通用解决方案。由于
PermissionEvaluator
界面不允许您在传递缺少的权限的同时传递评估结果,因此无法实现您的期望。此外,
AccessDecisionManager
决定与AccessDecisionVoter
实例投票相关的最终授权,其中一个实例是PreInvocationAuthorizationAdviceVoter
,它就@PreAuthorize
值的评估进行投票。长话短说,
PreInvocationAuthorizationAdviceVoter
在您的自定义PermissionEvaluator
返回false
到hasPermission
调用时对请求投反对票(给予请求-1分)。如您所见,无法传播此流中失败的原因
另一方面,您可以尝试一些变通方法来实现所需的功能。一种方法是在权限检查失败时,在自定义的
PermissionEvaluator
中引发异常。您可以使用此异常将缺少的权限传播到全局异常处理程序。在这里,您可以将缺少的权限作为参数传递给消息描述符。请注意,这将停止执行AccessDecisionManager
的过程,这意味着不会执行后续投票者(默认值为和)。如果你选择走这条路,你应该小心。另一种更安全但更笨拙的方法是在响应403之前实现自定义并自定义错误消息
AccessDeniedHandler
提供当前的HttpServletRequest
,可用于检索请求URI。但是,在这种情况下,坏消息是,您需要URI到权限的映射才能找到丢失的权限。我已经实现了Mert Z提到的第二种可能的解决方案。我的解决方案仅适用于API层中使用的@PreAuthorize注释(例如使用@RequestMapping)。我已经注册了一个自定义AccessDeniedHandler bean,在该bean中我获得了禁止API方法的@PreAuthorize注释的值,并将其填入错误消息中
公共类CustomAccessDeniedHandler实现AccessDeniedHandler{
私人调度员小调度员小调度员;
公共无效句柄(HttpServletRequest请求、HttpServletResponse响应、,
AccessDeniedException(AccessDeniedException)引发IOException,
ServletException{
如果(!response.isCommitted()){
List handlerMappings=dispatcherServlet.getHandlerMappings();
if(handlerMappings!=null){
HandlerExecutionChain处理程序=null;
用于(HandlerMapping HandlerMapping:handlerMappings){
试一试{
handler=handlerMapping.getHandler(请求);
}捕获(例外e){}
if(处理程序!=null)
打破
}
if(handler!=null&&handler.getHandler()HandlerMethod实例){
HandlerMethod=(HandlerMethod)handler.getHandler();
预授权methodAnnotation=method.getMethodAnnotation(预授权.class);
if(methodAnnotation!=null){
response.sendError(HttpStatus.FORBIDDEN.value(),
未满足授权条件:“+methodAnnotation.value());
返回;
}
}
}
response.sendError(HttpStatus.FORBIDDEN.value(),
HttpStatus.FORBIDDEN.GetReasonPhase());
}
}
@注入
公共无效setDispatcherServlet(DispatcherServlet DispatcherServlet){
this.dispatcherServlet=dispatcherServlet;
}
}
处理程序已在WebSecurity配置适配器中注册:
@EnableGlobalMethodSecurity(jsr250Enabled=true,preprestenabled=true)
@启用Web安全性
公共抽象类BaseSecurityInitializer扩展了WebSecurity配置适配器{
@凌驾
受保护的无效配置(HttpSecurity http)引发异常{
...
http.exceptionHandling().accessDeniedHandler(accessDeniedHandler());
...
}
@豆子
公共AccessDeniedHandler AccessDeniedHandler(){
返回新的CustomAccessDeniedHandler();
}
}
请注意,如果还有带有@ControllerAdvice的全局资源异常处理程序,则不会执行CustomAccessDeniedHandler。我通过在全局处理程序中重新调用异常(如此处所建议的)来解决此问题:
@ControllerAdvice
公共类ResourceExceptionHandler{
@ExceptionHandler(AccessDeniedException.class)
公共响应Entity accessDeniedException(accessDeniedException e)抛出accessDeniedException{
log.info(例如toString());
投掷e;
}
}
您采用了什么解决方案?我还是不能忍受
@PreAuthorize("hasPermission('salesorder','ViewSalesOrder')")
@RequestMapping(value = "/restapi/salesorders/", method = RequestMethod.GET)
public ModelAndView getSalesOrders(){}