Spring Security hasPermission for Collection< Object> [英] Spring Security hasPermission for Collection<Object>

查看:416
本文介绍了Spring Security hasPermission for Collection< Object>的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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



RestController:

  @PreAuthorize(hasPermission(#product,'WRITE'))
@RequestMapping(value =/ save,method = RequestMethod.POST)
公共产品保存(@RequestBody产品){
return productService.save(product);
}

PermissionEvaluator:

 公共类SecurityPermissionEvaluator实现PermissionEvaluator {

private Logger log = LoggerFactory.getLogger(SecurityPermissionEvaluator.class);

private final PermissionService permissionService;

public SecurityPermissionEvaluator(PermissionService permissionService){
this.permissionService = permissionService;
}

@Override
public boolean hasPermission(身份验证身份验证,Object targetDomainObject,Object权限){
CustomUserDetails userDetails =(CustomUserDetails)authentication.getPrincipal();
返回permissionService.isAuthorized(userDetails.getUser(),targetDomainObject,permission.toString());
}

@Override
public boolean hasPermission(身份验证身份验证,Serializable targetId,String targetType,Object权限){
//几乎相同的实现
}
}

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

  @PreAuthorize(hasPermission(#products, 'WRITE'))
@RequestMapping(value =/ saveCollection,method = RequestMethod.POST)
public Collection< Product>保存(@RequestBody Collection< Product>产品){
return productService.save(products);
}

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

PemissionService:

  public class PermissionService {

public boolean isAuthorized(User user,Object targetDomainObject,String permission){
if(targetDomainObject instanceof TopAppEntity){
if((TopAppEntity)targetDomainObject).getId() == null){
//检查权限并给出响应
} else {
//检查ACL并给出响应
}
} else if(targetDomainObject instanceof Collection ){
boolean isAuthorized = false;
集合targetDomainObjects =(集合)targetDomainObject;
for(Object targetObject:targetDomainObjects){
isAuthorized = isAuthorized(user,targetObject,permission);
if(!isAuthorized)break;
}
return isAuthorized;
}
}
}

我的问题是:



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

解决方案

我有几个解决方法。



1。第一个是使用我自己的 MethodSecurityExpressionHandler MethodSecurityExpressionRoot



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

  public class CustomMethodSecurityExpressionRoot extends SecurityExpressionRoot实现MethodSecurityExpressionOperations {

private final PermissionEvaluator permissionEvaluator;
private final身份验证身份验证;

private Object filterObject;
private Object returnObject;
私有对象目标;

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(身份验证,对象,权限))
返回false;
}
返回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(
身份验证身份验证,MethodInvocation调用){
CustomMethodSecurityExpressionRoot root =
new CustomMethodSecurityExpressionRoot(authentication, permissionEvaluator);
root.setTrustResolver(new AuthenticationTrustResolverImpl());
root.setRoleHierarchy(getRoleHierarchy());
返回root;
}
}

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



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

  @EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
公共类MethodSecurityConfig扩展GlobalMethodSecurityConfiguration {

@Autowired
私有PermissionService permissionService;

@Override
protected MethodSecurityExpressionHandler createExpressionHandler(){
PermissionEvaluator permissionEvaluator = new SecurityPermissionEvaluator(permissionService);
返回新的CustomMethodSecurityExpressionHandler(permissionEvaluator);
}
}

现在我们可以在<$ c $中使用新表达式c> RestController :

  @PreAuthorize(hasAccessToCollection(#products,'WRITE')) 
@RequestMapping(value =/ saveCollection,method = RequestMethod.POST)
public Collection< Product>保存(@RequestBody Collection< Product>产品){
return productService.save(products);
}

因此,在 PermissionService中处理集合的部分可以省略,因为我们将这个逻辑用于自定义表达式。



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



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

  @Component 
公共类SecurityPermissionEvaluator实现PermissionEvaluator {

@Autowired
private PermissionService permissionService;

@Override
public boolean hasPermission(Authentication authentication,Object targetDomainObject,Object permission){
if(!(targetDomainObject instanceof TopAppEntity))
抛出新的IllegalArgumentException() ;
CustomUserDetails userDetails =(CustomUserDetails)authentication.getPrincipal();
返回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){
抛出新的IllegalArgumentException(找不到类+ targetType);
}
}

public boolean hasPermission(Authentication authentication,Collection< Object> targetDomainObjects,Object permission){
for(Object targetDomainObject:targetDomainObjects){
if(!hasPermission(authentication,targetDomainObject,permission))
返回false;
}
返回true;
}

}

配置方法安全性:

  @EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
公共类MethodSecurityConfig扩展GlobalMethodSecurityConfiguration {

@Autowired
private PermissionEvaluator permissionEvaluator;
@Autowired
private ApplicationContext applicationContext;

@Override
protected MethodSecurityExpressionHandler createExpressionHandler(){
DefaultMethodSecurityExpressionHandler expressionHandler =
new DefaultMethodSecurityExpressionHandler();
expressionHandler.setPermissionEvaluator(permissionEvaluator);
//注意这里,或者Spring将无法解析bean
expressionHandler.setApplicationContext(applicationContext);
返回expressionHandler;
}
}

在表达式中使用服务:

  @PreAuthorize(@ securityPermissionEvaluator.hasPermission(authentication,#products,'WRITE'))
@RequestMapping(value = / saveCollection,method = RequestMethod.POST)
public Collection< Product>保存(@RequestBody Collection< Product>产品){
return productService.save(products);
}

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



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

  @Service 
public class PermissionService {

public boolean isAuthorized(用户用户,TopAppEntity domainEntity,字符串权限){
//删除了instanceof支票,可以直接对domainEntity进行操作
if(domainEntity.getId()== null){
// check当局并给予答复
} else {
//检查ACL并给予回复
}
}
}


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. 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? Is there some implementations in Spring Security for handling collections? At least, how can I optimize PemissionService for handling Collections?

解决方案

I have a couple of workarounds.

1. The first one is to use my own MethodSecurityExpressionHandler and MethodSecurityExpressionRoot.

Creating a CustomMethodSecurityExpressionRoot and define a method which will be our new expression for Collection handling. It will extend SecurityExpressionRoot to include default expressions:

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:

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. As an alternate option we could inject and use PermissionService directly.

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:

@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.

2. The second workaround is to call method directly using SpEL.

Now I'm using PermissionEvaluator as Spring bean (any service could be used here, but I'm preferring single point of entry again)

@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.

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
        }
    }
}

这篇关于Spring Security hasPermission for Collection&lt; Object&gt;的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆