Spring Security如何通过方法级安全按角色过滤接口返回的UserCollectionDto内容
Spring Security 方法级安全完全支持该需求,可参考以下两种方案实现:
方案一:使用内置
@PostFilter注解(推荐,简单场景优先用) 该方案适合过滤逻辑可通过单条规则判断的场景,Spring Security 会自动完成集合元素的过滤。
步骤1:开启方法级安全
在 Spring Security 配置类添加注解:
@Configuration @EnableMethodSecurity // Spring Security 6+ 版本用这个 // 低版本用 @EnableGlobalMethodSecurity(prePostEnabled = true) class SecurityConfig { // 其他安全配置逻辑 }
步骤2:给控制器方法加过滤注解
通过filterTarget指定要过滤的集合字段路径,value写过滤规则:
@GetMapping("/users") // filterTarget指定要过滤的是返回值ResponseEntity中body的content集合 // 过滤规则:管理员可看所有,普通用户只能看自己创建的用户数据 @PostFilter(filterTarget = "body.content", value = "hasRole('ADMIN') or filterObject.createUser == authentication.name") fun getAllUsers() : ResponseEntity<UserCollectionDto> { return getDelegate().getAllUsers() }
如果过滤逻辑复杂,可调用自定义Spring Bean的方法做判断:
// 自定义判断逻辑的Bean @Component class UserPermissionHandler { fun canView(authentication: Authentication, user: UserDto): Boolean { // 写复杂的角色、权限判断逻辑 val roles = authentication.authorities.map { it.authority } return when { "ROLE_ADMIN" in roles -> true "ROLE_USER" in roles -> user.createBy == authentication.name else -> false } } } // 控制器注解修改为 @PostFilter(filterTarget = "body.content", value = "@userPermissionHandler.canView(authentication, filterObject)")
步骤3:同步分页total字段(可选)
如果需要让total字段值和过滤后的content数量一致,可将UserCollectionDto的total改为可变变量,再加@PostAuthorize注解更新数值:
// 修改UserCollectionDto data class UserCollectionDto( val offset: Int, val limit: Int, var total: Int, // 改为var可变 val content: MutableList<UserDto> // 改为MutableList支持PostFilter修改元素 ) // 控制器上加额外注解 @PostAuthorize("returnObject.body.also { it.total = it.content.size } != null")
方案二:自定义后置处理(适合复杂过滤逻辑)
如果需要整体修改返回的UserCollectionDto实例(比如使用不可变data class不想修改字段可变性),可以直接在@PostAuthorize中调用自定义过滤方法,完成整体替换:
// 注册过滤工具为Spring Bean @Component class ContentFilter { fun filterByRole(authentication: Authentication, dto: UserCollectionDto): UserCollectionDto { val role = authentication.authorities.firstOrNull()?.authority ?: "ROLE_USER" val filteredContent = dto.content.filter { /* 你的过滤逻辑 */ } // 用data class的copy方法返回新实例 return dto.copy( content = filteredContent, total = filteredContent.size ) } // 给SpEL调用的入口方法,修改ResponseEntity的body fun doFilter(authentication: Authentication, entity: ResponseEntity<UserCollectionDto>): Boolean { val originBody = entity.body ?: return true val newBody = filterByRole(authentication, originBody) // 用反射替换ResponseEntity的body val field = ResponseEntity::class.java.getDeclaredField("body") field.isAccessible = true field.set(entity, newBody) return true } } // 控制器方法加注解 @PostAuthorize("@contentFilter.doFilter(authentication, returnObject)") fun getAllUsers() : ResponseEntity<UserCollectionDto> { return getDelegate().getAllUsers() }
内容的提问来源于stack exchange,提问作者Farrukh Najmi
相关产品推荐
相关产品推荐

