1. JWT란
- Json Web Token의 약자로 데이터를 주고 받을 수 있는 Json형식의 object를 말합니다.
- JWT는 한번 쓰여진 원본 내용이 유지되었는지 확인할 수 있는 특징이 있기 때문에 이를 활용하여 변경이 되지 않았음을 보장할 수 있다는 장점이 있습니다.
2. JWT를 인증 및 인가에 활용하는 이유.
- 앞서 말했듯 JWT는 원본 내용의 유지를 보장할 수 있습니다.
- 이를 통해 인증된 유저를 정의할 값과 인가에 활용될 권한을 유저에게 전송하는 데에 쓸 수 있고 이는 위변조가 불가능합니다.
- 이 때문에 서버 내 Session에 로그인 인증 정보를 저장하고 sessionId를 Client에게 보내주는 방식과 다르게 JWT 자체에 인증, 인가와 관련된 정보를 저장할 수 있기 때문에 서버를 Stateless하게 설계하는데에 도움을 줄 수 있다는 매우 큰 장점이 있습니다.
2-1. 개인 프로젝트에서 JWT를 사용한 이유
- Load balancer와 Auto Scaling group을 활용하여 WAS를 여러대 띄우는 구조를 가지고 있숩나더.
- Session 방식을 사용할 경우 서버내에 인증정보를 저장하기 때문에 어떤 WAS로 요청이 가느냐에 따라 인증처리가 제대로 되지않을 수 있습니다.
- 이를 해결하기 위한 방법으로는
Sticky Session
- 특정 Client의 경우 Load Balancer가 특정 WAS로만 요청을 보내주는 방식입니다.
- Session방식에서의 로그인 문제를 해결해줄 수 있지만 Load Balancer의 원래 목표인 유연하게 요청을 분산해주는 장점이 사라지게 될 수 있습니다.
Clustered Session
- DB, Redis등의 저장소를 활용하여 Session정보를 저장하여 공유하는 방식입니다.
- WAS내에 Session을 저장하지 않기 때문에 인증정보를 일관성있게 조회할 수 있고 Sticky Session을 사용할 필요가 없으며 Redis를 활용할 경우 데이터 저장소와 Session정보를 통신하면서 생길 수 있는 비용도 줄일 수 있습니다.
- 그래도 결국 Session정보를 저장해야하며 매 API호출 마다 DB와 통신하여 Session정보를 조회해야하는 점은 변하지 않습니다.
JWT의 경우
- JWT의 경우 JWT내에 인증 정보가 이미 포함되어 있기 때문에 이를 서버내에 저장할 필요가 없고 따라서 매 API호출 시 데이터 저장소에서 데이터를 조회할 비용이 사라집니다.
- 웹 뿐만 아니라 모바일 앱과도 호환이 될 때에 JWT가 더 확장성이 높습니다.
- 단점으로 생각해야할 부분은 JWT를 해독하는데에 드는 WAS내의 CPU 비용이 추가되는 부분이 있습니다.
3. 구체적인 JWT의 구조
- 기본적으로 JWT는 Header.Payload.Signature가 연달아 있는 구조로 두개의 dot을 통해 경계가 표현됩니다.
- 또한 Base64url를 통해 인코딩 되어 표현됩니다.
- JWT의 내용은 인코딩 되는 것이지 모든 내용이 암호화 되는 것이 아니기 때문에 누구나 읽을 수 있다는 생각을 가지고 민감한 정보는 절대 포함해서는 안됩니다.
- Header에는 암호화 알고리즘과 토큰의 종료가 표시됩니다.
{
"alg": "HS256",
"typ": "JWT"
}
3-2. Payload
- Payload에는 Entity에 대한 실질적인 정보인 Claim이 주로 저장됩니다.
- Claim은 알파벳 3글자 까지만 가능합니다.
Registered Claims
- 미리 정의된 Claim으로 의무는 아니지만 지정하는 것이 좋습니다.
- 인증 받은 유저를 식별할 수 있는 값이나, 권한, 유지 기간등이 저장됩니다.
- iss: 발행자, exp: 만료기간, sub: subject등
Public Claims
- JWT를 사용하는 이들끼리 정의한 클레임입니다.
Private Claims
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
3-3. Signature
- header+ payload+ secret을 특정한 암호화 알고리즘으로 암호화하여 지정한 내용입니다.
- JWT token을 요청시에 받게 되면 JWT유효성을 검증할 때에 이 내용을 쓰게 되고 만약 header, payload의 내용이 위변조 되었거나 secret키를 모르는 대상이 JWT를 발행했다면 Signater와 대조하여 확인할 수 있을 것입니다.
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
4. Authorization: Bearer
- 요청 시 JWT는 Authorization header에 Bearer {access_token}형식으로 보내어 인증합니다.
- 이렇게 보내는 이유는 RFC명세에 정해진 관습이며 Cookie를 사용하지 않기 때문에 CORS나 CSRF공격으로부터 비교적 안전할 수 있다는 장점이 있습니다.
5. Refresh Token
5-1. JWT 인증방식에서 Refresh Token을 왜 사용할까?
- JWT의 장점은 Session과 다리 인증 기능을 위해 서버에 정보를 유지하지 않고 Stateless 하게 구성 할 수 있다는 점입니다.
- 하지만 이 때문에 Token을 일단 발급하고 나면 원하는 때에 만료시키는 등의의 인증 상태에 대한 조작이 Session에 비해서 힘들다는 단점이 있습니다.
- Refresh Token발급을 통해 이러한 단점을 보완하면서도 보안성을 유지할 수 있습니다.
5-2. Refresh Token은 어떻게 구현될까?
- 로그인 시 Access Token과 함께 Refresh Token을 발급하고 Access Token의 경우 만료시간을 분단위로 짧게 유지시킵니다.
- Refresh Token의 경우 만료시간을 비교적 길게 두어 Access Token만료 시 Refresh Token으로 재 발급 하는 방식으로 구현합니다..
- 혹시나 Access Token이 탈취당하더라도 만료 시간이 적기 때문에 토큰을 탈취 당했을 때 위험이 감소합니다.
- 특정 유저를 로그아웃 시키고 싶을 경우 즉시 로그아웃 시킬 수는 없지만, 만료시간 이후 Refresh Token으로 Access Token을 재발급 할 때에 User정보를 조회하고 재발급이 가능한 유저인지 확인하는 로직을 두는 식으로 구현할 수 있습니다.
5-3. Refresh Token 저장 위치?
- Backend DB, Redis 등의 Storage에 저장하거나 Client측에 저장할 수 있습니다.
- Backend 측에 저장할 경우 JWT를 사용하는 이유인 서버를 Stateless하게 유지하려는 의도와 충돌하기 때문에 특별한 경우가 아니면 Client측에 저장하는 것이 일반적입니다.
- Client측에서는 Local Storage 혹은 Cookie에 저장할 수 있습니다.
Local Storage에 저장하는 경우
Cookie에 저장하는 경우
- 장점
- HttpOnly Flag로 XSS 공격을 방지할 수 있습니다.
- 단점
5-2. XSS, CSRF 공격.
5-2-1. XSS 공격이란
- XSS 공격은 웹사이트 관리자가 아닌 이가 웹페이지에 악성 스크립트를 삽입할 수 있는 취약점입니다.
- Script 코드를 해커가 삽입 시켜 동작하게 만드는 공격입니다.
- 이를 방지할 수 있는 방법은 기본적으로 입력, 출력값에 대한 검증이며 자세한 내용은 다음 링크를 참조해 주세요.
5-2-2 CSRF(**Cross-Site Request Forgery)** 공격이란
- 사이트간 요청 위조, 사용자가 자신의 의지와 무관한 요청을 만들게 만드는 공격.
전제 조건과 공격 과정
CSRF 공격을 위한 조건과 과정에 대해 알아보겠습니다. CSRF 공격을 시도하기 위해선 아래와 같은 몇 가지 조건이 필요합니다.
- 사용자가 보안이 취약한 서버로부터 이미 인증을 받은 상태여야 합니다.
- 쿠키 기반으로 서버 세션 정보를 획득할 수 있어야 합니다.
- 공격자는 서버를 공격하기 위한 요청 방법에 대해 미리 파악하고 있어야 합니다. 예상치 못한 파라미터가 있으면 불가능합니다.
위와 같은 조건이 만족되면 다음과 같은 과정을 통해 CSRF 공격이 수행됩니다.
- 사용자는 보안이 취약한 서버에 로그인합니다.
- 로그인 이후 서버에 저장된 세션 정보를 사용할 수 있는
sessionID
가 사용자 브라우저 쿠키에 저장됩니다.
- 공격자는 서버에 인증된 브라우저의 사용자가 악성 스크립트 페이지를 누르도록 유도합니다.
- 해당 악성 스크립트가 담긴 페이지를 클릭하도록 유도하는 방법은 다양한 것 같으나 몇 가지 유형을 정리하자면 다음과 같습니다.
- 게시판에 악성 스크립트를 게시글로 작성하여 관리자 혹은 다른 사용자들이 게시글을 클릭하도록 유도합니다.
- 메일 등으로 악성 스크립트를 직접 전달하거나, 악성 스크립트가 적힌 페이지 링크를 전달합니다.
- 사용자가 악성 스크립트가 작성된 페이지 접근시 쿠키에 저장된
sessionID
는 브라우저에 의해 자동적으로 함께 서버로 요청됩니다.
- 서버는 쿠키에 담긴
sessionID
를 통해 해당 요청이 인증된 사용자로부터 온 것으로 판단하고 처리합니다.
- 즉, 쿠키에 이미 sessionID가 담겨 있기 때문에 누군가 악성 스크립트로 요청을 강제하면 사용자의 요청과 무관한 요청이 이루어질 수 있음을 이용한 공격방법 입니다..
CSRF 방어 방법
- **Referrer 검증**
- Referer header는 Request에 포함된 요청을 하는 쪽의 웹페이지 urI를 나타낸다.
- Origin과 유사하지만 Origin은 Scheme, domain, port만을 의미하는데 반해 request path도 포함한다.
- Referer가 원하는 Referer가 아닌 경우 요청을 거부하는 방식으로 보안성을 강화할 수 있습니다.
- **CSRF 토큰 검증**
- 로그인 시 CSRF토큰을 생성하여 세션에 저장해두고 클라이언트 요청 시 이 값을 검증합니다.
- 세션을 저장해야하는 단점이 있습니다.
- 더 다양한 방법들
5-4. 위 내용들을 기반으로 Refresh Token을 어디에 저장하고 어떻게 구현할지 구체적으로 결정해 봅시다.
5-4-1. Refresh Token은 Cookie에 저장합니다.
5-4-2. XSS 공격을 방지.
- Local Storage는 XSS공격에 취약할 수 밖에 없습니다.
- Javascript코드로 탈취가 가능하기 때문입니다.
- 하지만, Cookie는 httpOnly flag를 줄 경우 Javascript코드로 조작이 불가능하기 때문에 XSS공격으로 인해 refresh token을 탈취하거나 조작하는것을 방지할 수 있습니다.
5-4-3. Cookie의 경우 CSRF에 취약하지만 이를 방지할 수 있습니다.
- 먼저 secure flag를 true로 설정하여 Https인 경우에만 브라우저에서 Cookie로 설정하여 활용하도록 합니다.
- SameSite=strict로 설정하여 CSRF를 방지할 수 있습니다.
- 이 경우 요청자와 Authorization Server가 같은 Site를 사용하는 경우에만 활용할 수 있습니다.
- 필자는 그렇지 않기 때문에 이를 none으로 설정하고 다른 방법들을 활용합니다.
- strict로 설정할 수 없는 경우 CORS Origin을 특정하여 다른 사이트에서의 요청이 불가능하도록 방지합니다.
- CORS는 요청한 서버의 Origin이 요청자의 Origin과 같지 않은 경우 서버에서 설정하여 응답 헤더로 적용된 Access-Control-Allow-* 헤더를 브라우저가 보고 응답 허용 여부를 결정하는 정책이다.
- 허용 Origin(프로토콜, 도메인, 포트의 종합), method, header등을 나누어서 설정할 수 있고 브라우저가 서버로 부터 응답을 받고 난 후 CORS 통과 여부를 결정한다.
- cors에 대한 자세한 내용 링크
- CORS로 방지하는 경우 한계점은 브라우저에서 응답을 받지 않는 것이지 요청 자체가 서버에서 처리되지 않는것은 아니기 때문에 좀 더 완벽한 CSRF방지를 위해서는 위에서 CSRF방지에 쓰인 여러 방법을 종합적으로 고려해야 합니다.
5-4-4. JWT인증 과정을 어떻게 구현해야할까요?
- 초기 로그인 시 Access token의 경우에는 클라이언트에 body, request param등으로 전달하여 Authorization header에 ‘Bearer {token}’으로 설정하도록 합니다.
- Refresh Token의 경우 응답의 Set-Cookie 헤더로 쿠키로 설정하도록 하고 httpOnly secure=true, sameSite 조건을 꼭 지정해줍니다.
- 매 요청시 JWT검증 과정 중 Access Token이 만료되었을 시에는 Refresh Token으로부터 Access Token을 재발급 받을 수 있는 API를 요청하도록 처리합니다.
- 이 API내에서 User를 조회하여 정지되지 않은 유저인지 확인하고 아닌 경우 Authority를 다시 확인하여 Access Token을 생성합니다.
- Refresh Token마저 만료되게 되면 재 로그인 요청을 시도하게 합니다.
- CSRF방지를 위해 Referer체크, CSRF토큰 검증등의 과정을 추가해야합니다.
6. 기타 JWT생성 시 유의사항.
Access Token의 만료시간을 짧게 주기.
JWT Secret key로 너무 짧은 문자열 사용
- Brute Force 공격에 취약함.
- secret key는 절대 노출 되서는 안되며 매우 복잡한 문자열을 활용하는 것이 좋음.
7. 구현
구현 포스팅