简体   繁体   English

Spring Security 具有集合权限<Object>

[英]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. 第一个是使用我自己的MethodSecurityExpressionHandlerMethodSecurityExpressionRoot

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.

相关问题 Spring安全性hasPermission无效 - Spring security hasPermission is not working Spring Security PermissionEvaluator:如何使用 object ID 实现 hasPermission 方法? - Spring Security PermissionEvaluator: how to implement hasPermission method with object ID? spring security中如何解释hasPermission? - How to interpret hasPermission in spring security? Spring Security中的hasPermission()不会调用CustomPermissionEvaluator - hasPermission() in Spring Security doesnt call the the CustomPermissionEvaluator 在Spring Security中与hasPermission一起使用时,权限参数是否区分大小写? - Is the permission parameter case-sensitive when using with hasPermission in spring security? 为什么在Spring Security中的hasPermission检查中使用“ #post”而不是“ post” - Why use “#post” instead of “post” in hasPermission check in Spring Security Spring Security使用PreAuthorize中的hasPermission仅使用一个参数 - Spring security use hasPermission within PreAuthorize with only one parameter 使用Spring Security&#39;hasPermission()&#39;对未经授权的REST服务请求返回JSON - Return JSON on unauthorized REST service request using Spring Security 'hasPermission()' Spring 安全 Acl 对象 - Spring Security Acl object 如果身份验证对象为null,hasPermission是否返回false - Does hasPermission return false if the authentication object is null
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM