ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • WEB) JWT 토큰인증 - 세션 vs 쿠키 vs 토큰
    TIL 2024. 1. 9. 15:53

     

     

     

     

     

    JWT 정의)

    ㄴJSON WEB TOKEN

    ㄴToken 기법의 한 종류(웹표준)
    ㄴJSON형태로 되어있어서. 값의 관리가 쉬움
    조작여부 판단이 간단해서 웹 표준으로 삼고있음 

     

     

     

     


    -구조
    ㄴToken이라는 것 자체가 그냥 특정한 폼을 가진 String

    "header":{
        token의 설정 정보(발행자, 만료시간, 종류 등)
    },
    "payload":{
        개발자가 Token에 넣고 싶은 값
    },
    "signature" : 비밀키로 암호화된 header + payload

    ㄴ이렇게 3가지의 Key를 String으로 변환해서 저장한게 Token

     

     

     

     

     

    -jwt가 어떻게 FE 조작을 막는가?)

    1. header와 payload를 각각 String으로 만듬
    2. A.B 의 형태로 저장
    3. A.B를 비밀키로 암호화해서 C라고 저장
    4. 최종적으로 A.B.C의 형태로 만들면 이게 Token

    A, B는 프엔도 갖고있음
    백엔은 전달받은 A.B.C를 가지고, C를 비밀키를 이용하여 복호화한뒤, A.B와 일치하는지 확인하여 토큰 조작여부 확인

    그결과: 세션처럼 서버메모리도 쓰지않고, 토큰이 조작되었는지 여부도 확인가능.

     

     

     

     

     

     

    JWT를 쓰는이유)

    http통신, 쿠키, 세션과의 차이점)

     

     

     

    1. http 통신인증

    ㄴForm태그로 페이지 호출할때마다 idx를 보내는 기법
    ㄴ개발할때 귀찮음
    ㄴ새로고침하면 idx초기화됨.
    ㄴFE에서 BE로 단순히 값을 쏴주는 것이라, 신뢰할 수 없음.

     

     

     

    2.  쿠키인증)

    ㄴ계정의 idx를 Cookie 에 담아서 사용하는 기법.

    ㄴ Cookie 는 브라우져의 메모리를 사용함.

    ㄴ Cookie 의 특징으론 http통신을 할때 자동으로 같이 전송.

    ㄴ 개발에 귀찮은 점은 해결했으나, 신뢰할 수 없다는 점을 해결하지 못했다.

    ㄴㄴ브라우저에서 조작가능하기 때문이다.

     

     

     

    3.  세션인증)

    ㄴ계정의 idx를 Cookie에 보관하지않고, Session에 보관함
    ㄴSession은 서버 메모리에 있으므로, 보안적으로 안전함 (조작x)
    ㄴ Cookie에 이 Session의 고유 id를 담아서 브라우져에 보냄.

     

     

     

    4.  토큰인증)

    ㄴSession을 쓰지 않고, 모든 값을 브라우져가 보관하게 하는 것.
    ㄴ서버의 메모리를 쓰지않아서 Cookie-session 기법보다 서버 사양이 낮아도 됨. (쓰는이유)
    ㄴ 모든 값을 브라우져가 보관하는데, 어떻게 조작이 되지 않게 할 것인가?

     

     

    토큰*

    토큰에는 개인정보를 넣지않고, 식별자키PK(idx), isAdmin 같은거만 넣는것이 좋다.

    그외 유저정보는 정보가 노출될 수 있고, 바뀔수 있는 값이기 때문이다.

    다만 isAdmin이 필요할때도 있으니 이건넣는다.

    토큰내 정보는 수정될 수 없기 때문에 유저 정보변경시, 새로운 토큰을 발급해줘야한다는 단점이 있다

     

     

     

     

    -Cookie-session VS Token

    ㄴToken기법은 서버 메모리를 사용하지않으니까, 비용적인 측면이 감소
    ㄴToken은 Ban등의 로그인 제한을 걸기가 어려움
    ㄴCookie-Session(세션) 방식은 session을 삭제하는게 사실상 로그인 해제와 동일

     

     

    세션방식은 서버에 정보를 보관( Stateful )해두고있기 떄문에 제어도 할 수 있다.

    동일계정 중복접속 방지와 같은 기능도 서버에 같은데이터가 중복되어있는지 만 보면되기때문에 세션으로만 구현가능하다

     

     



    Token기법 예시) 코로나 QR 인증

    Cookie-Session 기법 예시) 넷플릭스 4인, 유튜브 동시 송출 방지
    ㄴ중복되어 생성된 세션이있으면 기존 세션삭제함으로써 동시로그인 방지.

     

     

     

     

     

     

    Token의 한계점)

     

    -토큰은 서버에 아무 기록을 남기지 않는다(Stateless) 

     

    따라서 모든 정보가 브라우저(사용자)에게만 존재하기 때문에, 도중에 정보,권한을 수정할 수 없다. 

     

    해커가 토큰을 탈취할 경우, 해당 토큰을 무효화할 수도 없다. 

     

     

     

    Token을 보완하기위한 방법) - Access 토큰과 Refresh 토큰

     

    수명이짧은 Acess토큰과 수명이 긴 Refresh토큰 2개를 사용자에게 발급한다.

     

    매번 인가를 받을때마다 필요한 토큰이 Acess토큰이고, 이 Acess토큰을 재발급받기 위한 토큰이 Refresh토큰이다.

     

    이 Refresh토큰에 상응하는 정보들은 db에도 저장해두었다가, 이 토큰과 정보가 맞다면 새 Acess토큰을 발급해준다.

     

    이렇게할 경우, 토큰이 탈취당해도, 그 유효시간이 짧아 피해를 줄일 수 있다.

     

    그리고 특정계정을 로그아웃 시키려면 db에서 해당 refresh토큰을 지우면된다.

     

    이렇게 2가지의 토큰을 갖고 운영함으로써 문제점은 줄어들지만, 그래도 짧은 시간이나마 acess토큰이 유효할 수 있다는

     

    점에서 한계점이 있기는 하다.

     

     

     

     

     

     

     

     

     

     

     

     

     

    JWT 사용예제)

     

    npm install jsonwebtoken

    ㄴ으로 설치하기

     

     

    토큰 발급하기)

    ㄴ 로그인API

     
     
     
    // 로그인 api
    router.get("/login",
        middleware.userIDCheck,
        middleware.userPwCheck,
        async (req, res, next) => {
            const { userID, userPw } = req.body;
            const result = {
                "data": {}
            }

            try {
                const sql = "SELECT * FROM class.account WHERE id = $1 AND pw = $2";
                const values = [userID, userPw];

                const rs = await pool.query(sql, values) // pool.query에는 내부적으로 커넥션을 acquire, release하는 작업이 포함              되어있다.

                if (!rs.rows || rs.rows.length == 0) throw new Error("일치하는 회원정보없음");

                const user = rs.rows[0]
                const idx = user.idx
     

                // 토큰생성 ( jwt.sign() ), 페이로드에는 바뀌지않는값 PK을 싣는다( idx )
                // 첫번째매개변수 : 페이로드(Data)
                // 두번째매개변수 : 비밀키
                // 세번째 매개변수 : 토큰 설정정보
     
                const token = jwt.sign({
                    "idx": idx
                }, process.env.secretCode, {
                    "issuer": "stageus",
                    "expiresIn": "30m"
                })

                result.data.token = token;
     
                res.status(200).send(result);

            } catch (e) {
                next(e);
            }
        })

     

     

    생성된 토큰 )

    {
        "data": {
            "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZHgiOjM4LCJpYXQiOjE3MDQ4MDczMTIsImV4cCI6MTcwNDgwOTExMiwiaXNzIjoic3RhZ2V1cyJ9.CLVADT-3myRi8DnIRxiO6kxtpngEu8-NO9UCfFdtSI0"
        }
    }

     

     

     

    생성된 토큰을 가지고 API요청을보낼때)

    게시글 수정하기)

     

    ㄴ게시글 수정API에서 세션검사대신 토큰이용

     

     

    토큰을 이용한 로그인체크 함수)

    const jwt = require("jsonwebtoken")

    const loginCheck = (req, res, next) => {
        const { token } = req.headers

        try{
            //토큰이 없다면 에러   
            if(!token || token === "") throw new Error("no token");
     
            //토큰 해독하여 데이터(페이로드)로 전환( jwt.verify )
            //jwt.verify
            //첫번째 매개변수 : 토큰
            //두번째 매개변수 :  비밀키
            const payload = jwt.verify(token, process.env.secretCode)
           
            //req.user에 idx값 저장
            //req.user에 idx값 저장
            req.user = {
                idx : payload.idx
            }

            next();

        }catch (e) {
            res.send();
        }
    }


    module.exports = loginCheck

     

     

     
    const jwt = require("jsonwebtoken")
    const loginCheck = require("../middlewares/loginCheck")
     
    // 로그인 api
    router.get("/login",
        middleware.userIDCheck,
        middleware.userPwCheck,
        async (req, res, next) => {
            const { userID, userPw } = req.body;
            const result = {
                "data": {}
            }

            try {
                const sql = "SELECT * FROM class.account WHERE id = $1 AND pw = $2";
                const values = [userID, userPw];

                const rs = await pool.query(sql, values) // pool.query에는 내부적으로 커넥션을 acquire, release하는 작업이 포함되어있다.

                if (!rs.rows || rs.rows.length == 0) throw new Error("일치하는 회원정보없음");

                const user = rs.rows[0]
                const idx = user.idx

                // 토큰생성, 페이로드에는 바뀌지않는값 PK
                const token = jwt.sign({
                    "idx": idx
                }, process.env.secretCode, {
                    "issuer": "stageus",    토큰발행자: 스테이지어스 
                    "expiresIn": "30m"      토큰유효시간: 30분
                })

                result.data.token = token;
     

                res.status(200).send(result);

            } catch (e) {
                next(e);
            }
        })

     

     

     

     

     

     

     

     

    추천사이트 :https://jwt.io/

     

    JWT.IO

    JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties.

    jwt.io

     

    ㄴ 토큰을 해독해서 JSON 형식의 데이터로 만들수도 있고,

    ㄴ 데이터를 토큰으로 만들 수도 있다.

     

Designed by Tistory.