如何在Spring Security @ PreAuthorize/@ PostAuthorize注释中使用自定义表达式

Jaz*_*epi 28 security spring spring-security

有没有办法在@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.");
    }
}
Run Code Online (Sandbox Code Playgroud)

我更喜欢的是类似的东西.

@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));
}
Run Code Online (Sandbox Code Playgroud)

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

gog*_*tad 52

@PreAuthorize评估SpEl -expressions开始,最简单的方法就是指向bean:

    @PreAuthorize("@mySecurityService.someFunction()")
Run Code Online (Sandbox Code Playgroud)

MySecurityService.someFunction应该有返回类型boolean.

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

    @PreAuthorize("@mySecurityService.someFunction(authentication, #someParam)")
Run Code Online (Sandbox Code Playgroud)


pgi*_*cek 28

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
    }
}
Run Code Online (Sandbox Code Playgroud)

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

<bean id="methodSecurityExpressionHandler"
    class="my.package.CustomMethodSecurityExpressionHandler">
    <property name="roleHierarchy" ref="roleHierarchy" />
    <property name="permissionEvaluator" ref="permissionEvaluator" />
</bean>
Run Code Online (Sandbox Code Playgroud)

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

<security:global-method-security pre-post-annotations="enabled">
    <security:expression-handler ref="methodSecurityExpressionHandler"/>
</security:global-method-security>
Run Code Online (Sandbox Code Playgroud)

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

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

还有一件事.使用方法级安全性来保护控制器方法并且使用业务逻辑(也称为服务层方法)来保护方法并不常见.然后你可以使用类似下面的东西.

public interface GameService {

    // rest omitted

    @PreAuthorize("principal.admin or #game.owner = principal.username")
    public void delete(@P("game") Game game);
}
Run Code Online (Sandbox Code Playgroud)

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

  • 有没有办法避免从数据库中获取相同的数据?例如,在 isOwner(Long id) 的 ccustom 安全实现中,您可能需要获取对象(基于其 id)以检查所有者是否正常。然后 - 如果安全检查正常 - 您将进入实际的方法实现,其中最有可能您要做的第一件事是根据对象的 id 获取对象,然后用它做一些事情。因此,您已经从数据库中获取了同一个对象两次。 (3认同)

hol*_*s83 10

你可以写下你的注释:

@PreAuthorize("hasRole('ROLE_ADMIN') and hasPermission(#id, 'Game', 'DELETE')")
Run Code Online (Sandbox Code Playgroud)

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

然后定义表达式处理程序bean:

@Autowired
private PermissionEvaluator permissionEvaluator;

@Bean
public DefaultMethodSecurityExpressionHandler expressionHandler()
{
    DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();
    handler.setPermissionEvaluator(permissionEvaluator);
    return handler;
}
Run Code Online (Sandbox Code Playgroud)

并注入您的安全配置:

<global-method-security pre-post-annotations="enabled">
  <expression-handler ref="expressionHandler" />
</global-method-security>
Run Code Online (Sandbox Code Playgroud)