我的工作应用程序受到方法级安全性的保护:
RestController:
@PreAuthorize("hasPermission(#product, 'WRITE')")
@RequestMapping(value = "/save", method = RequestMethod.POST)
public Product save(@RequestBody Product product) {
return productService.save(product);
}
权限评估器:
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<Product> save(@RequestBody Collection<Product> products) {
return productService.save(products);
}
在此之后,我的权限服务处理集合对象,现在看起来像这样:
PmissionService:
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, '...')")
更优雅的方式处理集合? Spring Security 中是否有一些用于处理集合的实现?至少,如何优化 PmissionService
以处理 Collections
?
最佳答案
我有几个解决方法。
1.第一种是使用我自己的MethodSecurityExpressionHandler
和MethodSecurityExpressionRoot
。
创建一个 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<Object> 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;
}
}
创建自定义表达式处理程序并注入(inject) 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;
}
}
我还注入(inject)了有问题的 SecurityPermissionEvaluator
,因此它将成为自定义和默认表达式的单一入口点。作为替代选项,我们可以直接注入(inject)和使用 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<Product> save(@RequestBody Collection<Product> 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<Object> 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<Product> save(@RequestBody Collection<Product> products) {
return productService.save(products);
}
如果没有指定其他名称,则默认使用类名创建 Spring beans。
总结:这两种方式都是基于使用自定义服务直接调用或者注册为表达式,并且可以在发送到权限检查服务之前处理收集的逻辑,所以我们可以省略部分:
@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
}
}
}
关于java - Spring Security hasPermission for Collection<Object>,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44251325/