[英]Spring Security hasPermission for Collection<Object>
I have working application secured with method-level security:我有使用方法级安全保护的工作应用程序:
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
}
}
And everything works fine until I implemented API which saves collection of objects.一切正常,直到我实现了保存对象集合的 API。 The logic of this service is to update existing entities and/or create new entities.
此服务的逻辑是更新现有实体和/或创建新实体。
@PreAuthorize("hasPermission(#products, 'WRITE')")
@RequestMapping(value = "/saveCollection", method = RequestMethod.POST)
public Collection<Product> save(@RequestBody Collection<Product> products) {
return productService.save(products);
}
After this my permission service handles the collection object and looks like this now:在此之后,我的权限服务处理集合对象,现在看起来像这样:
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;
}
}
}
My question is:我的问题是:
How I can handle collections using @PreAuthorize("hasPermission(#object, '...')")
more elegant way?我如何使用
@PreAuthorize("hasPermission(#object, '...')")
更优雅的方式处理集合? Is there some implementations in Spring Security for handling collections? Spring Security 中是否有一些用于处理集合的实现? At least, how can I optimize
PemissionService
for handling Collections
?至少,我如何优化
PemissionService
来处理Collections
?
I have a couple of workarounds.我有几个解决方法。
1. The first one is to use my own MethodSecurityExpressionHandler
and MethodSecurityExpressionRoot
. 1. 第一个是使用我自己的
MethodSecurityExpressionHandler
和MethodSecurityExpressionRoot
。
Creating a CustomMethodSecurityExpressionRoot
and define a method which will be our new expression for Collection
handling.创建一个
CustomMethodSecurityExpressionRoot
并定义一个方法,它将成为我们处理Collection
的新表达式。 It will extend SecurityExpressionRoot
to include default expressions:它将扩展
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;
}
}
Create custom expression handler and inject CustomMethodSecurityExpressionRoot
:创建自定义表达式处理程序并注入
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;
}
}
I also injected SecurityPermissionEvaluator
used in question, so it will be a single point of entry for custom and default expressions.我还注入了有问题的
SecurityPermissionEvaluator
,因此它将成为自定义和默认表达式的单一入口点。 As an alternate option we could inject and use PermissionService
directly.作为替代选项,我们可以直接注入和使用
PermissionService
。
Configuring our method-level security:配置我们的方法级安全性:
@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);
}
}
Now we can use new expression in RestController
:现在我们可以在
RestController
使用 new 表达式:
@PreAuthorize("hasAccessToCollection(#products, 'WRITE')")
@RequestMapping(value = "/saveCollection", method = RequestMethod.POST)
public Collection<Product> save(@RequestBody Collection<Product> products) {
return productService.save(products);
}
As a result a part with handling collection in PermissionService
could be omitted as we took out this logic to custom expression.因此,当我们将此逻辑取出到自定义表达式时,可以省略
PermissionService
处理集合的部分。
2. The second workaround is to call method directly using SpEL. 2.第二种解决方法是直接使用SpEL调用方法。
Now I'm using PermissionEvaluator
as Spring bean (any service could be used here, but I'm preferring single point of entry again)现在我使用
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;
}
}
Configuring method security:配置方法安全:
@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;
}
}
Usage of the service in expression:表达式中服务的使用:
@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 created by default with class name if no other name specified.如果未指定其他名称,则默认使用类名创建 Spring bean。
Summary: both approaches based on using custom services calling them directly or registering them as expressions and could handle the logic of collection before it will be sent to authority checking service, so we can omit the part of it:总结:这两种方式都基于使用自定义服务直接调用或者将它们注册为表达式,并且可以在将其发送到权限检查服务之前处理收集逻辑,因此我们可以省略其中的部分:
@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
}
}
}
Yes, there is a smart way.是的,有一个聪明的方法。 I can tell you what I did.
我可以告诉你我做了什么。
@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;
}
Please do not use hard coded permissions, Use this way instead,请不要使用硬编码权限,而是使用这种方式,
import org.springframework.security.acls.domain.DefaultPermissionFactory;
public class MyPermissionFactory extends DefaultPermissionFactory {
public MyPermissionFactory() {
registerPublicPermissions(MyPermission.class);
}
}
To make custom permissions,要制作自定义权限,
import org.springframework.security.acls.domain.BasePermission;
public class MyPermission extends BasePermission { //use this class for creating custom permissions
private static Map<String, Integer> customPerMap = new HashMap<String, Integer>();
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());
}
If you need to authenticate urls based on admin users or role hierarchy, use tag in Spring Authentication not Authorization.如果您需要根据管理员用户或角色层次结构对 url 进行身份验证,请在 Spring Authentication 中使用 tag 而不是 Authorization。
Rest, you are using correctly, @PreAuthorize and @PreFilter both are correct and used acco to requirements.休息,您使用正确,@PreAuthorize 和@PreFilter 都是正确的,并根据要求使用。
You can use the @PreFilter
annotation .您可以使用
@PreFilter
注释。
So @PreFilter("hasPermission(filterTarget, '...')")
will call your PermissionService for each element of the Collection.所以
@PreFilter("hasPermission(filterTarget, '...')")
将为集合的每个元素调用您的 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
}
}
}
}
Note: this will not prevent a call of your controller method.注意:这不会阻止调用您的控制器方法。 It only gets an empty Collection.
它只会得到一个空的集合。
In some cases it's enough a default implementation of SecurityExpressionRoot
.在某些情况下,
SecurityExpressionRoot
的默认实现就足够了。 If your permission evaluation is based on only analyzing, for example, an Owner of Product you could use the next expressions:如果您的权限评估仅基于分析,例如,产品所有者,您可以使用以下表达式:
@GetMapping("")
@PostAuthorize("hasPermission(returnObject.![#this.owner],'ProductOwner','READ')")
public Collection<Product> getAllFiltering(<filters>) {...
@PostMapping("/collection")
@PreAuthorize("hasPermission(#products.![#this.owner],'ProductOwner','WRITE')")
public Collection<Product> save(@RequestBody Collection<Product> products) {...
@PutMapping("/collection")
@PreAuthorize("hasPermission(@productRepository.findByIds(#products.![#this.id]).![#this.owner],'ProductOwner','WRITE')")
public Collection<Product> update(@RequestBody Collection<Product> products) {...
In these cases your PermissionEvaluator
must be able to process collection.You could also continue using your PermissionEvaluator
for a single Product:在这些情况下,您的
PermissionEvaluator
必须能够处理集合。您也可以继续将PermissionEvaluator
用于单个产品:
@GetMapping("/{id}")
@PostAuthorize("hasPermission({ returnObject.owner },'ProductOwner','READ')")
public Product getById(@PathVariable int id) {...
or make an implementation of PermissionEvaluator
which analyzes whether an array or a single value was passed.或者实现
PermissionEvaluator
来分析传递的是数组还是单个值。
#products.![#this.owner]
- see "6.5.17 Collection Projection"; #products.![#this.owner]
- 参见“6.5.17 集合投影”; { returnObject.owner }
- see "6.5.3 Inline lists" here: https://docs.spring.io/spring/docs/3.0.x/reference/expressions.html { returnObject.owner }
- 请参阅此处的“6.5.3 内联列表”: https : { returnObject.owner }
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.