Spring Security OAuth, JWT를 활용한 인증 과정 개념 및 구현 총 정리(2)-JWT개념과 회원가입 및 로그인 로직 구현.

taehee kim·2023년 4월 2일
0

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의 내용은 인코딩 되는 것이지 모든 내용이 암호화 되는 것이 아니기 때문에 누구나 읽을 수 있다는 생각을 가지고 민감한 정보는 절대 포함해서는 안됩니다.

3-1. Header

  • 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

  • 개인적으로 정의한 Claim입니다.
{
  "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에 저장하는 경우

  • 장점
    • 구현이 쉽습니다.
  • 단점
    • XSS 공격에 취약합니다.

Cookie에 저장하는 경우

  • 장점
    • HttpOnly Flag로 XSS 공격을 방지할 수 있습니다.
  • 단점
    • CSRF공격에 대비해야합니다.

5-2. XSS, CSRF 공격.

5-2-1. XSS 공격이란

5-2-2 CSRF(**Cross-Site Request Forgery)** 공격이란

  • 사이트간 요청 위조, 사용자가 자신의 의지와 무관한 요청을 만들게 만드는 공격.

전제 조건과 공격 과정

CSRF 공격을 위한 조건과 과정에 대해 알아보겠습니다. CSRF 공격을 시도하기 위해선 아래와 같은 몇 가지 조건이 필요합니다.

  • 사용자가 보안이 취약한 서버로부터 이미 인증을 받은 상태여야 합니다.
  • 쿠키 기반으로 서버 세션 정보를 획득할 수 있어야 합니다.
  • 공격자는 서버를 공격하기 위한 요청 방법에 대해 미리 파악하고 있어야 합니다. 예상치 못한 파라미터가 있으면 불가능합니다.

위와 같은 조건이 만족되면 다음과 같은 과정을 통해 CSRF 공격이 수행됩니다.

  1. 사용자는 보안이 취약한 서버에 로그인합니다.
  2. 로그인 이후 서버에 저장된 세션 정보를 사용할 수 있는 sessionID가 사용자 브라우저 쿠키에 저장됩니다.
  3. 공격자는 서버에 인증된 브라우저의 사용자가 악성 스크립트 페이지를 누르도록 유도합니다.
    • 해당 악성 스크립트가 담긴 페이지를 클릭하도록 유도하는 방법은 다양한 것 같으나 몇 가지 유형을 정리하자면 다음과 같습니다.
    • 게시판에 악성 스크립트를 게시글로 작성하여 관리자 혹은 다른 사용자들이 게시글을 클릭하도록 유도합니다.
    • 메일 등으로 악성 스크립트를 직접 전달하거나, 악성 스크립트가 적힌 페이지 링크를 전달합니다.
  4. 사용자가 악성 스크립트가 작성된 페이지 접근시 쿠키에 저장된 sessionID는 브라우저에 의해 자동적으로 함께 서버로 요청됩니다.
  5. 서버는 쿠키에 담긴 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인증 과정을 어떻게 구현해야할까요?

  1. 초기 로그인 시 Access token의 경우에는 클라이언트에 body, request param등으로 전달하여 Authorization header에 ‘Bearer {token}’으로 설정하도록 합니다.
  2. Refresh Token의 경우 응답의 Set-Cookie 헤더로 쿠키로 설정하도록 하고 httpOnly secure=true, sameSite 조건을 꼭 지정해줍니다.
  3. 매 요청시 JWT검증 과정 중 Access Token이 만료되었을 시에는 Refresh Token으로부터 Access Token을 재발급 받을 수 있는 API를 요청하도록 처리합니다.
    1. 이 API내에서 User를 조회하여 정지되지 않은 유저인지 확인하고 아닌 경우 Authority를 다시 확인하여 Access Token을 생성합니다.
  4. Refresh Token마저 만료되게 되면 재 로그인 요청을 시도하게 합니다.
  5. CSRF방지를 위해 Referer체크, CSRF토큰 검증등의 과정을 추가해야합니다.

6. 기타 JWT생성 시 유의사항.

Access Token의 만료시간을 짧게 주기.

  • Refresh Token 도입.

JWT Secret key로 너무 짧은 문자열 사용

  • Brute Force 공격에 취약함.
  • secret key는 절대 노출 되서는 안되며 매우 복잡한 문자열을 활용하는 것이 좋음.

7. 구현

구현 포스팅

profile
Fail Fast

0개의 댓글