2
1

More than 1 year has passed since last update.

Spring Security + SPA(Nuxt.js)でX-XSRF-TOKENヘッダーが無くてCSRFで拒否される

Last updated at Posted at 2021-03-24

REST APIとSPAの環境構築をした際、CSRFでハマった。
CSRF用のCookieのやりとりは上手くいってたが、X-XSRF-TOKENヘッダーが送付されておらずエラーになっていた。

Spring Securityやnuxt/axiosが色々やってくれるので、背景知識が足りず深くハマってしまった。

状況

SPA

  • Nuxt.js 2.14.5
  • APIとの通信はnuxt/axiosを使用
  • S3 + CloudFrontで環境構築
    • ドメインは https://foo.com

REST API

  • Kotlin 1.4.0
  • Spring Boot 2.3.2
  • Spring Security
  • ECSで環境構築
    • ドメインは https://bar.com

Spring SecurityのCSRF設定

SecurityConfig
@EnableWebSecurity
class SecurityConfig(): WebSecurityConfigurerAdapter() {

    @Throws(Exception::class)
    override fun configure(http: HttpSecurity) {
        http
            .authorizeRequests()
            ...
            .and()
            .csrf()
            .ignoringAntMatchers("/home")
            .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())

.ignoringAntMatchers() で /homeへのCSRFを無効にしています。

  .ignoringAntMatchers("/home")

CookieCsrfTokenRepositoryを使用する際は.withHttpOnlyFalse()の記述をすると、JavaScriptからCookieを扱う事ができます。

  .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())

何が起きたか

local環境ではうまくいっていたが、ECS + S3で環境を構築をしデプロイするとCSRFではじかれた。
最初のアクセス /home(csrf無効)後に、次のアクセス /login(csrf有効)するとエラー。

Invalid CSRF Token 'null' was found on the request parameter '_csrf' or header 'X-XSRF-TOKEN'.

CSRF Tokenがnullと言われる。
Google Chrome Developer ToolsでNetworkを確認する。

最初の/home(csrf無効)のResponseのHeadersにset-cookie: XSRF-TOKEN=xxx; が返ってきて、
次の/login(csrf有効)のRequestのCookiesに、XSRF-TOKEN xxxx が入っている。

ただそのHeadersに、X-XSRF-TOKENの記載がない。
nuxt/axiosの仕様では

nuxt.config.js
axios: {
    credentials: true
}

と記載しておけば、Cookieを自動で付けてくれる。
CookieにXSRF-TOKENがいる場合は、X-XSRF-TOKENというヘッダーを生成してくれる。

なぜX-XSRF-TOKENのHeaderがないのか

JavaScriptは自身と異なるドメインのcookieを操作する事ができない。という事がわかった。
いわゆる3rd party cookieというやつの操作ができない。

今回の場合だと

フロント: https://foo.com
サーバ: https://bar.com

なので、foo.comのjsはbar.comで発行されたCookieを操作する事ができない事になる。
よって、axiosはCookieのXSRF-TOKENを、X-XSRF-TOKENというヘッダーに付け替える事ができず、nullになってしまった。

解決策

CSRF Token取得用のControllerを作成。

CsrfController
@RestController
class CsrfController {

    @GetMapping("/csrf")
    fun issueCsrfToken(csrfToken: CsrfToken): CsrfToken {
        return csrfToken
    }
}

返却するcsrfTokenの中身はこんな感じ

{
  parameterName: "_csrf",
  token: "xxx-xxxx-xxxx",
  headerName: "X-XSRF-TOKEN"
}

JavaScriptで /csrf を叩いてcsrfTokenを取得しcookieに保存。

const csrf = await this.$axios.$get('https://bar.com/csrf')
document.cookie = `XSRF-TOKEN=${csrf.token}`

このようすればJavaScriptでcookieを扱う事ができるので、
次の通信の際に、axiosがX-XSRF-TOKENヘッダーを作成してくれる。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1