JWT를 클라이언트에 저장하는 것은 두 가지 방식이 있다.
1. Cookie에 저장해서 매 요청에 포함시키기
2. localStorage 에 저장한 다음 토큰이 필요한 요청의 헤더나 바디에 포함시키기
쿠키라고 해서 당연히 공격에 완전히 강한 것은 아니다. HttpOnly 옵션을 사용하여 Javascript을 사용한 쿠키 조작이 불가능하게 하고, domain을 설정하여 특정 서버만 쿠키에 대한 접근을 허용할 수 있다. (XSS 공격 막기 위함)
또한 만료 기간을 설정하여 탈취 당하더라도 csrf 공격을 방지할 수 있고, path 를 통해 특정 요청에만 가능하도록 할 수 있다. 프론트와 백 서버가 HTTPS 를 사용한다면 secure를 통해 https에서만 쿠키를 보내도록 할 수 있다.
서버에서 응답을 전달할 때 ResponseCookie와 Set-Cookie 헤더를 사용하여 보안 설정된 쿠키 (HttpOnly, Secure, Domain 등)를 설정하는 것이 보안 상 낫다고 판단했다.
또한 localStorage 에는 사이트 UI 와 같은 (내가 봤던 페이지, 다크 모드 여부 등) 해커가 관심 없을만한 정보만 담는 것이 좋다.
보안 옵션과 함께 Set-Cookie로 클라이언트로 쿠키를 전송하기 위해선 헤더의 Access-Control-Allow-Credentials이 true여야 한다. 이는 cross-origin HTTP 요청에 보안 설정을 추가하는 것을 허용한다.
예를 들어 axios로 Post 요청을 한다면
axios.post(
url,
data,
{
withCredentials: true
}
)
Spring에선
@Bean
fun corsConfigurer(): WebMvcConfigurer? {
return object : WebMvcConfigurer {
override fun addCorsMappings(registry: CorsRegistry) {
registr
.allowCredentials(true)
.exposedHeaders("Set-Cookie")
}
}
}
방식으로 서로 쿠키 교환을 보안을 적용하여 주고받을 수 있다.
어쨌든 쿠키의 저장과 교환 방식에 보안 설정을 적용했더라도 언제든 공격 받을 수 있으니
공격 이후의 여파를 줄이기 위하여 유효기간이 짧은 access token과 refresh token, refresh token rotating 을 적용하고
token key 설정 시 secret key 하나 대신 public/private key 을 사용하는 것이 좋다.