Spring Boot GraphQL拦截权限验证以及异常拦截封装

GraphQL 拦截权限验证和异常拦截封装是在 GraphQL 服务中常见的一种需求,用于保护 GraphQL 接口免受未经授权的访问,并提供友好的错误信息处理。

1. 拦截权限验证

GraphQL 拦截权限验证通常涉及以下步骤:

  1. 定义权限规则: 确定哪些用户或角色具有权限访问 GraphQL 接口,以及哪些接口需要进行权限验证。

  2. 创建权限验证拦截器: 编写一个拦截器来拦截传入的 GraphQL 请求,在请求到达处理程序之前进行权限验证。拦截器通常会检查请求中的用户身份信息或令牌,并根据权限规则来决定是否允许继续执行请求。

  3. 实现权限验证逻辑: 在拦截器中实现具体的权限验证逻辑,例如验证用户是否具有访问特定字段或查询的权限,或者是否具有足够的角色权限。

  4. 拦截未经授权的请求: 如果权限验证失败,则拦截器可以返回适当的错误信息,或者抛出异常以终止请求并返回错误响应。

2. 异常拦截封装

异常拦截封装用于捕获并处理在 GraphQL 接口调用过程中可能发生的异常,以提供更好的错误处理体验。这通常涉及以下步骤:

  1. 创建全局异常处理器: 编写一个全局异常处理器,用于捕获 GraphQL 请求处理过程中抛出的异常。

  2. 定义异常处理逻辑: 在异常处理器中定义如何处理不同类型的异常。这可能包括将异常转换为友好的错误消息,记录异常信息以便进行故障排除,或者向客户端返回适当的 HTTP 响应。

  3. 注册异常处理器: 将异常处理器注册到 GraphQL 服务中,以确保它能够捕获并处理请求处理过程中抛出的异常。

通过实现权限验证和异常拦截封装,可以提高 GraphQL 服务的安全性和可靠性,确保只有经过授权的用户能够访问敏感数据,并提供友好的错误信息以便于开发人员和终端用户理解和处理。

1、引用Maven依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-graphql</artifactId>
    <version>2.7.9</version>
</dependency>
<dependency>
    <groupId>com.graphql-java-kickstart</groupId>
    <artifactId>graphql-java-servlet</artifactId>
    <version>15.0.0</version>
</dependency>

2、GraphQl配置类

/**
 * @author Lucas
 * date 2024/3/22 11:55
 * description GraphQl配置类
 */
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({GraphQlSourceBuilderCustomizer.class, SecurityGraphQlStrategy.class})
public class SecurityGraphQlConfig {

    @Resource
    private SecurityGraphQlStrategy securityGraphQlStrategy;

    /**
     * 拦截graphqlSource定义, 将securityGraphQlStrategy作为query/mutation策略进行配置
     * @return  graphqlSource自定义builder
     */
    @Bean
    public GraphQlSourceBuilderCustomizer sourceBuilderCustomizer() {
        return (resourceBuilder) -> resourceBuilder.configureGraphQl(graphQlBuilder -> {
            graphQlBuilder.queryExecutionStrategy(securityGraphQlStrategy)
                    .mutationExecutionStrategy(securityGraphQlStrategy)
                    .defaultDataFetcherExceptionHandler(new SecurityDataFetcherExceptionHandler())
                    .build();
        });
    }
}

3、权限验证拦截类

/**
 * @author Lucas
 * date 2024/3/22 11:55
 * description 权限验证拦截类
 */
@Slf4j
@Component
public class SecurityGraphQlStrategy extends AsyncExecutionStrategy implements WebGraphQlInterceptor {

    private static final String HEADERS = "headers";

    /**
     * 添加graphql自定义异常处理(将异常封装为R对象, 然后返回/打印R对象)
     */
    public SecurityGraphQlStrategy() {
        super(new SecurityDataFetcherExceptionHandler());
    }

    @Override
    public CompletableFuture<ExecutionResult> execute(ExecutionContext executionContext, ExecutionStrategyParameters parameters) throws NonNullableFieldWasNullException {
        HttpHeaders headers = executionContext.getGraphQLContext().get(HttpHeaders.class);
        String token = headers.getFirst(ACCESS_TOKEN);
        //权限验证逻辑
        if (false) {
            ExecutionResult executionResult = ExecutionResultImpl.newExecutionResult()
                    .data(null)
                    .errors(List.of(new GenericGraphQLError(JSON.toJSONString(R.fail(SecurityExceptionEnum.TOKEN_HDR_ABSENT)))))
                    .build();
            return Mono.fromFuture(CompletableFuture.completedFuture(executionResult)).toFuture();
        }
        return super.execute(executionContext, parameters);
    }

    /**
     * 拦截请求并将request里的headers copy到context中
     */
    @Override
    public Mono<WebGraphQlResponse> intercept(WebGraphQlRequest request, Chain chain) {
        request.configureExecutionInput((input, builder) -> {
            return builder.graphQLContext(builder1 -> {
                builder1.of(HttpHeaders.class, request.getHeaders())
                        .of(HEADERS, request.getHeaders())
                        .build();
            }).build();
        });
        return chain.next(request);
    }
}

4、异常处理器类

/**
 * @author Lucas
 * date 2024/3/22 11:56
 * description 异常处理器类
 */
public class SecurityDataFetcherExceptionHandler implements DataFetcherExceptionHandler {

    private static final Logger logNotSafe = LogKit.getNotPrivacySafeLogger(SimpleDataFetcherExceptionHandler.class);

    @Override
    public CompletableFuture<DataFetcherExceptionHandlerResult> handleException(DataFetcherExceptionHandlerParameters handlerParameters) {
        Throwable exception = unwrap(handlerParameters.getException());
        SourceLocation sourceLocation = handlerParameters.getSourceLocation();
        ResultPath path = handlerParameters.getPath();
        Map<String, Object> arguments = handlerParameters.getDataFetchingEnvironment().getArguments();

        BusinessExceptionWhileDataFetching error = new BusinessExceptionWhileDataFetching(path, exception, sourceLocation, arguments);
        logException(error, exception);
        return CompletableFuture.completedFuture(DataFetcherExceptionHandlerResult.newResult().error(error).build());
    }

    /**
     * Called to log the exception - a subclass could choose to something different in logging terms
     * @param error the graphql error
     * @param e     the exception that happened
     */
    protected void logException(BusinessExceptionWhileDataFetching error, Throwable e) {
        String errorMsg = "Exception while fetching data (%s) : %s. ".formatted(error.getPath(), error.getMessage());
        if (e instanceof BusinessException) {
            logNotSafe.warn(errorMsg + ExceptionUtils.filterThrowableMessage(e));
        } else {
            logNotSafe.warn(errorMsg, e);
        }
    }

    /**
     * Called to unwrap an exception to a more suitable cause if required.
     *
     * @param exception the exception to unwrap
     *
     * @return the suitable exception
     */
    protected Throwable unwrap(Throwable exception) {
        if (exception.getCause() != null) {
            if (exception instanceof CompletionException) {
                return exception.getCause();
            }
        }
        return exception;
    }
}

5、异常信息捕获返回固定格式类

/**
 * @author Lucas
 * date 2024/3/22 11:53
 * description 异常信息捕获返回固定格式类
 */
public class BusinessExceptionWhileDataFetching implements GraphQLError {

    private final String message;
    private final List<Object> path;
    private final Throwable exception;
    private final List<SourceLocation> locations;
    private final Map<String, Object> extensions;

    public BusinessExceptionWhileDataFetching(ResultPath path, Throwable exception, SourceLocation sourceLocation, Map<String, Object> arguments) {
        this.path = assertNotNull(path).toList();
        this.exception = assertNotNull(exception);
        this.locations = Collections.singletonList(sourceLocation);
        this.extensions = mkExtensions(exception);
        this.message = mkMessage(exception, arguments);
    }

    private String mkMessage(Throwable exception, Map<String, Object> arguments) {
        String msg = Optional.ofNullable(exception.getMessage()).orElse("");
        var r = R.fail(99, msg.length() > 80 ? "系统暂时繁忙,请稍后重试。" : msg);
        r.setErrorData(arguments);
        return JSON.toJSONString(r);
    }

    private Map<String, Object> mkExtensions(Throwable exception) {
        Map<String, Object> extensions = null;
        if (exception instanceof GraphQLError) {
            Map<String, Object> map = ((GraphQLError) exception).getExtensions();
            if (map != null) {
                extensions = new LinkedHashMap<>(map);
            }
        }
        return extensions;
    }

    @Override
    public String getMessage() {
        return message;
    }

    @Override
    public List<SourceLocation> getLocations() {
        return locations;
    }

    @Override
    public List<Object> getPath() {
        return path;
    }

    @Override
    public Map<String, Object> getExtensions() {
        return extensions;
    }

    @Override
    public ErrorType getErrorType() {
        return ErrorType.DataFetchingException;
    }

    @Override
    public String toString() {
        return "ExceptionWhileDataFetching{" +
                "path=" + path +
                ", exception=" + exception +
                ", locations=" + locations +
                '}';
    }

    @SuppressWarnings("EqualsWhichDoesntCheckParameterClass")
    @Override
    public boolean equals(Object o) {
        return GraphqlErrorHelper.equals(this, o);
    }

    @Override
    public int hashCode() {
        return GraphqlErrorHelper.hashCode(this);
    }
}

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot中,我们可以通过自定义拦截器来实现获取头部语言参数的封装。 首先,我们需要自定义一个拦截器,在拦截器中获取头部语言参数,并将其保存在本地线程中。代码如下: ```java public class LanguageInterceptor implements HandlerInterceptor { private static final ThreadLocal<String> languageHolder = new ThreadLocal<>(); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String language = request.getHeader("accept-language"); languageHolder.set(language); return true; } public static String getLanguage() { return languageHolder.get(); } public static void clearLanguage() { languageHolder.remove(); } } ``` 然后,我们需要在Spring Boot的配置文件中注册这个拦截器,并配置它的拦截路径。代码如下: ```java @Configuration public class InterceptorConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LanguageInterceptor()).addPathPatterns("/**"); } } ``` 最后,在我们的业务逻辑中,我们可以通过调用`LanguageInterceptor.getLanguage()`方法来获取头部语言参数。代码如下: ```java @RestController public class ExampleController { @GetMapping("/") public String hello() { String language = LanguageInterceptor.getLanguage(); // do something with language return "Hello"; } } ``` 需要注意的是,在业务逻辑中,我们需要在使用完头部语言参数后,调用`LanguageInterceptor.clearLanguage()`方法来清除本地线程中的数据,以避免内存泄漏。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值