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

Spring Boot Security下如何开放GraphQL introspection访问?

解决GraphQL自省请求被Spring Security拦截的问题

问题分析

你的核心问题有两个:

  • 自定义过滤器中直接调用request.getReader()会消耗请求体流,导致后续Spring Security过滤器无法读取请求体完成JWT验证,这是过滤器失效的关键原因。
  • 过滤器逻辑完全搞反:当前逻辑是仅允许自省请求通过,其他请求直接返回401,但你实际需要的是放行自省请求,其他请求继续走正常的JWT认证流程。

方案一:修复自定义过滤器

关键修改点

  1. 使用ContentCachingRequestWrapper缓存请求体,避免流被一次性消耗,保证后续过滤器能正常读取。
  2. 调整逻辑:放行自省请求和Graphiql相关资源,其他请求继续执行完整的Security过滤器链。

修复后的过滤器代码

import org.springframework.web.util.ContentCachingRequestWrapper;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class IntrospectionQueryFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {}

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        // 包装请求体,缓存以便重复读取
        ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper(httpRequest);

        if (isIntrospectionQuery(wrappedRequest) || isGraphiqlResource(wrappedRequest)) {
            // 放行自省请求和Graphiql静态资源
            chain.doFilter(wrappedRequest, response);
        } else {
            // 非自省请求,继续走正常的JWT认证流程
            chain.doFilter(wrappedRequest, response);
        }
    }

    private boolean isGraphiqlResource(HttpServletRequest request) {
        String uri = request.getRequestURI();
        return uri.equals("/graphiql") 
                || uri.startsWith("/graphiql/")
                || uri.equals("/favicon.ico");
    }

    private boolean isIntrospectionQuery(ContentCachingRequestWrapper request) throws IOException {
        if (!"POST".equalsIgnoreCase(request.getMethod()) || !"/graphql".equalsIgnoreCase(request.getRequestURI())) {
            return false;
        }

        // 从缓存中读取请求体,避免流被消耗
        String body = new String(request.getContentAsByteArray(), request.getCharacterEncoding());
        return body.contains("\"operationName\":\"IntrospectionQuery\"")
                || body.contains("__schema") // 兼容不带operationName的自省请求
                || body.contains("__type");
    }

    @Override
    public void destroy() {}
}

过滤器配置调整

确保过滤器添加在JWT认证过滤器之前(而非usernamePasswordAuthenticationFilter,因为你用的是JWT体系,可能不存在这个过滤器):

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    private final JwtAuthenticationFilter jwtAuthenticationFilter;

    public SecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter) {
        this.jwtAuthenticationFilter = jwtAuthenticationFilter;
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .csrf(csrf -> csrf.disable())
                .authorizeHttpRequests(auth -> auth
                        .requestMatchers("/graphiql/**", "/favicon.ico").permitAll()
                        .anyRequest().authenticated()
                )
                // 将自省过滤器放在JWT认证过滤器之前
                .addFilterBefore(new IntrospectionQueryFilter(), JwtAuthenticationFilter.class);

        return http.build();
    }
}

方案二:无需自定义过滤器,直接在Security配置中处理

利用Spring GraphQL提供的工具类判断自省请求,实现更简洁:

import org.springframework.graphql.server.WebGraphQlRequest;
import org.springframework.graphql.server.WebGraphQlRequestBuilder;
import org.springframework.security.web.util.matcher.RequestMatcher;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    private final JwtAuthenticationFilter jwtAuthenticationFilter;

    public SecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter) {
        this.jwtAuthenticationFilter = jwtAuthenticationFilter;
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .csrf(csrf -> csrf.disable())
                .authorizeHttpRequests(auth -> auth
                        .requestMatchers("/graphiql/**", "/favicon.ico").permitAll()
                        // 放行自省请求
                        .requestMatchers(introspectionRequestMatcher()).permitAll()
                        .anyRequest().authenticated()
                )
                .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }

    private RequestMatcher introspectionRequestMatcher() {
        return request -> {
            if (!"POST".equalsIgnoreCase(request.getMethod()) || !"/graphql".equals(request.getRequestURI())) {
                return false;
            }
            try {
                WebGraphQlRequest graphQlRequest = new WebGraphQlRequestBuilder()
                        .request((HttpServletRequest) request)
                        .build();
                return graphQlRequest.isIntrospectionQuery();
            } catch (IOException e) {
                return false;
            }
        };
    }
}

额外注意事项

  • 方案二需要Spring Boot 3.x + Spring GraphQL 1.2及以上版本,才支持WebGraphQlRequest.isIntrospectionQuery()方法。
  • 若使用Spring Boot 2.x,可复用方案一中的请求体解析逻辑来判断自省请求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 03:03:21