我们正在使用spring-spring-security-3.2。最近我们将@PreAuthorize注释添加到RestAPIs(之前它是基于URL的)。
@PreAuthorize("hasPermission('salesorder','ViewSalesOrder')")
@RequestMapping(value = "/restapi/salesorders/", method = RequestMethod.GET)
public ModelAndView getSalesOrders(){}
我们已经有了使用 - @ControllerAdvice和自定义PermissionEvaluator注释的全局异常处理程序,除错误消息外,一切正常。
让我们说一些用户正在访问API在没有“ViewSalesOrder”权限的情况下,弹出默认情况下抛出异常“访问被拒绝”,但没有告诉哪个权限丢失(我们要求提及缺少哪个权限) )。
是否可以抛出一个也包含权限名称的异常,因此最终的错误消息应该看起来像“访问被拒绝,你需要ViewSalesOrder权限”(这里的权限名称应该来自@PreAuthorize注释)?
请注意,我们有100个这样的restAPI,因此非常感谢通用解决方案。
答案 0 :(得分:2)
由于{
'cinema': 1,
'fire_station': 1,
'library': 1,
'railway_station': 1,
'school': 2,
'supermarket': 1
}
[
{'field': 'bus_station', 'choice': 'Bus Station', 'count': 0},
{'field': 'car_park', 'choice': 'Car Park', 'count': 0},
{'field': 'cinema', 'choice': 'Cinema', 'count': 1},
{'field': 'dentist', 'choice': 'Dentist', 'count': 0},
{'field': 'doctor', 'choice': 'Doctor', 'count': 0},
{'field': 'fire_station', 'choice': 'Fire Station', 'count': 1},
{'field': 'garden', 'choice': 'Public Garden', 'count': 0},
{'field': 'hospital', 'choice': 'Hospital', 'count': 0},
{'field': 'leisure_centre', 'choice': 'Leisure Centre', 'count': 0},
{'field': 'library', 'choice': 'Library', 'count': 1},
{'field': 'public_service', 'choice': 'Public Service', 'count': 0},
{'field': 'railway_station', 'choice': 'Railway Station', 'count': 1},
{'field': 'school', 'choice': 'School', 'count': 2},
{'field': 'supermarket', 'choice': 'Supermarket', 'count': 1},
{'field': 'woodland', 'choice': 'Woodland', 'count': 0}
]
界面不允许您将遗漏的权限与评估结果一起传递,因此没有很好的方法可以实现您的期望。
此外,PermissionEvaluator
决定对AccessDecisionManager
个实体的投票的最终授权,其中一个是AccessDecisionVoter
,其对PreInvocationAuthorizationAdviceVoter
值的评估进行投票。
长话短说,当您的自定义@PreAuthorize
返回PreInvocationAuthorizationAdviceVoter
到PermissionEvaluator
时,false
投票反对请求(给出请求-1点)。如您所见,无法在此流程中传播失败的原因。
另一方面,您可以尝试一些变通方法来实现您的目标。
一种方法是在权限检查失败时在您的自定义hasPermission
内抛出异常。您可以使用此异常将缺少的权限传播到全局异常处理程序。在那里,您可以将缺少的权限作为参数传递给消息描述符。请注意,这将暂停PermissionEvaluator
的执行过程,这意味着不会执行连续的选民(默认为RoleVoter和AuthenticatedVoter)。如果你选择走这条路,你应该小心。
另一种更安全但更笨拙的方法是实现自定义AccessDeniedHandler并在使用403响应之前自定义错误消息。AccessDecisionManager
为您提供可用于检索请求URI的当前AccessDeniedHandler
。但是,在这种情况下的坏消息是,您需要URI来进行权限映射才能找到丢失的权限。
答案 1 :(得分:0)
我已经实现了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;
}
}