Spring Security具有Collection 的Permission

我有使用方法级安全性保护的工作应用程序:

RestController:

@PreAuthorize("hasPermission(#product, 'WRITE')") @RequestMapping(value = "/save", method = RequestMethod.POST) public Product save(@RequestBody Product product) { return productService.save(product); } 

PermissionEvaluator:

 public class SecurityPermissionEvaluator implements PermissionEvaluator { private Logger log = LoggerFactory.getLogger(SecurityPermissionEvaluator.class); private final PermissionService permissionService; public SecurityPermissionEvaluator(PermissionService permissionService) { this.permissionService = permissionService; } @Override public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) { CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal(); return permissionService.isAuthorized(userDetails.getUser(), targetDomainObject, permission.toString()); } @Override public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) { // almost the same implementation } } 

在我实现保存对象集合的API之前,一切正常。 该服务的逻辑是更新现有实体和/或创建新实体。

 @PreAuthorize("hasPermission(#products, 'WRITE')") @RequestMapping(value = "/saveCollection", method = RequestMethod.POST) public Collection save(@RequestBody Collection products) { return productService.save(products); } 

在此之后我的权限服务处理集合对象,现在看起来像这样:

PemissionService:

 public class PermissionService { public boolean isAuthorized(User user, Object targetDomainObject, String permission) { if (targetDomainObject instanceof TopAppEntity) { if (((TopAppEntity) targetDomainObject).getId() == null) { // check authorities and give response } else { // check ACL and give response } } else if(targetDomainObject instanceof Collection) { boolean isAuthorized = false; Collection targetDomainObjects = (Collection) targetDomainObject; for (Object targetObject : targetDomainObjects) { isAuthorized = isAuthorized(user, targetObject, permission); if (!isAuthorized) break; } return isAuthorized; } } } 

我的问题是:

我如何使用@PreAuthorize("hasPermission(#object, '...')")处理集合@PreAuthorize("hasPermission(#object, '...')")更优雅的方式? Spring Security中是否有一些用于处理集合的实现? 至少,我如何优化PemissionService来处理Collections

我有几个解决方法。

1.第一个是使用我自己的MethodSecurityExpressionHandlerMethodSecurityExpressionRoot

创建CustomMethodSecurityExpressionRoot并定义一个方法,该方法将成为Collection处理的新表达式。 它将扩展SecurityExpressionRoot以包含默认表达式:

 public class CustomMethodSecurityExpressionRoot extends SecurityExpressionRoot implements MethodSecurityExpressionOperations { private final PermissionEvaluator permissionEvaluator; private final Authentication authentication; private Object filterObject; private Object returnObject; private Object target; public CustomMethodSecurityExpressionRoot(Authentication authentication, PermissionEvaluator permissionEvaluator) { super(authentication); this.authentication = authentication; this.permissionEvaluator = permissionEvaluator; super.setPermissionEvaluator(permissionEvaluator); } public boolean hasAccessToCollection(Collection collection, String permission) { for (Object object : collection) { if (!permissionEvaluator.hasPermission(authentication, object, permission)) return false; } return true; } @Override public void setFilterObject(Object filterObject) { this.filterObject = filterObject; } @Override public Object getFilterObject() { return filterObject; } @Override public void setReturnObject(Object returnObject) { this.returnObject = returnObject; } @Override public Object getReturnObject() { return returnObject; } @Override public Object getThis() { return target; } } 

创建自定义表达式处理程序并注入CustomMethodSecurityExpressionRoot

 public class CustomMethodSecurityExpressionHandler extends DefaultMethodSecurityExpressionHandler { private final PermissionEvaluator permissionEvaluator; public CustomMethodSecurityExpressionHandler(PermissionEvaluator permissionEvaluator) { this.permissionEvaluator = permissionEvaluator; super.setPermissionEvaluator(permissionEvaluator); } @Override protected MethodSecurityExpressionOperations createSecurityExpressionRoot( Authentication authentication, MethodInvocation invocation) { CustomMethodSecurityExpressionRoot root = new CustomMethodSecurityExpressionRoot(authentication, permissionEvaluator); root.setTrustResolver(new AuthenticationTrustResolverImpl()); root.setRoleHierarchy(getRoleHierarchy()); return root; } } 

我还注入了有问题的SecurityPermissionEvaluator ,因此它将成为自定义和默认表达式的单一入口点。 作为替代选项,我们可以直接注入和使用PermissionService

配置我们的方法级安全性:

 @EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true) public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration { @Autowired private PermissionService permissionService; @Override protected MethodSecurityExpressionHandler createExpressionHandler() { PermissionEvaluator permissionEvaluator = new SecurityPermissionEvaluator(permissionService); return new CustomMethodSecurityExpressionHandler(permissionEvaluator); } } 

现在我们可以在RestController使用新表达式:

 @PreAuthorize("hasAccessToCollection(#products, 'WRITE')") @RequestMapping(value = "/saveCollection", method = RequestMethod.POST) public Collection save(@RequestBody Collection products) { return productService.save(products); } 

因此,当我们将此逻辑用于自定义表达式时,可以省略PermissionService具有处理集合的部分。

2.第二种解决方法是直接使用SpEL调用方法。

现在我使用PermissionEvaluator作为Spring bean(这里可以使用任何服务,但我更喜欢单点入口)

 @Component public class SecurityPermissionEvaluator implements PermissionEvaluator { @Autowired private PermissionService permissionService; @Override public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) { if (!(targetDomainObject instanceof TopAppEntity)) throw new IllegalArgumentException(); CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal(); return permissionService.isAuthorized(userDetails.getUser(), targetDomainObject, permission.toString()); } @Override public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) { CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal(); try { return permissionService.isAuthorized(userDetails.getUser(), targetId, Class.forName(targetType), String.valueOf(permission)); } catch (ClassNotFoundException e) { throw new IllegalArgumentException("No class found " + targetType); } } public boolean hasPermission(Authentication authentication, Collection targetDomainObjects, Object permission) { for (Object targetDomainObject : targetDomainObjects) { if (!hasPermission(authentication, targetDomainObject, permission)) return false; } return true; } } 

配置方法安全性:

 @EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true) public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration { @Autowired private PermissionEvaluator permissionEvaluator; @Autowired private ApplicationContext applicationContext; @Override protected MethodSecurityExpressionHandler createExpressionHandler() { DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler(); expressionHandler.setPermissionEvaluator(permissionEvaluator); // Pay attention here, or Spring will not be able to resolve bean expressionHandler.setApplicationContext(applicationContext); return expressionHandler; } } 

在表达式中使用服务:

 @PreAuthorize("@securityPermissionEvaluator.hasPermission(authentication, #products, 'WRITE')") @RequestMapping(value = "/saveCollection", method = RequestMethod.POST) public Collection save(@RequestBody Collection products) { return productService.save(products); } 

如果没有指定其他名称,则默认使用类名创建Spring bean。

总结:这两种方法都基于使用自定义服务直接调用它们或将它们注册为表达式,并且可以在将它们发送到权限检查服务之前处理集合逻辑,因此我们可以省略它的一部分:

 @Service public class PermissionService { public boolean isAuthorized(User user, TopAppEntity domainEntity, String permission) { // removed instanceof checks and can operate on domainEntity directly if (domainEntity.getId() == null) { // check authorities and give response } else { // check ACL and give response } } } 

是的,有一种聪明的方式。 我可以告诉你我做了什么。

 @Component("MySecurityPermissionEvaluator ") @Scope(value = "session") public class PermissionService { @Autowired private PermissionEvaluator permissionEvaluator; public boolean myPermission(Object obj, String permission) { boolean isAuthorized = false; Authentication a = SecurityContextHolder.getContext() .getAuthentication(); if (null == obj) { return isAuthorized; } if (a.getAuthorities().size() == 0) { logger.error("For this authenticated object, no authorities could be found !"); return isAuthorized; } else { logger.error("Authorities found " + a.getAuthorities()); } try { isAuthorized = myPermissionEval .hasPermission(a, obj, permission); } catch (Exception e) { logger.error("exception while analysisng permissions"); } return isAuthorized; } 

请不要使用硬编码权限,请改用此方法,

 import org.springframework.security.acls.domain.DefaultPermissionFactory; public class MyPermissionFactory extends DefaultPermissionFactory { public MyPermissionFactory() { registerPublicPermissions(MyPermission.class); } } 

要制作自定义权限,

 import org.springframework.security.acls.domain.BasePermission; public class MyPermission extends BasePermission { //use this class for creating custom permissions private static Map customPerMap = new HashMap(); static { customPerMap.put("READ", 1); customPerMap.put("WRITE", 2); customPerMap.put("DELETE", 4); customPerMap.put("PUT", 8); } /** *Use the function while saving/ getting permission code **/ public static Integer getCode(String permName) { return customPerMap.get(permName.toUpperCase()); } 

如果需要根据管理员用户或角色层次结构validationURL,请在Spring Authentication而不是Authorization中使用tag。

rest,你使用正确,@ PreAuthorize和@PreFilter都是正确的,并按照要求使用。

您可以使用@PreFilter注释 。

所以@PreFilter("hasPermission(filterTarget, '...')")将为Collection的每个元素调用PermissionService。

 public class PermissionService() { public boolean isAuthorized(User user, Object targetDomainObject, String permission) { if (targetDomainObject instanceof TopAppEntity) { if (((TopAppEntity) targetDomainObject).getId() == null) { // check authorities and give response } else { // check ACL and give response } } } } 

注意:这不会阻止调用控制器方法。 它只获得一个空集合。