You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot资源服务器@PreAuthorize未强制Keycloak Scope权限问题

Spring Boot资源服务器@PreAuthorize忽略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这类方法级安全注解,必须显式开启注解支持才能让其生效。

具体步骤

  1. 在Spring Security配置类上添加@EnableMethodSecurity注解
    这个注解会激活方法级安全拦截器,让@PreAuthorize、@PostAuthorize等注解生效(Spring Boot 3.x及以上版本用这个注解,替代旧版的@EnableGlobalMethodSecurity)。

  2. 调整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();
        }
    }
    
  3. 确认注解写法正确性
    原生JwtAuthenticationConverter会自动把令牌中的scope转换为带SCOPE_前缀的Authority,所以@PreAuthorize("hasAuthority('SCOPE_admin')")是正确写法,和管理员令牌中的adminscope完全对应;普通用户用@PreAuthorize("hasAuthority('SCOPE_user')")即可。

验证效果

配置完成后,用普通用户(令牌含userscope)访问POST /api/files/{id}会被权限拦截,管理员用户(令牌含adminscope)可以正常访问,符合预期。

内容的提问来源于stack exchange,提问作者maxime Lassort

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 18:07:34