为什么带有 hasPermission 的 @PreAuthorize(在自定义 PermissionEvaluator 中)在一个存储库中触发但在另一个存储库中没有触发?
Why is @PreAuthorize with hasPermission (in custom PermissionEvaluator) triggered in one Repository but not in the other?
出于某种奇怪的原因,我的带有 hasPermission 表达式的 @PreAuthorize 注释是从我的一个存储库中触发的,但在另一个看起来几乎相同的存储库中却没有。
为了先解决这个问题,我启用了 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;
}
}
下面的项目存储库一切正常,我的 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 表达式。事实上,甚至整个 class 的 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 class...以防与此有关。
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;
}
}
我还在保存方法的 ProjectRoleRepository 接口上设置了断点,如果我在那里添加断点需要一段时间才能触发,但它确实触发并显示值已正确填充。
有没有什么我做的不同可能是 @PreAuthorize annotation/hasPermission 表达式没有触发 ProjectRoleRepository class 的原因?我还有 2 个具有安全性的存储库,包括我在此处显示的存储库,其中一切正常。如果需要,我可以显示更多代码,但我认为这是所有相关的。
编辑:一些代码显示在这种情况下评估器是针对 Project 而不是 ProjectRole 触发的。这是在一个 CommandLineRunner class 中,我们用它来最初用一些测试数据填充数据库。
@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"
我们找到了罪魁祸首。
@EnableGlobalMethodSecurity(prePostEnabled = true)
之前已移至我们的 MethodSecurityConfiguration
class,我们在其中设置了自定义 PermissionEvaluator。我将其从那里移除并放回我们的 public class SecurityConfiguration extends WebSecurityConfigurerAdapter
并修复了这些东西。但我仍然觉得很奇怪,它仍然适用于某些存储库,但不适用于其他存储库。如果有人能对此有所启发,那就太好了。为了完整起见,下面的 class 是我们错误定义的地方。
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;
}
}
请注意 Spring Data Rest 用户,您无需创建 GlobalMethodSecurityConfiguration
即可使用自定义 PermissionEvaluator
。事实上,这会导致严重的伤害。
相反,只需将您的自定义 PermissionEvaluator
定义为上下文中的普通 bean,Spring 安全性就会将其拾取。
出于某种奇怪的原因,我的带有 hasPermission 表达式的 @PreAuthorize 注释是从我的一个存储库中触发的,但在另一个看起来几乎相同的存储库中却没有。
为了先解决这个问题,我启用了 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;
}
}
下面的项目存储库一切正常,我的 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 表达式。事实上,甚至整个 class 的 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 class...以防与此有关。
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;
}
}
我还在保存方法的 ProjectRoleRepository 接口上设置了断点,如果我在那里添加断点需要一段时间才能触发,但它确实触发并显示值已正确填充。
有没有什么我做的不同可能是 @PreAuthorize annotation/hasPermission 表达式没有触发 ProjectRoleRepository class 的原因?我还有 2 个具有安全性的存储库,包括我在此处显示的存储库,其中一切正常。如果需要,我可以显示更多代码,但我认为这是所有相关的。
编辑:一些代码显示在这种情况下评估器是针对 Project 而不是 ProjectRole 触发的。这是在一个 CommandLineRunner class 中,我们用它来最初用一些测试数据填充数据库。
@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"
我们找到了罪魁祸首。
@EnableGlobalMethodSecurity(prePostEnabled = true)
之前已移至我们的 MethodSecurityConfiguration
class,我们在其中设置了自定义 PermissionEvaluator。我将其从那里移除并放回我们的 public class SecurityConfiguration extends WebSecurityConfigurerAdapter
并修复了这些东西。但我仍然觉得很奇怪,它仍然适用于某些存储库,但不适用于其他存储库。如果有人能对此有所启发,那就太好了。为了完整起见,下面的 class 是我们错误定义的地方。
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;
}
}
请注意 Spring Data Rest 用户,您无需创建 GlobalMethodSecurityConfiguration
即可使用自定义 PermissionEvaluator
。事实上,这会导致严重的伤害。
相反,只需将您的自定义 PermissionEvaluator
定义为上下文中的普通 bean,Spring 安全性就会将其拾取。