🙏내용에 대한 피드백은 언제나 환영입니다!!🙏
이전 글에서 세션과 JWT에 대해서 간단히 다루어 보았다.
이제, JWT에서의 권한인증을 위해 사용되는 AccessToken, RefreshToken에 대해서 알아보려고 한다.
우선 토큰을 생성하는 간단한 코드를 보겠다. (진행중인 사이드 프로젝트에서 가져온 코드이다.)
return Jwts.builder()
.subject(subject)
.claims(claims)
.issuedAt(new Date(System.currentTimeMillis()))
.expiration(new Date(System.currentTimeMillis() + expirationTime))
.signWith(secretKey)
.compact();
차례대로 간단히 설명하면,
Jwts.builder(): JWT를 생성하기 위한 빌더 객체를 생성.
subject(subject): JWT의 "sub" (subject) 클레임을 설정.
claims(claims): 추가적인 클레임들을 JWT에 추가.
issuedAt(new Date(System.currentTimeMillis())): 토큰의 발급 시간을 설정.
expiration(new Date(System.currentTimeMillis() + expirationTime)): 토큰의 만료 시간을 설정.
signWith(secretKey): JWT를 개발자가 설정한 secretKey를 통해 서명.
compact(): 모든 설정이 완료된 JWT를 문자열 형태로 변환.
토큰에 사용자의 중요한 개인정보가 아닌 정보들을 담고, 만료 시간을 설정하며 토큰의 사용시간을 설정할 수 있다.
먼저 AccessToken과 RefreshToken의 역할을 간단히 설명하겠다.
사용자를 인증하기 위해서 AccessToken을 사용한다. 클라이언트에서 AccessToken을 Authorization 헤더에 담아 서버에 보내고, 서버는 AccessToken에 대한 유효성 검사를 통해 인증을 진행한다.
RefreshToken은 AccessToken이 최초 발급될 때 같이 발급된다.
AccessToken의 만료 시간이 지나 더 이상 사용할 수 없게되면, RefreshToken에 있는 Payload에 있는 사용자의 정보를 이용하여 재발급 해주는 역할을 한다.
이러한 RefreshToken의 정보는 저장소에 저장되어 있어야하는데 RefreshToken은 자주 접근하고 다루기에 보통 Redis라는 in-memory 공간에 저장을 한다. (토큰 이후에 Redis에 대해서 알아보고자 한다.)
토큰은 한 번 발급되면 만료 시간이 지날 때 까지 계속 사용할 수 있다.
이 말은 토큰을 탈취당하게 된다면, 만료 시간이 지나기 전까지는 탈취자가 마음대로 사용할 수 있는 것이다.
그래서, AccessToken의 만료 기한을 길게한다면 탈취를 당했을 때 문제가 생길 수 있다.
이러한 문제점을 해결하기 위해서 AccessToken의 만료 시간을 짧게하고(ex. 30분~1시간) RefreshToken(만료시간 ex. 7일)을 사용하여 AccessToken을 재발급해주는 과정을 거치는 것이다.
토큰의 저장 위치는 보통 메모리, 쿠키, 스토리지 세 가지로 볼 수 있다.
공부를 하면서 내가 내린 결론은 Access Token은 메모리(로컬 변수), Refresh Token은 Cookie에 저장하는 것이다.
어디까지나 나의 생각이고 이렇게 생각한 이유는 다음과 같다.
Access Token같은 경우에는 인증에 있어 직접적인 영향을 끼친다. 또한, 탈취의 위험을 낮추기 위해 만료 시간을 짧게 한다. 그래서 새로고침하면 사라지기는 하지만, 메모리의 로컬변수에 저장하고 사용하면 보안성을 높일 수 있고, 만료시간 또한 짧기에 적절하다고 생각한다.
Refresh Token같은 경우에는 인증에 있어 직접적인 영향을 끼치진 않지만, Access Token 재발급에 있어 중요한 역할을 한다. 그래서, XSS를 방지할 수 있는 HttpOnly 속성을 적용한 쿠키에 저장을 하는 것이 적절하다고 생각한다. 추가적으로, CSRF 공격을 통해 공격을 우회하려면 요청 보내는 클라이언트의 주소를 사용하게 되는데, 응답 또한, 그 주소로 보내게 된다. 즉, Refresh Token으로 요청을 보내도 Access Token은 응답으로 오기 때문에 CSRF의 위험으로도 벗어날 수 있다.
이것이 정답이다라는 것은 아니다. 틀린 부분도 있을 것이고, 맞는 부분도 있을 것이다. 그 부분에 대해서 더 정확히 공부해 나가야겠다.
CSRF(Cross-Site Request Forgery)와 XSS(Cross-Site Scripting)이란?
- CSRF - 사용자가 의도하지 않은 요청을 보내는 것이다. 다음과 같은 예시가 있다.
- 공격자는 CSRF 공격을 위해 사용자가 방문할 수 있는 악성 웹페이지 제작.
- 사용자가 은행 웹사이트 등에 로그인된 상태에서 악성 웹페이지를 방문.
- 악성 웹페이지는 사용자가 의도하지 않은 요청을 은행 웹사이트로 전송.
- 은행 웹사이트는 이 요청을 정상적인 사용자 요청으로 처리.
- XSS - 웹 애플리케이션의 취약점을 이용하여 악성 스크립트를 삽입하는 공격. 다음과 같은 예시가 있다.
- 공격자는 XSS 공격을 위해 웹 애플리케이션의 입력 필드에 악성 스크립트를 삽입.
- 웹 애플리케이션은 이 입력을 제대로 검증하지 않고 그대로 저장하거나 출력.
- 다른 사용자가 웹 애플리케이션을 방문하여 악성 스크립트가 포함된 페이지를 열람.
- 악성 스크립트는 사용자 브라우저에서 실행되어 사용자의 쿠키를 탈취하거나, 피싱 페이지로 리다이렉트시키는 등의 악의적인 동작을 수행.
JWT는 아주 중요한 기술 중 하나라고 생각한다. 어느 서비스든 로그인 기능은 있을 것이다. 하지만, 그 정보가 악의적인 사람에 의하여 해킹을 당한다면 사용자들의 신뢰도를 떨어트리고 그 서비스는 이용가치가 떨어진다고 생각한다.
모든 공부든 그렇겠지만, 특히 JWT에 대한 공부는 끝이 없이 추가적으로 공부를 하여야겠다는 생각을 한다.