WEB

[JWT]쿠키/세션/JWT 비교, jwt 구현 (nodejs)

쥬쥬코드 2024. 10. 29. 23:18

보통 로그인을 구현할 때, 토큰을 많이 사용하곤 한다. 

JWT 토큰을 대부분 사용하는데 이 토큰에 대해 알아보도록 하자.

 

우선 쿠키와 세션에 대해 알고 있나요?

 

쿠키

client가 웹사이트에 접속 시 그 사이트가 사용하게 되는 일련의 작은 기록

 

(클라이언트)ID/PW로 로그인 요청 → (서버)쿠키에 정보 저장 → (서버)정보 전달

로그인 요청을 할 때 마다 받은 쿠키를 던져서 요청을 함

다른 요청을 할 때에도 기존 쿠키로 ID, PW까지 동시에 클라이언트가 날림

 

단점

  • 노출 시 민감 정보까지 다 노출이 되어 보안이 안좋음
  • 브라우저마다 쿠키 지원 형태가 달라 브라우저 간의 공유 불가능
  • 쿠키 사이즈가 4KB로 제한되어있음
  • 서버는 매번 id, pw를 받아서 인증해야하는 불편함이 있음
  • 조작된 데이터가 넘어오는 경우를 방지할 수 없음

세션

키를 주고 자물쇠를 여는 형식

무언가에 대한 특정 인증 정보를 서버가 가지고 있고, 그 값을 클라이언트에게 전달하는 방식으로 인증

(세션 ID를 특정 저장소에 저장하여 사용)

 

 

JWT ??

개인정보 노출 시의 단점 보완하기 위해 나옴

쿠키의 정보 저장체 자체를 주고 받는다는 사실은 바뀌지 않음

BUT 민감한 정보를 다뤄야하는 상황(ex. 로그인)에서 보완점을 찾기 위해 나온 것

 

1. 클라이언트가 id pw로 서버에 로그인 요청
2. id pw 인증 후 세션 ID 발급 → 세션 저장소에 저장
3. 세션 ID를 특정한 형태(쿠키/json)로 클라이언트에게 다시 반환
4. 이후 사용자 인증이 필요한 정보를 요청할 때마다 이 세션 ID를 쿠키에 담아 서버에 함께 전달
5. 인증이 필요한 api면, 서버는 세션 ID가 세션 저장소에 있는지 확인 6. 있다면 인증 완료 후 api 처리, 없다면 401 에러 반환

 

만약 보안 문제가 발생하면 세션 저장소만 삭제하면 끝.

⇒ 근데 만약 세션 저장소가 다른 이유로 장애가 난다면 정상적인 사용자가 인증을 못할 수도 있음

 

단점

  • 세션 저장소에 문제가 발생하면 인증 체계가 무너져 이전에 인증된 유저도 인증이 불가해짐
  • 세션 저장소가 필수적으로 존재하므로 이를 사용하기 위한 비용이 듬
  • 세션 ID가 탈취되면 대처는 가능하지만 클라이언트인척 위장하는 보안의 약점이 있을 수 있음
  • 사용자가 많아질수록 메모리를 많이 차지하게 됨
  • “매번” 요청시 세션 저장소를 조회해야함

 


 

JWT (JSON Web Token)

인증에 필요한 정보들을 Token에 담아 암호화시켜 사용하는 토큰 (하나의 인터넷 표준 인증 방식)

기본적인 인증을 진행하는 구조는 Cookie와 크게 다르지 않음.

 

BUT JWT는 서명된 토큰 !

공개/개인 키를 쌍으로 사용하여 토큰에 서명할 경우, 서명된 토큰은 개인 키를 보유한 서버가 이 서명된 토큰의 정상적인 토큰인지 인증할 수 있음

 

JWT 구조

  • Header
  • Payload
  • Signature

1. Header : 타입이나 서명 생성에 어떤 알고리즘이 사용되었는지 저장

 

2. Payload : 사용자(토큰)에 대한 property를 key-value의 형태로 저장

나는 userid, iat, exp 넣음

 

[ V 표준 스팩 요소 V ]

더보기
  1. iat(issued at): 해당 토큰이 발급된 시간
  2. exp(Expiration Time): 해당 토큰의 만료 시간
  3. aud(Audience): 해당 토큰을 발급받을 대상
  4. sub(Subject): 토큰 제목
  5. nbf(Not Before): 해당 토큰의 활성화 날짜. 이 시간 이전에는 해당 토큰을 사용할 수 없음을 보장한다
  6. iss(issuer): 토큰 발급자
  7. jti(JWT id): 토큰의 식별자. 여러 issuer가 토큰을 발급할 경우 이를 구분하기 위한 값이다

페이로드에는 무조건 남에게 보여져도 상관 없는 데이터만 담는 것이 좋다.

 

jwt.io 에서 서버에서 생성한 JWT를 넣기만하면 아래와 같이 다 보임

 

그래서 JWT는 단순히 “식별을 하기 위한” 정보만을 담아두어야 한다.

header와 payload는 특별한 암호화 없이 흔히 사용할 수 있는 base64 인코딩을 사용하기 때문에 서버가 아니더라도 그 값을 확인할 수 있다.

 

헤더와 페이로드까지 암호화를 해야하지 않나? 에 대해서는 필요없는 행위임.

민감한 정보를 막을때만 사용해야하는 암호화의 과정 자체가 많은 리소스를 사용하므로 신중하게 사용해야함. 매 http 요청시 복호화를 계속 해야하니까..

 

3. Signature

Header를 디코딩한 값 + payload를 디코딩한 값 + your-256-bit-secret(서버가 가지고 있는 개인키)

⇒ 서버가 가지고 있는 개인 키를 가지고 암호화 되어있는 상태

 

  • JWT 토큰을 클라이언트가 서버로 요청과 동시에 전달
  • 서버가 가지고 있는 개인키를 가지고 Signature을 복호화한 다음 base64UrlEncode(header)가 JWT의 header와 일치한지, base64UrlEncode(payload)와 일치한지 확인하여 일치한다면 인증을 허용함

 

만약 클라이언트가 payload에 담긴 식별자가 변조된 JWT로 요청을 하더라도 서버가 애초에 발급했던 Signature 안의 payload와 다르기 때문에 인증 불가

 

다른사람이 토큰을 위조하거나 헤더값을 변조해도 시그니쳐 정보와 다르거나 검증하는 측에서 복호화가 불가능해서 위변조 된 토큰인지를 쉽게 구분 가능

 

장점

  • JWT 자체가 이미 인증된 정보라 이를 저장하기 위한 저장소를 따로 필요로 하지 않음(인증 서버가 터져서 다른 서비스를 모두 못쓰게 되는 등의 일을 걱정할 필요 X)
  • 세션과 같이 서버에서 클라이언트 상태를 저장해 둘 필요가 없음
  • 암호화한 시그니처를 통해 위변조를 쉽게 알아낼 수 있어 보안성 좋음
  • JSON이라는 형태로 존재하므로 범용성 좋음

 

 


 

 

JWT 사용 (nodeJS)

[login.js]

const jwt = require('jsonwebtoken');
const YOUR_SECRET_KEY = process.env.SECRET_KEY;

router.post('/login', async (req, res) => {
    const { userid, password } = req.body;
    if (!userid || !password ) {
        return res.status(400).send({ message: 'id, password는 필수입력 사항입니다.' });
    }
    let user = new reg_model(userid, password);
    const checklogin = await user.login();
    console.log("[login.js > post > login]",user);
    
    if(checklogin){
        const token = jwt.sign({        
            userid: user.userid      
        }, YOUR_SECRET_KEY, {
            algorithm: 'ES256',      //비대칭 암호화 알고리즘 사용  
            expiresIn: '1h'      
        });
        return res.send({ 
            message: '로그인 성공',
            token
        });
    }
    else{
        return res.status(400).send({ message: '로그인 실패' });
    }
    
    
})

module.exports = router;

 

 

비대칭 암호화 알고리즘은 반드시 개인키와 공개키 한 쌍이 필요한데,

sign 함수는 어떤 것이 공개키이고, 어떤 것이 개인키인지 여부를 start line 플래그를 통해 확인한다

 

ECDSA: https://dinochiesa.github.io/jwt/

위의 사이트에서 키 발급

 

다시보기 버튼 누르고 발급받은 내용을 env에 저장

 

위에서 발급받은 키로 수정해준다

const PRIVATEKEY = process.env.PRIVATEKEY;
const PUBLICKEY = process.env.PUBLICKEY;
const token = jwt.sign({        
            userid: user.userid      
        }, PRIVATEKEY, {
            algorithm: 'ES256',        
            expiresIn: '1h'      
        });

'WEB' 카테고리의 다른 글

스프링 객체지향과 다형성  (0) 2024.11.25
서블릿(Servlet)  (0) 2024.11.15
[Tomcat] Web Server와 WAS, Apache Tomcat  (8) 2024.11.10
[pandas] csv 데이터 api 연동, 처리  (2) 2024.11.03
스프링 의존성 주입에 관하여  (2) 2024.10.27