如何在SpringBoot中用Netflix Dgs暴露公私两个GraphQL端点?
SpringBoot + Netflix Dgs 实现公开/私有API两种方案指引
方案一:双API端点(公开/私有分离)
核心思路
通过注册两个独立的Dgs GraphQL Servlet,分别映射到不同路径,给私有端点配置认证拦截,同时限制公开端点仅加载允许的查询/变更操作。
实现步骤
注册双Servlet端点
自定义配置类,创建两个DgsGraphQLServlet实例,分别对应公开和私有API,映射到不同路径(如/public/graphql和/private/graphql)。同时为公开端点筛选仅允许的DataFetcher组件:@Configuration public class DgsMultiEndpointConfig { // 公开API Servlet:仅加载标记为@PublicApi的组件 @Bean public ServletRegistrationBean<DgsGraphQLServlet> publicDgsServlet( DgsGraphQLPublicContext publicContext) { DgsGraphQLServlet servlet = new DgsGraphQLServlet(publicContext); return new ServletRegistrationBean<>(servlet, "/public/graphql"); } // 私有API Servlet:加载所有DataFetcher组件 @Bean public ServletRegistrationBean<DgsGraphQLServlet> privateDgsServlet( DgsGraphQLContext privateContext) { DgsGraphQLServlet servlet = new DgsGraphQLServlet(privateContext); return new ServletRegistrationBean<>(servlet, "/private/graphql"); } // 公开Context:筛选公开组件 @Bean public DgsGraphQLPublicContext publicDgsContext(List<DgsComponent> allComponents) { List<DgsComponent> publicComponents = allComponents.stream() .filter(c -> c.getClass().isAnnotationPresent(PublicApi.class)) .collect(Collectors.toList()); return new DgsGraphQLPublicContext(publicComponents); } // 私有Context:使用所有组件 @Bean public DgsGraphQLContext privateDgsContext(List<DgsComponent> allComponents) { return new DgsGraphQLContext(allComponents); } } // 自定义标记注解 @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface PublicApi {}配置Spring Security拦截规则
对私有端点强制认证,公开端点直接放行:@Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth .requestMatchers("/public/graphql").permitAll() .requestMatchers("/private/graphql").authenticated() .anyRequest().denyAll() ) // 根据你的认证方式配置,比如JWT、OAuth2等 .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults())); return http.build(); } }标记公开DataFetcher
在允许公开的DataFetcher类上添加@PublicApi注解,确保只有这些组件被加载到公开端点。
方案二:单端点下的部分操作公开
核心思路
通过方法级安全或自定义拦截器,在单端点内区分公开/私有操作,无需拆分端点。
实现方式1:方法级安全注解
利用Spring Security的@PreAuthorize注解,直接在DataFetcher方法上标记权限要求:
- 开启方法级安全:
@Configuration @EnableMethodSecurity public class MethodSecurityConfig {} - 在DataFetcher中标记权限:
@DgsComponent public class PublicQueries { // 公开查询:允许所有访问 @DgsQuery(field = "publicHello") @PreAuthorize("permitAll()") public String publicHello() { return "Hello from public API"; } } @DgsComponent public class PrivateQueries { // 私有查询:仅认证用户可访问 @DgsQuery(field = "currentUser") @PreAuthorize("isAuthenticated()") public User currentUser() { // 返回当前认证用户数据 return null; } }
实现方式2:自定义Schema Directive拦截
通过GraphQL自定义指令标记公开操作,再用Dgs拦截器校验权限:
- 在GraphQL Schema中添加
@public指令:directive @public on QUERY | MUTATION type Query { publicHello: String! @public currentUser: User! } type Mutation { publicSignup(input: SignupInput!): User! @public updateProfile(input: ProfileInput!): User! } - 实现Dgs数据拦截器:
@Component public class PublicApiInterceptor implements DgsDataFetchingInterceptor { @Override public CompletableFuture<Object> intercept( DgsDataFetchingEnvironment env, DataFetcher<Object> dataFetcher) { // 检查当前操作是否标记为@public boolean isPublic = env.getFieldDefinition().getDirectives().stream() .anyMatch(d -> d.getName().equals("public")); if (isPublic) { return dataFetcher.get(env); } // 非公开操作校验认证状态 Authentication auth = SecurityContextHolder.getContext().getAuthentication(); if (auth == null || !auth.isAuthenticated()) { throw new UnauthorizedException("Authentication required for this operation"); } return dataFetcher.get(env); } }
内容的提问来源于stack exchange,提问作者justrying
相关产品推荐
相关产品推荐

