Post

Cookie, Session, JWT

✅ HTTP stateless 무상태성

HTTP는 우리를 기억하지 못한다. 따라서 HTTP의 모든 요청은 별개의 요청이다.

내가 로그인해서 이 서비스에 가입된 것을 증명해도, HTTP는 나를 기억하지 못함!
➡️ 따라서 HTTP 요청 떄마다 누군가 보내거나 보관하고 있어야 한다.

  • Cookie: client가 정보 가지고 있음
  • Session: server가 정보 가지고 있음
  • JWT 토큰: client가 정보 가지고 있음
  • 브라우저에 저장되는 작은 테스트 조각(정보)
  • (클라이언트가 어떤 상품을 보고 있는지, 어떤 상품을 장바구니에 넣었는지, 다크모드로 보고있는지 등등…)
  • 보안이 중요한 비밀번호같은 정보는 Cookie에 저장하지 않는다. ❌
  • key, value로 저장된다.
  • 정보가 Cookie에 저장되어 있으면 서버를 rerun하면 모든 정보가 사라진다.

✅ Session

Screenshot 2024-09-03 at 23 41 56

  • 세션은 쿠키를 기반으로 한다.
  • 일정 기간동안 클라이언트에게서 들어오는 요구 = 하나의 상태
  • 상태를 유지
  • 👎🏻 session is saved on server’s DB or memory, taking up lot of space
  • 👎🏻 might result in server DB, memory overhead
  • 👎🏻 session makes server scalability difficult
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@RestController
@RequestMapping("/api")
public class SessionTokenSampleController {
    @GetMapping("/set-session") //세션 만들기
    public String setSession(HttpSession httpSession){
        httpSession.setAttribute("user", "Kim");
        httpSession.setAttribute("gender", "male");
        httpSession.setAttribute("job", "actor");
        return "session set complete";
    }

    @GetMapping("/get-session") //세션 가져오기
    public String getSession(HttpSession httpSession){
        String user= (String) httpSession.getAttribute("user");
        String gender= (String) httpSession.getAttribute("gender");
        String job= (String) httpSession.getAttribute("job");
        return String.format("%s, %s, %s", user, gender, job);
    }
}

    //new Session
    //서버가 나의 세션을 관리하고 있다.
    @GetMapping("/set-session") //세션 만들기
    public String setSession2(HttpSession httpSession){
        httpSession.setAttribute("user", "Lee");
        httpSession.setAttribute("gender", "female");
        httpSession.setAttribute("job", "programmer");
        return "session set complete";
    }

    @GetMapping("/get-session") //세션 가져오기
    public String getSession2(HttpSession httpSession){
        String user= (String) httpSession.getAttribute("user");
        String gender= (String) httpSession.getAttribute("gender");
        String job= (String) httpSession.getAttribute("job");
        return String.format("%s, %s, %s", user, gender, job);
    }
  • stored at: client memory/harddisk
  • format: text
  • expire: set when saving cookie(default: when broswer ends)
  • resoruce: use client resource
  • size: 20 per domain, 4KB per cookie

Session

  • stored at: server memory
  • format: object
  • expire: when client logs out, expires when there is no response
  • resoruce: use server resource
  • size: unlimited

✅ Token

  • stateless
  • server does not remember like session
  • server issue token to client
  • client shows this token when requesting to server

정보가 토큰에 저장되어 있기 떄문에 서버를 rerun해도 정보가 남아있다!

1️⃣ 클라이언트가 로그인을 한다.
2️⃣ 서버는 클라이언트에게 토큰을 준다.
3️⃣ 토큰 안에는 암호화된 정보가 가득하다.
4️⃣ 클라이언트는 이 토큰을 들고 있다가 새로운 요청을 보낼 때 토큰도 보여준다.
5️⃣ 서버는 토큰을 검증하여 클라이언트에게 응답을 준다.

What is inside Token?

  • digital signature made by server with server’s private key
  • server can verify token’s digital signature with public key

What is the benefits of using token?

  • 👍🏻 server overhead problem solved(problem of session)
  • 👍🏻 can scale up server(problem of session)
  • 👍🏻 can be used as OAUTH
  • 👍🏻 encrypted, thus prevent forging(problem of cookie)

✅ JWT

Json Web Token
used for authorization

  • Json format을 사용한다. (문자열로 구성)
  • location: can be placed in URL, HTTP header(Json format)
  • 사용자 속성을 정의하는 claim 기반의 Web Token
  • 정보를 알아볼 수 없게 encoding되어 있다.
  • 알려지면 안되는 비밀번호같은 중요한 정보 가득❗️

1️⃣ 서버는 클라이언트를 authenticate
2️⃣ 인증이 되면, 서버는 비밀키, 공개키를 생성하고
헤더와 페이로드 인코딩한다음
− 둘을 합친 문자열비밀키로 서명하여
JWT생성
− 그리고 클라이언트에게 보낸다
3️⃣ 클라이언트는 다음 요청시 이 JWT를 서버에게 보낸다
4️⃣ 서버는 JWT의 서명을 공개키로 검증

☑️ Access Token, Refresh Token

  • access token: to authenticate
  • refresh token: when access token expires, to issue access key

☑️ JWT 구성

Header.Payload.Signature
devided with .

✔️ Header

how to verify JWT

  • 타입 typ: token type "JWT"
  • 알고리즘 alg: hash 알고리즘
  • 키 kid: key used for digital signature private/public key
1
2
3
4
5
{
	"typ" : "JWT",
	"alg" : "HS256"
    "kid" : "Key ID"
}
  • 위와 같은 JSON객체문자열로 만들고 UTF-8Base64 URL-Safe인코딩하면 아래와 같이 header 생성
1
Base64URLSafe(UTF-8('{"alg": "ES256","kid": "Key ID"}')) -> eyJhbGciOiJFUzI1NiIsImtpZCI6IktleSBJRCJ9

✔️ Payload

JWT 정보(sub, name, phoneNum, gender…)

  • JSON 형태: Claim으로 구성
  • Payload 속성들을 Claim set이라고 부름
  • registered claim: hold information about token
  • 공개 Claim
  • 비공개 Claim

What is inside payload?

  • Client information
  • token created date, time
1
2
3
4
{
    "iss": "sohee.park",
    "iat": "1586364327"
}
  • 위와 같은 JSON객체문자열로 만들고 UTF-8Base64 URL-Safe인코딩하면 아래와 같이 payload 생성
1
Base64URLSafe('{"iss": "sohee.park","iat": "1586364327"}') -> eyJpYXQiOjE1ODYzNjQzMjcsImlzcyI6ImppbmhvLnNoaW4ifQ

✔️ Signature

암호화된 정보를 풀 수 있는 코드
signed header+payload

  • use header's alg(algorithm) and private key to create signature and encode
  • 유효성 검증
  • 암호화 코드
  • 알고리즘 해쉬값
1
2
3
Base64URLSafe(Sign('ES256', '${PRIVATE_KEY}',
'eyJhbGciOiJFUzI1NiIsImtpZCI6IktleSBJRCJ9.eyJpYXQiOjE1ODYzNjQzMjcsImlzcyI6ImppbmhvLnNoaW4ifQ'))) ->
MEQCIBSOVBBsCeZ_8vHulOvspJVFU3GADhyCHyzMiBFVyS3qAiB7Tm_MEXi2kLusOBpanIrcs2NVq24uuVDgH71M_fIQGg

⭐️ JWT JAVA

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@RestController
@RequestMapping("/api")
public class SessionTokenSampleController {
    @GetMapping("/generate-token")
    public String generateToken(HttpServletResponse httpServletResponse){
        String jwt= Jwts.builder()
                .setSubject("token1") //claim
                .claim("user", "Park")
                .claim("gender", "male")
                .claim("job", "teacher")
                .compact();
        httpServletResponse.addHeader("Token", jwt); //header로 보내기
        return "Jwt set complete";
    }

    @GetMapping("/show-token")
    public String showToken(@RequestHeader("Token") String token){
        Claims claims = Jwts.parser()
                .parseClaimsJwt(token)
                .getBody();

        String user= (String) claims.get("user");
        String gender= (String) claims.get("gender");
        String job= (String) claims.get("job");
        return String.format("%s, %s, %s", user, gender, job);
    }
}

Session 🆚 JWT

JWT

👍🏻 stateless: 토큰에 정보가 저장되어 있으니 서버나 클라이언트는 자유로움
👍🏻 분산 시스템에 유리하다: 토큰에 정보가 저장되어 있으니 여러 서버, 여러 클라이언트 모두에서 분산해 사용할 수 있음
👎🏻 보안이 약하다: 토큰 탈취되면 😱
👎🏻 JWT 크기 증가: 너무너무 길어지면 JWT 자체가 길어져 속도가 느려진다.
💡 언제 적합할까 ❓ 다양한 플랫폼을 운영하는 서비스(모바일, PC)

Session

👍🏻 보안이 유리하다: 서버에 저장되어 있으니 서버만 잘 지키면 정보 안전
👍🏻 유저 관리 용이: 서버와 연결 끊어버리기만 하면 끝
👎🏻 상태 유지: 서버가 상태를 계속 유지하야하는 부담
👎🏻 서버 부담 큼: 서버가 여러 명의 정보를 가지고 있어야 하고, 서버에 문제생기면 큰일남 😱
💡 언제 적합할까 ❓ 서버 측에서 사용자 상태관리를 해야 할 때

This post is licensed under CC BY 4.0 by the author.