GraphQL(六)登录态校验Directive

简介: GraphQL Directive(指令)是GraphQL中的一种特殊类型,它允许开发者在GraphQL schema中添加元数据,以控制查询和解析操作的行为1. `Schema`中定义`directive`2. 实现`DgsReactiveCustomContextBuilderWithRequest`接口,构建请求内全局使用的上下文对象3. 实现`SchemaDirectiveWiring`,对`Field`进行拦截校验4. `Directive`注入

GraphQL Directive(指令)是GraphQL中的一种特殊类型,它允许开发者在GraphQL schema中添加元数据,以控制查询和解析操作的行为

Directive的详细说明及使用可见GraphQL(五)指令[Directive]详解

本文将介绍通过自定义Directive实现的GraphQL登录态校验,步骤依次为:

  1. Schema中定义directive
  2. 实现DgsReactiveCustomContextBuilderWithRequest接口,构建请求内全局使用的上下文对象
  3. 实现SchemaDirectiveWiring,对Field进行拦截校验
  4. Directive注入

Schema Directive定义

"必须要登录"
directive @needLogin on FIELD_DEFINITION

type Employee {
    "雇员名称"
    employees(month: Date : [String] @needLogin
}

LoginContextBuilder

实现DgsReactiveCustomContextBuilderWithRequest接口,可以使用当前的请求信息,例如 HTTP 请求头、请求参数等构建自定义的上下文对象

在查询执行过程中,GraphQL 会将该上下文对象传递给所有的数据解析器(DataFetcher),使得数据解析器能够访问和修改上下文对象中的数据

使用DgsReactiveCustomContextBuilderWithRequest接口可以实现许多有用的功能,例如:

  • 存储用户身份验证信息,以便在数据解析器中进行鉴权
  • 将请求相关的信息(例如请求参数、请求头等)传递给数据解析器,以便数据解析器根据这些信息返回正确的数据
  • 存储请求相关的上下文信息,例如请求开始时间、请求结束时间等,以便进行性能分析和监控
@Component
public class LoginContextBuilder implements DgsReactiveCustomContextBuilderWithRequest<YyContext> {
   
    private static final Logger LOGGER = LoggerFactory.getLogger(LoginContextBuilder.class);

    @Autowired
    private ReactiveLoginService reactiveLoginService;

    @Autowired
    private HttpHeaderAuthorization httpHeaderAuthorization;

    @NotNull
    @Override
    public Mono<LoginContext> build(@Nullable Map<String, ?> map, @Nullable HttpHeaders httpHeaders, @Nullable ServerRequest serverRequest) {
   
        boolean interiorAuth = httpHeaderAuthorization.auth(serverRequest);
        if(interiorAuth){
   
            LoginContext loginContext = new LoginContext(true, IpUtil.getClientIpAddress(serverRequest)).setInteriorAuth(true);
            LOGGER.info("interior request loginContext:{}",loginContext);
            return Mono.just(loginContext);
        }else{
   
            return reactiveLoginService.getUid(serverRequest)
                    .doOnSuccess(user-> LOGGER.info("login user:{}",user))
                    .onErrorResume(e-> Mono.just(LoginUser.NOT_LOGIN_USER))
                    .map(user -> new LoginContext(user, IpUtil.getClientIpAddress(serverRequest)));
        }
    }
}

ReactiveLoginService

subscribeOn(Schedulers.boundedElastic())将该Mono订阅到一个boundedElastic调度器的线程中,这样Mono中的操作就会在该线程上执行,而不会阻塞当前的线程

Schedulers.boundedElastic()调度器是一个弹性线程池,它根据需要动态地创建和销毁线程,以适应不同的负载情况

public class ReactiveLoginService {
   
    private LoginService loginService;
    public ReactiveLoginService(LoginService loginService){
   
        this.loginService = loginService;
    }

    /**
     * 登录校验
     * @param httpServerRequest
     * @return
     */
    public Mono<LoginUser> getUid(ServerRequest httpServerRequest){
   
        return Mono.fromCallable(()->{
   
            long uid = loginService.login(new WebFluxHttpServletRequest(httpServerRequest),null);
            return uid > 0 ? new LoginUser(uid) : LoginUser.NOT_LOGIN_USER;
        }).subscribeOn(Schedulers.boundedElastic());
    }
    public void close(){
   
        loginService.close();
    }
}

Directive 实现

@Component
public class NeedLoginDirective implements SchemaDirectiveWiring {
   
    private static final Logger LOGGER = LoggerFactory.getLogger(NeedLoginDirective.class);

    public static final String NEED_LOGIN_DIRECTIVE = "needLogin";

    public NeedLoginDirective() {
   
    }

    @Override
    public GraphQLFieldDefinition onField(SchemaDirectiveWiringEnvironment<GraphQLFieldDefinition> environment) {
   
        GraphQLFieldDefinition field = environment.getElement();
        GraphQLFieldsContainer parentType = environment.getFieldsContainer();
        // 原始DataFetcher,无需修改参数值时,最后需返回原始DataFetcher的值
        DataFetcher originalDataFetcher = environment.getCodeRegistry().getDataFetcher(parentType, field);
        if (field.getDirective(NEED_LOGIN_DIRECTIVE) == null) {
   
            return field;
        }
        LOGGER.info("onField field:{} needLogin.", field);
        DataFetcher needLoginDataFetcher = dfe -> {
   
            LoginContext loginContext = DgsContext.getCustomContext(dfe);
            if (loginContext.getLoginUser().hasLogin()) {
   
                return originalDataFetcher.get(dfe);
            } else {
   
                LOGGER.info("not login return null");
                throw new NeedLoginRuntimeException();
            }
        };
        environment.getCodeRegistry().dataFetcher(parentType, field, needLoginDataFetcher);
        return field;
    }
}

Directive 注入

GraphQLSchemaConfiguration

@Configuration
public class GraphQLSchemaConfiguration {
   
    @DgsComponent
    public class SecuredDirectiveRegistration {
   

        private NeedLoginDirective needLoginDirective;
        public SecuredDirectiveRegistration(NeedLoginDirective needLoginDirective) {
   
            this.needLoginDirective = needLoginDirective;
        }

        @DgsRuntimeWiring
        public RuntimeWiring.Builder addSecuredDirective(RuntimeWiring.Builder builder) {
   
            return builder.directive(NeedLoginDirective.NEED_LOGIN_DIRECTIVE,needLoginDirective);
        }
    }
}

参考资料:

  1. GraphQL(五)指令[Directive]详解
  2. Derectives 原理
相关文章
|
JavaScript
VUE element-ui之form表单自定义验证11位手机号码(封装验证规则)
VUE element-ui之form表单自定义验证11位手机号码(封装验证规则)
3379 0
VUE element-ui之form表单自定义验证11位手机号码(封装验证规则)
|
3月前
|
存储 缓存 JSON
玩转Express(二)登录态&中间件
玩转Express(二)登录态&中间件
|
缓存 前端开发 算法
Vue3 + Nest 实现权限管理系统 后端篇(二):使用 JWT 实现注册与登录
Vue3 + Nest 实现权限管理系统 后端篇(二):使用 JWT 实现注册与登录
780 0
|
JavaScript
【分享】宜搭js代码验证组件校验结果(触发组件校验)
有时候需要手动触发校验,特别是自定义页面,校验通过才进行下一步 by 页一
1080 1
|
前端开发
怎么使用async-validator快速校验表单
怎么使用async-validator快速校验表单
382 0
|
JavaScript 安全 前端开发
Vue如何读取cookie实现路由守卫(检查用户登录状态)
一般实现路由守卫,判断用户的登录状态使用token和cookie验证两种方法,这次项目后端是给我提供的cookie验证,写到这里就记录一下,希望能帮助到你们
659 0
Vue如何读取cookie实现路由守卫(检查用户登录状态)
|
JavaScript 前端开发
将jquery validate校验框架的remote异步验证设置为同步校验
将jquery validate校验框架的remote异步验证设置为同步校验
309 0
将jquery validate校验框架的remote异步验证设置为同步校验
|
中间件 数据库
【Node.js+koa--后端管理系统】用户登录接口设计 | 登录验证 | 登录返回凭证(令牌)
【Node.js+koa--后端管理系统】用户登录接口设计 | 登录验证 | 登录返回凭证(令牌)
270 0
【Node.js+koa--后端管理系统】用户登录接口设计 | 登录验证 | 登录返回凭证(令牌)
|
存储 中间件 关系型数据库
【Node.js+koa--后端管理系统】用户注册接口设计 | 连接Mysql数据库 | 校验注册权限
【Node.js+koa--后端管理系统】用户注册接口设计 | 连接Mysql数据库 | 校验注册权限
255 0
【Node.js+koa--后端管理系统】用户注册接口设计 | 连接Mysql数据库 | 校验注册权限
|
存储 前端开发
非Vuex实现的登录状态判断封装
在项目中肯定会有用户登录状态的判断,所以我们需要封装判断登录状态,用来满足整个项目的应用,当然刚不使用封装的话,会造成耦合度高,代码冗余等结果,在项目中可能常常用到vuex状态管理来进行登录状态的存,那如果项目用不到状态管理,那就可以使用简单的封装来进行登录状态判断。
58 0