Spring Boot WebFluxでのSPA向けのCSRF対策
Spring SecurityでSPAアプリ(React0向けのCSRF対策を実装したときのメモ
通常のWebMVCでのCSRF対策
以下のスライドにあるようにConfigクラスで、csrfTokenRepository へ CookieCsrfTokenRepository をJSから読めるようにwithHttpOnlyFalseで登録してあげればよい
これによりgetのエンドポイントへアクセスした際のレスポンスでXSRF-TOKENというCookieが返るので、以降のリクエストの X-XSRF-TOKEN ヘッダーにXSRF-TOKENの値を設定してやればよい。
このときCookieをJSで読み取って設定する必要があるので、CookieCsrfTokenRepository を withHttpOnlyFalse でインスタンス化して登録する必要がある
reactからは cookies.get('XSRF-TOKEN');csrfトークンを取得して、リクエストする際のheaderに詰めることができる
WebFluxでのCSRF対策
WebFluxでもWebMVCと同様に csrfTokenRepository へ CookieServerCsrfTokenRepository (ここの登録するクラスが違うので注意)を withHttpOnlyFalseで登録してあげればよい
WebFluxの場合これに加えて、CookieにXSRF-TTOKENされるようにfilterなどを設定してあげる必要がある
詳細は以下のIssueを参照
WebMVCと仕様が違うことに対して不満の声が結構上がっているのでもしかしたらそのうち治るかも
CSRFトークンをCookieに設定する実装の例
code: kotlin
import org.springframework.http.ResponseCookie
import org.springframework.security.web.server.csrf.CsrfToken
import org.springframework.stereotype.Component
import org.springframework.web.server.ServerWebExchange
import org.springframework.web.server.WebFilter
import org.springframework.web.server.WebFilterChain
import reactor.core.publisher.Mono
import java.time.Duration
@Component
class CsrfHelperFilter : WebFilter {
companion object {
const val CSRF_COOKIE_NAME = "XSRF-TOKEN"
}
override fun filter(serverWebExchange: ServerWebExchange,
webFilterChain: WebFilterChain): Mono<Void> {
val key = CsrfToken::class.java.name
val csrfToken: Mono<CsrfToken> = serverWebExchange.getAttribute(key) ?: Mono.empty()
return csrfToken.doOnSuccess { token ->
val cookie = ResponseCookie.from(CSRF_COOKIE_NAME, token.token)
.maxAge(Duration.ofHours(1))
.httpOnly(false)
.path("/")
.build()
serverWebExchange.response.cookies.add(CSRF_COOKIE_NAME, cookie)
}.then(webFilterChain.filter(serverWebExchange))
}
}
SpringSecurity内でどのようにCSRFトークンチェックが行われているのか?
最初上記の実装を行って、ブラウザからCookieの内容を変更してみたところエラーにならなかったため、なにか実装を間違えたかと思った
しばらく考えて、みてHeaderで送信した値とCookie内の値に差異があるときはエラーになることがわかった
つまり、Double Submit Cookieでトークンチェックが行われていると思われる
Double Submit Cookieについては以下参照
異なるドメインからはCookieが見えない、変えられないことを前提にして、Cookie内と同じ値をHeaderやBodyで送信してCookie内のトークンとHeader or Body内のトークンが一致することを確認するやり方
異なるドメインからCookieが見えない、変えられないことを前提にしているので強度は少し弱いよねというはなしはあるので適材適所で