为什么 @PreAuthorize 和 hasPermission(在自定义 PermissionEvaluator 中)在一个存储库中触发,而在另一个存储库中没有触发? [英] Why is @PreAuthorize with hasPermission (in custom PermissionEvaluator) triggered in one Repository but not in the other?

查看:110
本文介绍了为什么 @PreAuthorize 和 hasPermission(在自定义 PermissionEvaluator 中)在一个存储库中触发,而在另一个存储库中没有触发?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

出于某种奇怪的原因,我的带有 hasPermission 表达式的 @PreAuthorize 注释是从我的一个存储库中触发的,而不是在另一个看起来几乎相同的存储库中触发.

For some weird reason my @PreAuthorize annotation with a hasPermission expression is triggered from ONE of my repositories but not in another that seems almost identical.

为了首先解决这个问题,我使用 prePostEnabled = true 启用了 GlobalMethodSecurity.我还编写了一个自定义的 PermissionEvaluator.

Just to get this out of the way first, I have enabled GlobalMethodSecurity with prePostEnabled = true. I have also written a custom PermissionEvaluator.

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration;

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfiguration extends GlobalMethodSecurityConfiguration {

    @Autowired
    private CustomPermissionEvaluator permissionEvaluator;

    @Override
    protected MethodSecurityExpressionHandler createExpressionHandler() {
        DefaultMethodSecurityExpressionHandler expressionHandler =
                new DefaultMethodSecurityExpressionHandler();
        expressionHandler.setPermissionEvaluator(permissionEvaluator);
        return expressionHandler;
    }
}

对于下面的项目存储库,一切正常,我的 hasPermission 表达式被正确触发,自定义 PermissionEvaluator 运行.

For the Project Repository below everything is working well, my hasPermission expression is correctly triggered and the custom PermissionEvaluator is run.

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.security.access.prepost.PreAuthorize;

@PreAuthorize("hasRole('" + Constants.ROLE_USER + "')")
public interface ProjectRepository extends PagingAndSortingRepository<Project, Long> {

    @Override
    @PreAuthorize("hasPermission(#project, " + Constants.WRITE + ")")
    Project save(@Param("project") Project project);

    @Override
        //TODO: add security based on id delete
        //@PreAuthorize("hasPermission(#project, " + Constants.DELETE + ")")
    void delete(@Param("id") Long id);

    @Override
    @PreAuthorize("hasPermission(#project, " + Constants.DELETE + ")")
    void delete(@Param("project") Project project);

    @Override
    @Query("select p from Project p left join p.roles r left join r.account a where p.id = ?1 and ?#{principal.username} = a.username")
    Project findOne(@Param("id") Long id);

    @Override
    @Query("select p from Project p left join p.roles r left join r.account a where ?#{principal.username} = a.username")
    Page<Project> findAll(Pageable pageable);

}

为了完整起见,这是项目实体:

For completeness, this is the Project entity:

import com.fasterxml.jackson.annotation.JsonBackReference;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;

import javax.persistence.*;
import java.util.List;

@Data
@Entity
@RequiredArgsConstructor
public class Project {

    private @Id @GeneratedValue Long id;
    private @Column(nullable = false) @NonNull String name;
    private @Column(nullable = false) @NonNull String description;

    private @Version @JsonIgnore Long version;

    private @OneToMany(mappedBy = "project", cascade = CascadeType.REMOVE) List<ProjectRole> roles;

    private Project() {
    }

}

但是,对于下面的 ProjectRole 存储库,不会触发 hasPermission 表达式.事实上,即使是整个类的 PreAuthorize 也没有被触发(我将所需的用户角色设置为一些随机字符串来测试).不过@Query 注释使用正确.

However, for the ProjectRole Repository below, the hasPermission expression is NOT triggered. In fact, even the PreAuthorize on the whole class isn't triggered (I set the required user role to some random string to test this). The @Query annotations are used correctly though.

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.security.access.prepost.PreAuthorize;

@PreAuthorize("hasRole('" + Constants.ROLE_USER + "')")
public interface ProjectRoleRepository extends PagingAndSortingRepository<ProjectRole, Long> {

    //TODO: why is this not triggering?
    @Override
    @PreAuthorize("hasPermission(#projectRole, " + Constants.WRITE + ")")
    ProjectRole save(@Param("projectRole") ProjectRole projectRole);

    //TODO: add security based on id delete
    @Override
    void delete(@Param("id") Long id);

    @Override
    @PreAuthorize("hasPermission(#projectRole, " + Constants.DELETE + ")")
    void delete(@Param("projectRole") ProjectRole projectRole);

    @Override
    @Query("select r from ProjectRole r left join r.account a where r.id = ?1 and ?#{principal.username} = a.username")
    ProjectRole findOne(@Param("id") Long id);

    @Override
    @Query("select r from ProjectRole r left join r.account a where ?#{principal.username} = a.username")
    Page<ProjectRole> findAll(Pageable pageable);

    ProjectRole findByProjectAndAccount(Project project, Account account);

}

以及随附的 ProjectRole 类...以防万一它与此有关.

And the accompanying ProjectRole class... just in case it has something to do with that.

import lombok.Data;
import lombok.NonNull;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;

import javax.persistence.*;

@Data
@Entity
@Table(uniqueConstraints = {
        @UniqueConstraint(columnNames = {"project_id", "account_id"})
})
public class ProjectRole {
    private @Id @GeneratedValue Long id;
    private @JoinColumn(name = "project_id", nullable = false) @NonNull @ManyToOne Project project;
    private @JoinColumn(name = "account_id", nullable = false) @NonNull @ManyToOne Account account;

    private @Enumerated ProjectRoleEnum name;

    protected ProjectRole() {}

    public ProjectRole(Project project, Account account, ProjectRoleEnum name) {
        this.project = project;
        this.account = account;
        this.name = name;
    }
}

自定义 PermissionEvaluator 本身的代码如下.我在 hasPermission 方法的第一行设置了一个断点,它会为其他存储库触发,但不会为 ProjectRole 触发.

The code of the custom PermissionEvaluator itself is below. I set a breakpoint on the first line of the hasPermission method, it's triggered for other repositories but not for ProjectRole.

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;

import java.io.Serializable;

@Component
public class CustomPermissionEvaluator implements PermissionEvaluator {

    private final ProjectRoleRepository projectRoleRepository;
    private final AccountRepository accountRepository;


    @Autowired
    public CustomPermissionEvaluator(ProjectRoleRepository projectRoleRepository, AccountRepository accountRepository) {
        this.projectRoleRepository = projectRoleRepository;
        this.accountRepository = accountRepository;
    }

    @Override
    public boolean hasPermission(Authentication auth, Object targetDomainObject, Object permission) {
        if (targetDomainObject == null)
            return false;

        if ((auth == null) || !(permission instanceof String))
            return false;

        Object principal = auth.getPrincipal();
        if (!(principal instanceof String || principal instanceof CustomUserPrincipal))
            throw new UnsupportedOperationException("Principal should be instance of String or CustomUserPrincipal.");
        Account account;
        if (principal instanceof String) account = accountRepository.findByUsername((String) principal);
        else account = ((CustomUserPrincipal) principal).getAccount();

        if (targetDomainObject instanceof Project)
            return hasPermissionOnProject(account, (Project) targetDomainObject, (String) permission);
        else if (targetDomainObject instanceof ProjectRole)
            return hasPermissionOnProjectRole(account, (ProjectRole) targetDomainObject, (String) permission);
        else
            throw new RuntimeException("targetDomainObject should be instance of Project or ProjectRole but was " + targetDomainObject);
    }

    private boolean hasPermissionOnProjectRole(Account account, ProjectRole projectRole, String permission) {
        //code here
    }

    private boolean hasPermissionOnProject(Account account, Project project, String permission) {
        //code here
    }

    @Override
    public boolean hasPermission(Authentication auth, Serializable targetId, String targetType, Object permission) {
        return false;
    }
}

我还在保存方法的 ProjectRoleRepository 接口上设置了一个断点,如果我在那里添加断点需要一段时间才能触发,但它确实触发并显示值已正确填充.

I have also set a breakpoint on the ProjectRoleRepository interface on the save method, it takes a while to trigger if I add a breakpoint there but it does trigger and shows the values are filled correctly.

是否有任何我做的不同的事情可能导致 @PreAuthorize 注释/hasPermission 表达式没有为 ProjectRoleRepository 类触发?我还有 2 个具有安全性的存储库,包括我在这里展示的一个,其中一切正常.如果需要,我可以显示更多代码,但我认为这就是相关的全部.

Is there ANYTHING I am doing differently that could be the cause for the @PreAuthorize annotation/hasPermission expression not triggering for the ProjectRoleRepository class? I have 2 more repositories with security, including the one I showed here, where everything is working correctly. If needed I can show more code but I think this is all that's relevant.

一些代码显示在这种情况下为 Project 而不是 ProjectRole 触发评估器.这是在一个 CommandLineRunner 类中,我们用它最初用一些测试数据填充数据库.

some code showing in which case the evaluator is triggered for Project but not ProjectRole. This is in a CommandLineRunner class that we use to initially fill the database with some testdata.

    @Override
    public void run(String... strings) {

        Account user1 = this.accounts.save(new Account("user1", "xxx",
                Constants.ROLE_USER));

        SecurityContextHolder.getContext().setAuthentication(
                new UsernamePasswordAuthenticationToken("user1", "doesn't matter",
                        AuthorityUtils.createAuthorityList(Constants.ROLE_USER)));


        Project p1 = new Project("Name", "Blabla.");
        this.projects.save(p1);

        ProjectRole ownerRolep1 = new ProjectRole();
        ownerRolep1.setAccount(user1);
        ownerRolep1.setProject(p1);
        ownerRolep1.setName(ProjectRoleEnum.OWNER);
        this.projectRoles.save(ownerRolep1);
}

我重现这一点的另一种方式是通过实际进行 REST 调用,发布实体会触发 Project 而不是 ProjectRole 的评估器

The other way I'm reproducing this is by actually doing REST calls, posting the entity triggers the evaluator for Project but not for ProjectRole

curl -X POST -u username:pw localhost:8080/projects -d "{\"name\": \"projectName\", \"description\": \"projectDesc\"}" -H "Content-Type:application/json"
curl -X POST -u username:pw localhost:8080/projectRoles -d "{\"name\":\"1\", \"account\": \"/accounts/3\", \"project\": \"/projects/8\"}" -H "Content-Type:application/json"

推荐答案

我们找到了罪魁祸首.

@EnableGlobalMethodSecurity(prePostEnabled = true) 之前已移至我们的 MethodSecurityConfiguration 类,我们在其中设置了自定义 PermissionEvaluator.我从那里删除了它并将它放回我们的 public class SecurityConfiguration extends WebSecurityConfigurerAdapter 和修复的东西.但我仍然觉得很奇怪,它仍然适用于某些存储库但不适用于其他存储库.如果有人能对此有所启发,那就太好了.为了完整起见,下面的类是我们错误定义的地方.

@EnableGlobalMethodSecurity(prePostEnabled = true) had previously been moved to our MethodSecurityConfiguration class, in which we set up the custom PermissionEvaluator. I removed it from there and put it back in our public class SecurityConfiguration extends WebSecurityConfigurerAdapter and that fixed things. But I still find it very odd that it would still work for some repositories but not for others. If someone could shine a light on that, that would be great. For completeness sake, the class below was where we incorrectly defined it.

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration;

@Configuration
public class MethodSecurityConfiguration extends GlobalMethodSecurityConfiguration {

    @Autowired
    private CustomPermissionEvaluator permissionEvaluator;

    @Override
    protected MethodSecurityExpressionHandler createExpressionHandler() {
        DefaultMethodSecurityExpressionHandler expressionHandler =
                new DefaultMethodSecurityExpressionHandler();
        expressionHandler.setPermissionEvaluator(permissionEvaluator);
        return expressionHandler;
    }
}

这篇关于为什么 @PreAuthorize 和 hasPermission(在自定义 PermissionEvaluator 中)在一个存储库中触发,而在另一个存储库中没有触发?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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