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

Spring+GraphQL端点可选授权及@PreAuthorize方法级权限问题

解决Spring Boot + GraphQL的「可选授权」问题

你的核心问题在于:当前的Security配置要么强制所有GraphQL请求必须携带JWT(导致公开的query/mutation无法访问),要么完全放行GraphQL端点(导致@PreAuthorize因为SecurityContext为空而失效)。要实现「端点开放但方法级权限可控」的目标,我们需要调整过滤器逻辑和Security配置,让GraphQL请求既能匿名通过,又能正确解析JWT并传递认证信息给方法级注解。

问题根源拆解

  • 如果把GraphQL端点加入permitAll,你的AuthorizationFilter不会处理该请求,导致携带JWT的请求也不会被解析,SecurityContext始终为空,@PreAuthorize自然无法生效。
  • 如果不加入permitAll,AuthorizationFilter会强制所有GraphQL请求必须带JWT,直接阻断了公开访问的需求。

解决方案步骤

1. 调整HttpSecurity配置,开放GraphQL端点但保留JWT解析

首先修改configure方法,把GraphQL端点(API_ROOT_URL)加入permitAll,同时让AuthorizationFilter处理所有可能携带JWT的请求(包括GraphQL和需要认证的REST端点):

@Override 
protected void configure(HttpSecurity http) throws Exception { 
    log.debug("configureHttpSecurity"); 

    http.csrf().disable() 
        .sessionManagement() 
        .sessionCreationPolicy(SessionCreationPolicy.STATELESS) 
        .and() 
        .authorizeRequests() 
        // 公开REST端点 + GraphQL端点允许匿名访问
        .antMatchers(permitAllEndpointList.toArray(new String[0]), API_ROOT_URL)
        .permitAll() 
        // 其他需要强制认证的REST端点(比如消息服务)
        .antMatchers(MESSAGING_ROOT_URL)
        .authenticated() 
        .and() 
        .addFilterBefore(new CustomCorsFilter(), UsernamePasswordAuthenticationFilter.class) 
        .addFilterBefore(new AuthenticationFilter(authenticationManager()), UsernamePasswordAuthenticationFilter.class) 
        // 让AuthorizationFilter处理所有请求,尝试解析JWT
        .addFilterBefore(new AuthorizationFilter(), UsernamePasswordAuthenticationFilter.class); 
}

2. 修改AuthorizationFilter,允许匿名请求通过并正确解析JWT

核心是把原来「无JWT就返回403」的逻辑改成「无JWT就放行(设置匿名上下文),有JWT就解析认证」:

@Override 
public Authentication attemptAuthentication(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws AuthenticationException, IOException, ServletException { 
    try { 
        String authorization = httpServletRequest.getHeader("Authorization"); 
        if (authorization != null && authorization.startsWith("Bearer ")) { 
            // 解析JWT并返回已认证的Authentication对象
            return getAuthentication(authorization.replace("Bearer ", "")); 
        } 
    } catch (ExecutionException e) { 
        // JWT无效时才返回403
        httpServletResponse.sendError(HttpServletResponse.SC_FORBIDDEN,"The provided token was either not valid or is already expired!"); 
        return null; 
    } catch (IOException | InterruptedException e) { 
        httpServletResponse.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,"There was a problem verifying the supplied token!"); 
        return null; 
    } 
    // 无JWT时返回匿名认证对象,让请求继续流转
    return new AnonymousAuthenticationToken("graphql-filter", "anonymousUser", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS")); 
}

注意:getAuthentication方法需要返回已认证的Authentication对象(比如UsernamePasswordAuthenticationToken,构造时传入true表示已认证),这样Spring Security才会把它存入SecurityContext。

3. 确保@PreAuthorize注解生效

首先在你的Spring Boot启动类或配置类上开启方法级安全:

@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    // ... 其他配置
}

然后在GraphQL Resolver方法上按需添加注解:

@Component
public class BookResolver implements GraphQLQueryResolver, GraphQLMutationResolver {

    // 完全公开,匿名用户可访问
    @PreAuthorize("permitAll()")
    public Book getBookById(Long id) {
        // 业务逻辑
    }

    // 仅登录用户可访问
    @PreAuthorize("isAuthenticated()")
    public List<Book> getMyBooks() {
        // 业务逻辑
    }

    // 仅管理员可访问
    @PreAuthorize("hasRole('ADMIN')")
    public Book createBook(BookInput input) {
        // 业务逻辑
    }
}

为什么这样可行?

  • GraphQL端点被加入permitAll,所以匿名请求能顺利到达Resolver层。
  • AuthorizationFilter会尝试解析所有请求的JWT:有有效JWT就设置SecurityContext为已认证用户,没有就设置为匿名用户。
  • @PreAuthorize注解会根据SecurityContext中的认证状态,判断是否允许访问该方法,完美实现「可选授权」的需求。
  • 其他需要强制认证的REST端点(比如MESSAGING_ROOT_URL),HttpSecurity的authenticated()规则会拦截匿名请求,符合原有安全要求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 07:00:48