미소를뿌리는감자의 코딩

[CatchLounge] JWT - 브라우저 Cookie 저장 본문

프로젝트

[CatchLounge] JWT - 브라우저 Cookie 저장

미뿌감 2024. 9. 5. 11:39
728x90

1. 개요

JWT란, Json Web Token으로, 서버에 토큰 저장의 부담을 줄여줄 수 있다.

client가 로그인을 성공하게 되면 서버에서 JWT를 반환해 준다.

 

이를 client가 session 혹은 브라우저 쿠키에 저장을 해두고, 요청 시마다 

헤더에 토큰을 포함하여 요청을 보내어, 사용자에 대한 인증을 간편히 할 수 있다.

 

 

위에 그림은 로그인 시, JWT 토큰을 서버에서 발행하여 client에게 넘겨주는 것이다.

HTTP 응답에서 Headers에 "set-cookie" : <jwt 토큰> 으로 반환해준다. (session에다가 저장하는 것도 가능하다.)

이를 통해 브라우저는 응답 받은 jwt 토큰을 쿠키에 저장해 두고, 요청을 jwt토큰을 포함해서 서버에 보낸다.

 

client에서 서버로 요청을 보낼 땐, Headers 부분에 Authorization: Bearer <JWT 토큰> 으로 반환을 해준다.

이를 서버에서 이용해서, 인증된 사용자로 판단, 편리하게 이용할 수 있다.

 

마지막으로 JWT는 header + payload + signature로 구성되어 있다.

각 부분은 . 으로 구분이 된다.

header는 JWT 타입과 이용 된, 알고리즘을 알려주며, 

Payload는 사용자에 대한 정보를 포함한다.

signature은 비밀 키 부분으로, 보안을 위해서 존재한다.

 

이때, JWT의 헤더와 HTTP 요청의 Headers는 다른 것이니, 잘 파악하도록 하자.

 

2. 코드

다음으로 JWT를 적용시킨 코드를 확인해 보자.

 

우선, /sign_in 경로에 POST 요청을 보내었다. 

 

        $.ajax({
          type: "POST",
          url: "/sign_in",
          data: {
            username_give: username,
            password_give: password,
          },
          success: function (response) {
            if (response["result"] == "success") {
              $.cookie("mytoken", response["token"], { path: "/" });
              alert("로그인 성공");
              window.location.href = "/";
            } else {
              alert("로그인 실패");
            }
          },
        });

 

@app.route("/sign_in", methods=["POST"])
def sign_in():
    username_receive = request.form["username_give"]
    password_receive = request.form["password_give"]

    password_hash = hashlib.sha256(password_receive.encode("utf-8")).hexdigest()
    result = collection_user.find_one(
        {
            "username": username_receive,
            "password": password_hash,
        }
    )
    if result is not None:
        payload = {
            "username": username_receive,
            "exp": datetime.utcnow()
            + timedelta(seconds=60 * 60 * 24),  # 로그인 24시간 유지
        }

        token = jwt.encode(payload, SECRET_KEY, algorithm="HS256")

        return jsonify({"result": "success", "token": token})
    return jsonify({"result": "failure"})

 

비밀번호의 경우 암호화를 해주고, db에서 일치하는 사용자가 있는 지, 확인한다.

있다면, payload ( 사용자에 대한 정보를 포함하는 곳 ) 에 username과 expiretime을 설정해 두고, 

나머지 jwt를 구성해 준다. 이렇게 토큰을 생성이 완료되었다.

 

json으로 token을 넘겨주었다.

success: function (response) {
            if (response["result"] == "success") {
              $.cookie("mytoken", response["token"], { path: "/" });
              alert("로그인 성공");
              window.location.href = "/";
            } else {
              alert("로그인 실패");
            }
          }

 

jQuery의 cookie 플러그인을 사용하여, 브라우저의 쿠키에 저장을 해주었다.

"token"으로 명시되어 들어온 토큰을, "mytoken"이라는 이름으로 명시를 하였다.

경로를 / 즉, 루트 경로로 설정해 두어, 저장한 쿠키를 모든 경로에서 접근할 수 있도록 하였다.

 

다음으로, client에서 다른 API로 요청을 보낼 때, 토큰을 포함해서 보내는 client측 코드를 확인해 보자.

        function leaveTable() {
            const token = getCookie("mytoken");
            $.ajax({
                type: "POST",
                url: "/cancel",
                headers: { Authorization: `Bearer ${token}` },
                data: {},
                success: function (response) {
                    if (response["result"] === "success") {
                        alert("자리가 취소되었습니다.");
                        window.location.reload();
                    }
                },
                error: function (xhr) {
                    if (xhr.status === 400) {
                        alert(xhr.responseJSON.message);
                    } else {
                        alert("예약에 실패했습니다. 다시 시도해주세요.");
                        window.location.reload();
                    }
                },
            });
        }

 

getCookie로 브라우저에 mytoken의 이름으로 저장해 두었던 token을 들고 왔다.

이후 headers에 Authorization이라는 이름으로 토큰 값을 전달해 주었다.

 

이제, 백엔드에서 cookie가 토큰이 포함되어 요청된 것을 어떻게 처리하는 지 확인해 볼 것이다.

 

@app.route("/cancel", methods=["POST"])
@token_required
def cancel_table(current_user):
    is_reserved = int(current_user.get("is_reserved", "0"))

    if is_reserved > 0:  # 예약되어있는 경우 DB에서 예약을 False로 바꾼다.
        collection_table.update_one(
            {"tableNum": is_reserved},
            {"$set": {"occupied": False, "user_name": "None", "time": None}},
        )
        collection_user.update_one(
            {"_id": current_user["_id"]}, {"$set": {"is_reserved": 0}}
        )
        emit_db_update()
    else:
        return jsonify({"result": "fail", "message": "예약된 내용이 없습니다."})

    return jsonify({"result": "success"})

 

해당 코드에서, @token_required를 주목해야 한다. token_required라는 데코레이터 함수를 이용해 주었다.

 

token_required 함수를 확인해 보자.

def token_required(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        token = None
        if "Authorization" in request.headers:
            token = request.headers["Authorization"].split(" ")[1]
        if not token:
            return jsonify({"result": "failure", "message": "Token is missing"}), 401
        try:
            data = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
            current_user = collection_user.find_one({"username": data["username"]})
        except:
            return jsonify({"result": "failure", "message": "Token is invalid!"}), 401
        return f(current_user, *args, **kwargs)

    return decorated

token_required는 인자로 함수를 받고, 함수 실행 중간에 요청이 온 f(함수)를 실행하고 이어서 token_required 함수를 실행하는 식으로 구성이 된다.

 

즉 

======token_required 실행 ======

--------cancel_table 함수 실행 -------

======token_required 실행=======

 

이 흐름으로 코드가 실행된다.

 

@wraps(f)는 functools 모듈에 있는 데코레이터이다.

이는 데코레이터로 감싸인 함수의 원래 메타데이터들을 유지하도록 도와준다.

즉, 위 예제에선 cancel_table 의 메타데이터를 유지하도록 도와주게 되는 것이다.

 

token_required 함수를 자세히 봐보자.

request.headers에 Authorization 유무와 유효성을 확인하고, 성공한다면, 

jwt.decode를 이용해서 토큰을 decode한 후, payload에 저장해 두었던 정보들을 읽어온다.

data["username"] 으로 collection에서 user를 찾아낸다.

 

이후, 

f(current_user, *args, **kwargs) 코드가 진행됨에 따라, 

인자로 current_user를 전달해주며, cancel_table 함수가 실행되게 된다. 

 

그렇다면 *args와 **kwargs는 무엇일까?
이는 함수에 전달되는 인자의 개수를 미리 알 수 없거나 여러 개의 인자를 유연하게 처리하고 싶을 때, 사용된다 (gpt왈)

 

데코레이터 함수는 감쌀 함수마다 인자가 다르기 때문에 *args와 **kwargs를 사용해 준다.

각각,

args [위치 인자] 

kwargs [키워드 인자]

라고 부른다.

같이 사용해서, 위치 혹은 키워드로 매칭을 할 수 있도록 한다.

 

이렇게 JWT와 브라우저 쿠키에 저장, 사용하는 방법에 대해서 알아보았다.

 

 

728x90