【问题标题】:How to use custom expressions in Spring Security @PreAuthorize/@PostAuthorize annotations如何在 Spring Security @PreAuthorize/@PostAuthorize 注解中使用自定义表达式
【发布时间】:2015-01-01 01:40:13
【问题描述】:

有没有办法在 @Preauthorize 块中创建更具表现力的语句?这是我发现自己重复的一个例子,因为@Preauthorize 开箱即用并不是非常聪明。

@RequestMapping(value = "{id}", method = RequestMethod.DELETE)
public void deleteGame(@PathVariable int id, @ModelAttribute User authenticatingUser) {
    Game currentGame = gameService.findById(id);
    if(authenticatingUser.isAdmin() || currentGame.getOwner().equals(authenticatingUser)) {
        gameService.delete(gameService.findById(id));
    } else {
        throw new SecurityException("Only an admin, or an owner can delete a game.");
    }
}

我更喜欢类似的东西。

@RequestMapping(value = "{id}", method = RequestMethod.DELETE)
@Preauthorize(isAdmin(authenicatingUser) OR isOwner(authenicatingUser, id)
public void deleteGame(@PathVariable int id, @ModelAttribute User authenticatingUser, @ModelAttribute currentGame ) { //I'm not sure how to add this either :(
   gameService.delete(gameService.findById(id));
}

部分问题是我需要对数据库进行查询以获取其中一些东西来验证权限,例如查询数据库以获取游戏的副本,然后将游戏的所有者与提出请求的人。我不确定所有这些是如何在 @Preauthorize 注释处理器的上下文中运行的,或者我如何将内容添加到 @Preauthorize("") 值属性中可用的对象集合中。

【问题讨论】:

    标签: spring security spring-security


    【解决方案1】:

    由于@PreAuthorize 评估SpEl-表达式,最简单的方法就是指向一个bean:

        @PreAuthorize("@mySecurityService.someFunction()")
    

    MySecurityService.someFunction 应该有返回类型boolean

    如果你想传递Authentication-object,Spring-security 会自动提供一个名为authentication 的变量。您还可以使用任何有效的 SpEl 表达式来访问传递给您的安全方法的任何参数、评估正则表达式、调用静态方法等。例如:

        @PreAuthorize("@mySecurityService.someFunction(authentication, #someParam)")
    

    【讨论】:

    • 这对我来说听起来是个不错的解决方案。您可以创建多个包含自定义授权代码的 @Components,这些代码可以跨端点重复使用
    【解决方案2】:

    1) 首先你必须重新实现MethodSecurityExpressionRoot,它包含额外的特定于方法的功能。最初的 Spring Security 实现是包私有的,因此不能仅仅扩展它。我建议检查给定类的源代码。

    public class CustomMethodSecurityExpressionRoot extends SecurityExpressionRoot implements MethodSecurityExpressionOperations {
    
        // copy everything from the original Spring Security MethodSecurityExpressionRoot
    
        // add your custom methods
    
        public boolean isAdmin() {
            // do whatever you need to do, e.g. delegate to other components
    
            // hint: you can here directly access Authentication object 
            // via inherited authentication field
        }
    
        public boolean isOwner(Long id) {
            // do whatever you need to do, e.g. delegate to other components
        }
    }
    

    2) 接下来您必须实现自定义MethodSecurityExpressionHandler,它将使用上面定义的CustomMethodSecurityExpressionRoot

    public class CustomMethodSecurityExpressionHandler extends DefaultMethodSecurityExpressionHandler {
    
        private final AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
    
        @Override
        public void setReturnObject(Object returnObject, EvaluationContext ctx) {
            ((MethodSecurityExpressionRoot) ctx.getRootObject().getValue()).setReturnObject(returnObject);
        }
    
        @Override
        protected MethodSecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication,
            MethodInvocation invocation) {
            final CustomMethodSecurityExpressionRoot root = new CustomMethodSecurityExpressionRoot(authentication);
            root.setThis(invocation.getThis());
            root.setPermissionEvaluator(getPermissionEvaluator());
            root.setTrustResolver(this.trustResolver);
            root.setRoleHierarchy(getRoleHierarchy());
    
            return root;
        }
    }
    

    3) 在您的上下文中定义表达式处理程序 bean,例如通过 XML,您可以按如下方式进行操作

    <bean id="methodSecurityExpressionHandler"
        class="my.package.CustomMethodSecurityExpressionHandler">
        <property name="roleHierarchy" ref="roleHierarchy" />
        <property name="permissionEvaluator" ref="permissionEvaluator" />
    </bean>
    

    4) 注册上面定义的处理程序

    <security:global-method-security pre-post-annotations="enabled">
        <security:expression-handler ref="methodSecurityExpressionHandler"/>
    </security:global-method-security>
    

    5) 然后只需在 @PreAuthorize 和/或 @PostAuthorize 注释中使用定义的表达式

    @PreAuthorize("isAdmin() or isOwner(#id)")
    public void deleteGame(@PathVariable int id, @ModelAttribute currentGame) {
        // do whatever needed
    }
    

    还有一件事。使用方法级别的安全性来保护控制器方法并不是很常见,而是使用业务逻辑来保护方法(也就是您的服务层方法)。然后你可以使用类似下面的东西。

    public interface GameService {
    
        // rest omitted
    
        @PreAuthorize("principal.admin or #game.owner = principal.username")
        public void delete(@P("game") Game game);
    }
    

    但请记住,这只是一个示例。它期望实际的主体具有isAdmin() 方法,并且游戏具有返回所有者用户名的getOwner() 方法。

    【讨论】:

    • 这个解决方案帮助我完成了 spring boot 1.3.3.RELEASE
    • 有没有办法避免从数据库中获取相同的数据?例如。在 isOwner(Long id) 的自定义安全实现中,您可能需要获取对象(基于其 id)以检查所有者是否正常。然后 - 如果安全检查没问题 - 你进入实际的方法实现,你做的第一件事很可能是根据它的 id 获取对象,然后用它做一些事情。所以,你已经从数据库中获取了两次相同的对象。
    【解决方案3】:

    你可以这样写注释:

    @PreAuthorize("hasRole('ROLE_ADMIN') and hasPermission(#id, 'Game', 'DELETE')")
    

    要让 hasPermission 部分正常工作,您需要实现 PermissionEvaluator 接口。

    然后定义一个表达式处理bean:

    @Autowired
    private PermissionEvaluator permissionEvaluator;
    
    @Bean
    public DefaultMethodSecurityExpressionHandler expressionHandler()
    {
        DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();
        handler.setPermissionEvaluator(permissionEvaluator);
        return handler;
    }
    

    并注入您的安全配置:

    <global-method-security pre-post-annotations="enabled">
      <expression-handler ref="expressionHandler" />
    </global-method-security>
    

    【讨论】:

      猜你喜欢
      • 2013-01-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-01-11
      • 2015-09-12
      • 2020-10-25
      • 2019-05-02
      • 1970-01-01
      相关资源
      最近更新 更多