为什么@PreAuthorize在一个存储库中触发了hasPermission(在自定义PermissionEvaluator中)而在另一个存储库中没有触发?

时间:2018-02-19 03:15:46

标签: java spring spring-boot spring-security spring-data-rest

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

为了首先解决这个问题,我使用GlobalMethodSecurity启用了prePostEnabled = true。我还写了一个自定义的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;
    }
}

对于下面的Project Repository,一切运行正常,我的hasPermission表达式被正确触发并运行自定义PermissionEvaluator。

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);

}

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

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注释。

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类......以防万一与它有关。

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触发。

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

我还在save方法的ProjectRoleRepository接口上设置了一个断点,如果我在那里添加一个断点,它需要一段时间来触发,但它会触发并显示值被正确填充。

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

编辑:一些代码显示在哪种情况下,为Project而不是ProjectRole触发评估程序。这是在CommandLineRunner类中,我们用它来初始用一些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

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"

2 个答案:

答案 0 :(得分:0)

我们找到了罪魁祸首。

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

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

答案 1 :(得分:0)

对于Spring Data Rest用户,请注意,您无需创建GlobalMethodSecurityConfiguration即可使用自定义PermissionEvaluator。实际上,这将导致兔子受伤。

相反,只需将您的自定义PermissionEvaluator定义为上下文中的普通bean,Spring安全性便会采用它。