Spring Boot资源服务器@PreAuthorize未强制Keycloak Scope权限问题
问题概述
开发Spring Boot资源服务器,用Keycloak管理认证授权,遇到核心问题:即便用户令牌不含所需Scope,仍能访问被@PreAuthorize保护的端点。
尝试过的操作:
- 用
@PreAuthorize("hasAuthority('SCOPE_admin')")做方法级权限控制 - 用
SecurityFilterChain配置请求级权限时功能正常,但切换到@PreAuthorize后,所有用户均可无视令牌Scope访问目标端点 - 已验证令牌内容,试过
@PreAuthorize("hasAuthority('admin')")和@PreAuthorize("hasAuthority('SCOPE_admin')")两种写法,希望采用原生Scope方式,不自定义JwtAuthenticationConverter
相关代码
安全配置代码
@Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(authorizeRequests -> authorizeRequests .requestMatchers(HttpMethod.POST, "/api/files/{id}").access(hasScope("admin")) // Require 'admin' scope for POST .requestMatchers(HttpMethod.GET, "/api/files").access(hasScope("admin")) // Allow 'admin' scope for GET .requestMatchers(HttpMethod.GET, "/api/files").access(hasScope("user")) // Allow 'user' scope for GET .requestMatchers("/api/files/public-endpoint").permitAll() // Public access for specific endpoint .anyRequest().authenticated() // All other endpoints require authentication ) .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults())); return http.build(); }
控制器代码
@RestController @RequestMapping("/api/files") public class FilesController { private final FileService fileService; public FilesController(FileService fileService) { this.fileService = fileService; } @GetMapping("/public-endpoint") public String publicEndpoint() { return "This is a public endpoint!"; } @PostMapping("/{id}") @PreAuthorize("hasAuthority('SCOPE_admin')") // Requires 'admin' scope public File updateFile(@PathVariable("id") Integer id, @RequestBody File file) { if (!id.equals(file.getId())) { return null; } Optional<File> toUpdate = this.fileService.findById(id); if (toUpdate.isEmpty()) { return null; } return this.fileService.update(file); } @GetMapping @PreAuthorize("hasAuthority('SCOPE_user')") // Tried with this too public List<File> findAllFiles() { return this.fileService.findAll(); } }
Keycloak令牌内容
- 管理员用户令牌:
"scope": "openid profile admin email" - 普通用户令牌:
"scope": "openid profile email user"
解决方案
核心原因
Spring Security默认不会自动启用@PreAuthorize这类方法级安全注解,必须显式开启注解支持才能让其生效。
具体步骤
在Spring Security配置类上添加
@EnableMethodSecurity注解
这个注解会激活方法级安全拦截器,让@PreAuthorize、@PostAuthorize等注解生效(Spring Boot 3.x及以上版本用这个注解,替代旧版的@EnableGlobalMethodSecurity)。调整SecurityFilterChain配置(可选)
既然要切换到方法级权限控制,可以把请求级的具体权限规则移除,只保留基础的认证和公共端点配置,避免规则冲突:@Configuration @EnableMethodSecurity // 关键:开启方法级安全支持 public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(authorizeRequests -> authorizeRequests .requestMatchers("/api/files/public-endpoint").permitAll() // 保留公共端点配置 .anyRequest().authenticated() // 所有其他端点仅需认证,具体权限交给方法注解控制 ) .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults())); return http.build(); } }确认注解写法正确性
原生JwtAuthenticationConverter会自动把令牌中的scope转换为带SCOPE_前缀的Authority,所以@PreAuthorize("hasAuthority('SCOPE_admin')")是正确写法,和管理员令牌中的adminscope完全对应;普通用户用@PreAuthorize("hasAuthority('SCOPE_user')")即可。
验证效果
配置完成后,用普通用户(令牌含userscope)访问POST /api/files/{id}会被权限拦截,管理员用户(令牌含adminscope)可以正常访问,符合预期。
内容的提问来源于stack exchange,提问作者maxime Lassort

