使用JPA关联Permissions与Users表时遇Access Denied问题求助
JPA关联Permissions与Users表触发AccessDeniedException的排查与优化方案
问题背景
通过createdBy字段使用JPA关联Permissions表和Users表时,触发org.springframework.security.access.AccessDeniedException: Access Denied异常,取消关联配置后系统恢复正常。需排查异常原因,并找到更优的关联方式获取用户信息。
报错栈信息
org.springframework.security.access.AccessDeniedException: Access Denied at org.springframework.security.web.access.intercept.AuthorizationFilter.doFilter(AuthorizationFilter.java:98) ~[spring-security-web-6.1.3.jar:6.1.3] ...
相关实体类代码
User实体类(实现UserDetails)
import jakarta.persistence.*; import lombok.AccessLevel; import lombok.Data; import lombok.Setter; import org.hibernate.annotations.ColumnDefault; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.Collection; @Entity(name = "users") @Data public class User implements UserDetails { ... }
Permission实体类
import jakarta.persistence.*; import lombok.AccessLevel; import lombok.Data; import lombok.NoArgsConstructor; import lombok.Setter; import java.time.LocalDateTime; @Entity(name = "permissions") @Data @NoArgsConstructor public class Permission { ... }
异常原因分析
- 关联加载触发权限校验:当JPA自动加载关联的User实体时,User类实现了
UserDetails,其内部权限相关字段(如authorities)的加载或访问可能触发Spring Security的权限校验逻辑,若当前认证用户无对应权限则抛出异常。 - 延迟加载的上下文缺失:若使用懒加载(默认关联策略),在视图渲染阶段(OpenSessionInView模式下)延迟加载User实体时,Security的认证上下文可能已失效,导致权限校验失败。
- 实体权限注解冲突:若User或Permission实体的关联字段上标注了
@PreAuthorize等权限注解,关联加载时会触发这些注解的校验,不符合条件则拒绝访问。 - 查询未做权限过滤:关联查询时未添加用户权限过滤条件,查询到了当前用户无权访问的User数据,触发Security的全局权限拦截。
优化关联方案与解决措施
1. 懒加载+显式控制加载时机
将关联设置为懒加载,仅在需要用户信息的Service层主动加载,避免自动触发不必要的权限校验:
@Entity(name = "permissions") @Data @NoArgsConstructor public class Permission { // 存储创建者ID,用于手动加载 @Column(name = "created_by", updatable = false) private Long createdById; // 懒加载关联,仅在主动调用时加载User @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "created_by", insertable = false, updatable = false) private User createdBy; }
在Service层需要用户信息时,通过userRepository.findById(permission.getCreatedById())主动获取,确保操作在有效认证上下文内执行。
2. 使用DTO封装查询结果
避免直接关联实体,通过自定义JPQL查询仅获取所需用户字段,封装到DTO中,减少权限校验触发点:
// 定义DTO类 public class PermissionWithUserDto { private Long permissionId; private String permissionName; private String creatorUsername; private String creatorEmail; // 构造方法 public PermissionWithUserDto(Long permissionId, String permissionName, String creatorUsername, String creatorEmail) { this.permissionId = permissionId; this.permissionName = permissionName; this.creatorUsername = creatorUsername; this.creatorEmail = creatorEmail; } // Getter } // 在PermissionRepository中添加查询方法 @Repository public interface PermissionRepository extends JpaRepository<Permission, Long> { @Query("SELECT new com.example.dto.PermissionWithUserDto(p.id, p.name, u.username, u.email) " + "FROM permissions p JOIN users u ON p.createdById = u.id WHERE p.id = :id") PermissionWithUserDto findPermissionWithCreatorInfo(@Param("id") Long permissionId); }
3. 调整权限配置允许关联访问
若必须关联加载,确保当前用户拥有访问User实体的权限:
- 在User实体类或字段上添加权限注解:
@Entity(name = "users") @Data @PreAuthorize("hasAuthority('VIEW_USER_BASIC_INFO')") public class User implements UserDetails { ... }
- 确保当前认证用户被授予
VIEW_USER_BASIC_INFO权限。
4. 调整OpenSessionInView过滤器顺序
若使用OpenSessionInView,将其放在Security过滤器链之后,确保延迟加载时认证上下文有效:
@Bean public FilterRegistrationBean<OpenSessionInViewFilter> openSessionInViewFilter() { FilterRegistrationBean<OpenSessionInViewFilter> registration = new FilterRegistrationBean<>(); registration.setFilter(new OpenSessionInViewFilter()); // 设置在Security过滤器之后执行 registration.setOrder(FilterRegistrationBean.LOWEST_PRECEDENCE - 1); return registration; }
内容的提问来源于stack exchange,提问作者Khả Nam
相关产品推荐
相关产品推荐

