Spring BootとGraphQLContextを連携させる

Spring Boot上にGraphQLを構築するとクエリによっては認証させたいことがあると思います。
その時に役に立つのがGraphQLContextですがあまり情報が英語・日本語含めて少なかったので今回まとめてみました。

## GraphQLContextとは
名前そのままのでリクエストからGraphQLからのContextを作成できるためのインターフェイスです。
これを使うことで認証情報やロール権限などをContext経由で取得することが出来ます。

## 依存ライブラリ

まずGraphQLの依存ライブラリに何を指定すればいいのか、色々と情報がバラバラでしたが`graphql-spring-boot-starter`を入れれば大丈夫そうです。

参考のURL：
https://github.com/graphql-java-kickstart/graphql-spring-boot

```kotlin:build.gradle.kt
dependencies {
    implementation("com.graphql-java-kickstart:graphql-spring-boot-starter:8.0.0")
    runtimeOnly("com.graphql-java-kickstart:playground-spring-boot-starter:8.0.0")
    testImplementation("com.graphql-java-kickstart:graphql-spring-boot-starter-test:8.0.0")
}
```

## 目的を整理する
基本的にGraphQLと認証の組み合わせが必要になるのはモバイルアプリやSPAで構築されたアプリからのリクエストだと思います。
今回はHttp Headerに認証トークンを設定した上でリクエストさせたいと思います。

## GraphQLからHttpServletRequestを取得する

`HttpServletRequest`を取得するには以下のコードになります。

```kotlin
import graphql.kickstart.tools.GraphQLMutationResolver
import graphql.schema.DataFetchingEnvironment

@Component
final class GraphQLMutationResolver: GraphQLMutationResolver {
    fun registerUser(input: User, environment: DataFetchingEnvironment): User {
       val context = environment.getContext<DefaultGraphQLServletContext>()
       val request: HttpServletRequest = context.getHttpServletRequest()
    }
}

```

`environment: DataFetchingEnvironment`は`Query` or `Mutation`で指定したメソッドの引数の最後に自動的に付与されます。
また、`DefaultGraphQLServletContext`は`graphql-java-servlet`に含まれるクラスになっていて、`graphql-java`とservletの橋渡しを提供しているライブラリになります。
DefaultGraphQLServletContextはGraphQLContextを実装したクラスであるので、`environment.getContext`で利用できます。

## 独自のGraphQLContextを作成する
毎回DefaultGraphQLServletContextからヘッダー情報を取り出して認証させるのは手間です。そのため独自のGraphQLContext作成したいと思います。

初めにGraphQLContextを生成するための`AuthGraphQLBuilder`を作成します。
内部ではDefaultGraphQLServletContextを使ってHeaderからトークンを取り出しています。そして、取り出したトークンをもとに独自のAuthContextを作成します。

```kotlin:AuthGraphQLBuilder.kt

import graphql.kickstart.execution.context.GraphQLContext
import graphql.kickstart.servlet.context.DefaultGraphQLServletContext
import graphql.kickstart.servlet.context.GraphQLServletContext
import graphql.kickstart.servlet.context.GraphQLServletContextBuilder

@Component
final class AuthGraphQLBuilder: GraphQLServletContextBuilder {
    val AUTHORIZATION = "Authorization"

    override fun build(httpServletRequest: HttpServletRequest?, httpServletResponse: HttpServletResponse?): GraphQLContext {
        val context = DefaultGraphQLServletContext.createServletContext().with(httpServletRequest).with(httpServletResponse).build()
        val accessToken: String? = context.httpServletRequest.getHeader(AUTHORIZATION)
        return AuthContext(formatAccessToken(accessToken), context)
    }

    override fun build(session: Session?, handshakeRequest: HandshakeRequest?): GraphQLContext {
        throw IllegalStateException("not support")
    }

    override fun build(): GraphQLContext {
        throw IllegalStateException("not support")
    }

    private fun formatAccessToken(rawToken: String?): String? {
        return rawToken?.replace("Bearer ", "")
    }
}
```

処理の内容は簡単でverifyTokenの中で認証処理を実行させているだけです。

```kotlin:AuthContext.kt

class AuthContext(accessToken: String?, private val context: GraphQLServletContext): GraphQLServletContext {

    init {
        accessToken?.also { token ->
            verifyToken(token)
        }
    }

    private fun verifyToken(accessToken: String) {
        // 認証処理をする
    }

    override fun getSubject(): Optional<Subject> {
        return context.subject
    }

    override fun getDataLoaderRegistry(): Optional<DataLoaderRegistry> {
        return context.dataLoaderRegistry
    }

    override fun getFileParts(): MutableList<Part> {
        return context.fileParts
    }

    override fun getParts(): MutableMap<String, MutableList<Part>> {
        return context.parts
    }

    override fun getHttpServletRequest(): HttpServletRequest {
        return context.httpServletRequest
    }

    override fun getHttpServletResponse(): HttpServletResponse {
        return context.httpServletResponse
    }
}
```

最後は使い方が簡単です。以下のようにAuthContextを指定してあげれば認証済みのContextを取得できます。

```kotlin
val authContext = environment.getContext<AuthContext>()
```

今回GraphQLContextについて色々と調べてみたのですが、情報が古かったりして参考に出来ることが出来なくて非常に困りました。
また、公式ドキュメントも見つからなかったので大変でした。

今回参考になったのは下のYoutubeの動画でした
https://www.youtube.com/watch?v=YsM2VSnWUcg&t=421s