我们正在使用spring和spring-security-3.2。最近我们正在向RestAPI添加注解@PreAuthorize(以前是基于URL的)。
@PreAuthorize("hasPermission('salesorder','ViewSalesOrder')")
@RequestMapping(value = "/restapi/salesorders/", method = RequestMethod.GET)
public ModelAndView getSalesOrders(){}
我们已经有了全局异常处理程序,它用- @ControllerAdvice和自定义PermissionEvaluator进行了注解,除了错误消息外,一切都正常。
假设某个用户在没有"ViewSalesOrder“权限的情况下访问API,那么默认情况下,Spring会抛出异常”Access is denied“,但没有说明缺少哪个权限(我们需要说明缺少哪个权限)。
是否可能引发一个异常,其中还包括权限名称,因此最终错误消息应该看起来像“访问被拒绝,您需要ViewSalesOrder权限”(此处权限名称应该来自@PreAuthorize注解)?
请注意,我们有100个这样的restAPI,所以通用的解决方案将高度赞赏。
4条答案
按热度按时间66bbxpm51#
由于
PermissionEvaluator
接口不允许将缺少的权限与求值结果沿着传递,因此没有一种很好的方法可以达到您的期望。此外,
AccessDecisionManager
决定关于AccessDecisionVoter
示例的投票的最终授权,其中一个示例是PreInvocationAuthorizationAdviceVoter
,其关于@PreAuthorize
值的评估进行投票。长话短说,当您的自定义
PermissionEvaluator
返回false
到hasPermission
调用时,PreInvocationAuthorizationAdviceVoter
投票反对请求(给请求-1分)。另一方面,您可以尝试一些变通方法来实现您想要的结果。
一种方法是在权限检查失败时在自定义的
PermissionEvaluator
中抛出一个异常。您可以使用此异常将缺少的权限传播到全局异常处理程序中。您可以将缺少的权限作为参数传递给消息描述符。请注意,这将暂停AccessDecisionManager
的执行进程,这意味着将不会执行后续的投票器(默认值为RoleVoter和AuthenticatedVoter)。如果选择此路径,则应小心。另一种更安全但更笨拙的方法是实现一个自定义的AccessDeniedHandler,并在响应403之前自定义错误消息。
AccessDeniedHandler
提供了当前的HttpServletRequest
,可用于检索请求URI。然而,在这种情况下,坏消息是,您需要一个URI到权限的Map,以便找到丢失的权限。kyvafyod2#
我已经实现了Mert Z提到的第二种可能的解决方案。我的解决方案只适用于API层中使用的@PreAuthorize注解(例如,使用@RequestMapping)。我已经注册了一个自定义AccessDeniedHandler bean,在其中我获取了被禁止的API方法的@PreAuthorize注解的值,并将其填充到错误消息中。
该处理程序在WebSecurityConfigurerAdapter中注册:
请注意,如果还有一个全局资源异常处理程序,其中包含@ControllerAdvice,那么CustomAccessDeniedHandler将不会被执行。我通过在全局处理程序中重新抛出异常来解决这个问题(如这里所建议的https://github.com/spring-projects/spring-security/issues/6908):
uurv41yg3#
在EL-Expression内部调用的方法可能会抛出 org.springframework.security.access.AccessDeniedException:
9bfwbjaz4#
理想情况下,
@PreAuthorize
注解除了支持SpEl值之外,还应该支持String message();
。但是,不管出于什么原因,它并不支持。这里的大多数建议似乎都不必要地繁琐和复杂。正如@lathspell所建议的,提供您自己的错误消息沿着任何自定义访问验证逻辑的最简单方法是就是添加一个简单的方法来执行检查,并在检查失败时抛出AccessDeniedException
,然后在SpEl表达式中引用该方法。