[JWT] 쿠키와 세션, JWT 시작하기

인증 vs 인가

- 인증(= 로그인) Authentication

-> 관리자든 고객이든 인증을 통해서 사이트에 가입된 사용자라는 걸 증명

 

- 인가 Authorization

: 예시) 같은 사이트 내에 관리자 / 고객에 따라 접근할 수 있는 페이지 다름, 인증 후에 페이지마다의 접근 권한을 확인

-> 인증 후에, 이 친구 혹시 이 페이지 접근 권한이 있는지 확인 하는 것이 인가!


🤔 쿠키

- 웹에서 서버와 클라이언트가 주고받는 데이터 중 하나

1) 로그인
2) 서버가 쿠키 생성
3) 브라우저가 저장
4) 이후 요청마다 쿠키를 같이 전송

- 웹 서버가 생성해서 웹 브라우저 주면, 브라우저가 자기 메모리에 저장해두고, 다음에 같은 웹서버 방문할 때 쿠키 들고 요청하러감

 

장점 :

- 서버가 저장하지 않음 => 서버 저장 공간 확보할 수 있음. 

- Stateless(상태를 저장하지 않음) => RESTful

 

단점 : 보안 취약!  

 

- Cookie에 담아서 계속 핑퐁 치기에는 보안 걱정 => 세션으로 해결

- Cookie에 중요한 정보를 담지 말고, 중요한 정보는 서버에 저장해두고 그 정보가 어디있는지 주소만 적어서 Cookie에 담음

- Cookie에 넣어서 보내기엔 너무 중요한 내용은 서버가 가진 금고에 넣어두고 그 금고 번호(Session ID)만 쿠키에 넣어서 통신


🤔 세션

로그인이 되어있는 상태

 

1) 로그인
2) 서버가 세션 생성
3) 서버 메모리(또는 DB)에 사용자 정보 저장
4) 세션 ID 발급
5) 세션 ID를 쿠키에 담아서 브라우저에 전달
6) 이후 요청 시 세션 ID를 쿠키로 전송
7) 서버가 세션 ID로 사용자 정보 조회

장점 : 보안 비교적 좋음. 

단점 : 서버가 저장 O => 서버 저장 공간 차지, Statless X

 

세션과 쿠키의 단점을 보완하기 위해 JWT를 가져옴

 


 JWT (JSON Web Token)

: JSON 형태의 데이터를 안전하게 전송하기 위한 (웹에서 사용하는) 토큰

= 토큰을 가진 사용자가 "증명"을 하기 위한 수단

cf. 토큰 : (인증용) 입장 가능한 유저 / (인가용) 관리자 권한 & 일반 유저 권한

 

장점 

- 보안에 강하다! <= "암호화"가 되어 있다

-  HTTP 특징을 잘 따랐다 = Stateless하다 =  서버가 상태를 저장하지 않음. 

- 서버 부담 줄여줄 수 있음. 

 

cf. 토큰을 발행하는 서버를 따로 만들어 줄 수도 있음

 

구조

JWT의 공식 사이트를 들어가봅시다. 

https://www.jwt.io/

 

JSON Web Tokens - jwt.io

JSON Web Token (JWT) is a compact URL-safe means of representing claims to be transferred between two parties. The claims in a JWT are encoded as a JSON object that is digitally signed using JSON Web Signature (JWS).

www.jwt.io

토큰은 암호화(Encoded)되어 있다고 했습니다. 왼쪽에 보이는 이상한 알파벳 문자열들이 바로 토큰인데요. 

이 문자열을 복호화(Decoded)돼서 오른쪽의 정보로 보여주고 있습니다. 

 

그럼 Header / Payload / Verify signature로 나눠져있는 걸 알 수 있을 겁니다.

Header : 토큰을 암호화하는 데 사용한 알고리즘, 토큰의 타입(Jwt)

payload : 사용자 정보 JSON형태 (이름.. 관리자 권환...)

verify signature : Header payload를 가지고 통체적으로 적어놓고 서명(보증) <- 만약 페이로드 값이 바뀌면, 이 서명값도 통째로 바뀌기 때문에 JWT를 믿고 쓸 수 있음.


JWT로 인증 / 인가 하는 절차


JWT 구현해보기

https://www.npmjs.com/package/jsonwebtoken  -> npm으로 다운로드

 

var jwt = require('jsonwebtoken'); //jwt 모듈 소환
var token = jwt.sign({foo : 'bar'}, 'shhhh'); 
//token 생성 = jwt 서명을 했다! (페이로드, 나만의 암호키) + SHA256(기본)

console.log(token);

 

출력값을 JWT Decoder에 넣어주면 ...

이때 shhhh라는 SECRET 값을 넣어줬기 때문에 더 수준높은 수준의 암호화가 가능합니다. 

 

이제 검증을 해봅시다.

// 검증!
// 만약 검증에 성공하면, 페이로드 값을 확인할 수 있음. 
var decoded = jwt.verify(token, 'shhhh');
console.log(decoded);

발행 시간(iat)에 따른 값이 다름 -> 서명값도 달라짐!

 


youtube Project 실습 jwt 적용

...
//jwt 모듈
const jwt = require('jsonwebtoken');

//dotenv 모듈
const dotenv = require('dotenv');
dotenv.config();

router.use(express.json());


//로그인
router.post(
    '/login', 
    [
        body('email').notEmpty().isEmail().withMessage('이메일 확인 필요'),
        body('password').notEmpty().isString().withMessage('비밀번호 확인 필요'),
        validate
    ],
    (req, res) => {
        const {email, password} = req.body

        let sql = `SELECT * FROM users WHERE email = ?`
        conn.query(sql, email,
            function(err, result){
                if(err){
                    console.log(err)
                    return res.status(400).end()
                }

                var loginUser = result[0]; 
                    
                if(loginUser && loginUser.password == password){
                    //token 발급
                    const token = jwt.sign({
                        email : loginUser.email,
                        name : loginUser.name
                    }, process.env.PRIVATE_KEY, {
                        expiresIn : '30m',
                        issuer : "kayoung"
                    });
                    
                    res.cookie("token", token, {
                        httpOnly : true
                    })

                    res.status(200).json({
                        message : `${loginUser.name}님 로그인 되었습니다.`,
                    })
                }
                else {
                    res.status(403).json({
                        message : "이메일 또는 비밀번호가 틀렸습니다."
                    })   
                }
            }
        )
})

 

Cookie

토큰은 쿠키에 담아서 보내주어야됩니다!

res.cookie("token", token)

 

403 statuscode?

서버가 요청(Request)을 받았고, 이해도 했으나, 이 페이지(리소스)를 볼 자격이 없어 인증을 거절하는 상태입니다. 

 


Secure

HTTP : http://localhost:1234/login

HTTPS(secure) : https://www.naver.com 

 

Secure: true 라고 설정하면?

HTTPS(암호화된 통신)일 때만 서버로 전송해 누군가 중간에 데이터를 가로채도(Sniffing), 암호화되어 있어서 내용을 볼 수 없습니다.

 

httpOnly

(= 브라우저의 자바스크립트로는 이 쿠키를 절대 건드리지 못하게 하고, 오직 HTTP 통신(서버랑 API 주고받을 때) 헤더에만 실어서 보낼 거니?)

  • HttpOnly: true
    • 프론트엔드 개발자가 콘솔 창에 document.cookie를 쳐도 이 쿠키는 안 보입니다.
    • 자바스크립트로 접근이 불가능합니다.
    • 하지만 서버로 API 요청을 보낼 때는 브라우저가 알아서 헤더에 쏙 넣어서 보내줍니다. 
  • HttpOnly: false (기본값)
    • 자바스크립트로 document.cookie를 통해 쿠키를 읽거나 조작할 수 있습니다.
    • 편해 보이지만, 보안상 매우 위험합니다.
      • XSS(Cross Site Scripting) : 프론트엔드 단에서,  웹 브라우저 js로 접근해 공격당할 수 있음