본문 바로가기
Node.js

12/25 로그인(인증) 세션 만료

by 케이리케리 2023. 12. 25.

로그인(인증) 세션 만료

 

먼저 인증과 인가에 대해 알아보자.

  • 인증 (= 로그인)
    Authentication
    로그인 왜 필요한가? 로그인을 통해 가입된 유저인지를 확인 -> 특정 기능 사용 가능
    쇼핑몰에서 상품을 볼 때는 로그인 필요X (단순히 데이터를 조회하는 용도, 크리티컬한 데이터가 아닌 공개적인 데이터)
    쇼핑몰에서 장바구니 담을 때 로그인 필요O
    쇼핑몰 상품 구매 시 로그인 필요O
    마이 페이지 로그인 필요O
    ...
  • 인가
    Authorization 권한 , 허가 부여
    ex. 같은 사이트 내에 관리자/고객에 따라 접근할 수 있는 페이지가 다름

인증 👉 관리자든 고객이든 인증을 통해 사이트에 가입된 사용자라는 것을 증명하는 것

인가 👉인증 후에 로그인한 사용자가 해당 페이지에 접근 권한이 있는가를 확인하는 것

 

서버가 클라이언트 인증을 확인하는 방법

쿠키 vs 세션 vs JWT

 

  • 쿠키
    1) 로그인하면 -> 서버가 쿠키를 굽는다. (쿠키 생성)
    2) 사용자 <-> 서버가 쿠키를 핑퐁
    장점 :  서버가 저장 X => 서버 저장공간 절약, Stateless(=> RESTful)
    단점 : 보안 취약

 

  • 세션
    Cookie에 중요한 정보를 담지말고, 중요한 정보는 서버에 저장하자. 그 정보가 어딨는지 주소만 적어서 Cookie에 담자.
    쿠키에 넣어서 보내기엔 너무 중요한 내용은 서버가 가진 금고(Session)에 넣어두고 그 금고번호(Session ID)만 쿠키에 넣어 통신한다.
    1) 로그인하면 -> 서버가 금고를 만들어서 정보를 저장하고 그 금고에 번호를 부여한다.
    2) 사용자 <-> 서버가 번호만 가지고 대화(통신)
    장점 : 보안 비교적 개선
    단점 : 서버가 저장 O => 서버가 저장공간을 차지, Stateless X

세션은 상태를 의미하는데 주로 웹개발에서는 로그인이 되어있는 상태를 말한다.

 

쿠키와 세션의 단점을 보완해서 나온 것이 JWT이다.

  • JWT(JSON Web Token)
    개념 : JSON 형태의 데이터를 안전하게 전송하기 위한 웹에서 사용하는 토큰
    = 토큰을 가진 사용자가 증명을 하기위한 수단
    cf. 토큰은 (인증용) 입장 가능한 유저인지 확인 / (인가용) 관리자 권한&일반 유저 권한 구분을 목적으로 사용한다.

    장점 :
    - 보안에 강함 <= 암호화가 되어있다.
    - HTTP 특징을 잘 따른다. = Stateless하다. <= 서버가 상태를 저장하지 않는다.
    - 서버에 부담을 줄여줄 수 있다.
    + 토큰을 발행하는 서버를 따로 만들어 줄 수도 있다.

    구조 (jwt.io 참고)
    - 헤더(Header) : 토큰을 암호화하는데 사용한 알고리즘, 토큰의 형태(jwt)
    - 페이로드(Payload, 내용) : 사용자 정보 담겨있다(ex. 이름, 주소, 핸드폰, ... 원하는 정보 담으면 되지만 비밀번호를 담는 경우는 잘 없긴함)
    - 서명(Signature) : 만약 페이로드 값이 바뀌면 이 서명값이 통째로 바뀌기때문에 JWT를 믿고 쓸 수 있다.

JWT 인증/인가 절차

 

JWT 사용하기 위한 node.js에 설치 명령어 (jwt 설치 및 사용방법)

npm i jsonwebtoken

 

token 생성 (= token 서명을 했다.)

👉 jwt.sign(payload, secretOrPrivateKey, [options, callback])

var jwt = require('jsonwebtoken'); //jwt 모듈 소환
//토큰 생성
var token = jwt.sign({ foo: 'bar' }, 'shhhhh'); //jwt.sign(페이로드, 나만의 암호키) + 알고리즘은 SHA256

console.log(token);

 

// console.log(token);의 출력값
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpYXQiOjE3MDM0Nzc0MzV9.Fe_3Xd1UIkA2gMoXzjhfd9m1aN-ugVaqY_8tnqdHsTA

출력값을 보면 jwt의 구조를 알 수 있다. 

XXXX.YYYY.ZZZZ 👉 header.payload.signature

 

jwt.io에서 토큰 해독을 통해 ".(dot)"을 기준으로 각 영역에 담긴 정보를 알 수 있다.

jwt.io에서 토큰 해독

 

 

검증 (검증 성공하면 페이로드 값을 확인할 수 있음) 👉 jwt.verify(token, secretOrPublicKey, [options, callback])

let decoded = jwt.verify(token, "shhhhh");
console.log(decoded); //{ foo: 'bar', iat: 1703477435 }
console.log(decoded.foo); //bar

 

 

 

.env (environment: 환경 변수 즉, '설정 값')

개념 :

개발을 하다 포트넘버, 데이터베이스 계정, 암호키, ... 등 외부에 유출되면 안되는 중요한 환경 변수

, 깃허브에 올라가면 안되는 값을 따로 관리하기위한 파일

 

파일 확장자가 .env

 

.env 파일은 환경 변수 파일 -> 프로젝트 최상위 패키지에 존재해야한다.

 

dotenv 모듈 설치 명령어 & 사용법 (dotenv)

npm i dotenv

 

 

 

cookie에 jwt 담아서 응답하기

// 로그인
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, results) {
            if (err) {
                console.log(err);
                return res.status(400).end();
            }

            let loginUser = results[0];
            if (loginUser && loginUser.password == password) {
                //token 발급
                const token = jwt.sign(
                    {
                        email: loginUser.email,
                        name: loginUser.name,
                    },
                    process.env.PRIVATE_KEY
                );

                //쿠키에 토큰 담아 보내는 방법
                res.cookie("token", token);

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

 

로그인 API에서 로그인 성공했을 때 token을 생성하고 res.cookie()를 통해 cookie에 토큰을 담았다.

//token 발급
const token = jwt.sign(
    {
        email: loginUser.email,
        name: loginUser.name,
    },
    process.env.PRIVATE_KEY
);

//쿠키에 토큰 담아 보내는 방법
res.cookie("token", token);

 

클라이언트가 로그인 요청을 보냄  -> 서버가 DB에서 데이터를 확인 -> 토큰 발행  -> 로그인 성공 응답 + cookie에 토큰 담아 보내기

 

 

위의 이미지에서 HttpOnly와 Secure를 볼 수 있다. 이 두가지는 보안상에서 중요한 역할을 한다.

Secure는 http, https 환경 중 어떤 환경에서 보낼 것인지를 물어보는 것이다.

HTTP

ex. http://localhost:3000

 

HTTPS

ex. https://www.naver.com/

S는 Secure를 의미하고 http를 암호화해서 보낸다는 말이다.

 

HttpOnly(= 프론트엔드가 아니라 API 호출"만" 허락할거니?) : XXS 공격(프론트엔드 공격 : 웹 브라우저로 js에 접근해 공격하는 것)

false -> 프론트엔드 즉, 화면단에서 공격이 들어오면 공격 당할 수 있다.

 true => 프론트엔드 신경안쓰고 Http API로만 쿠키 접근하게 한다.

 

httpOnly를 true로 바꾸기

res.cookie("token", token, { httpOnly: true });

 

 

JWT 유효기간 설정

JWT의 유효기간을 설정하지 X = 영원히 유효한 토큰 = 영원히 로그인 세션이 만료 X (계속 로그인 상태)

그래서 JWT의 유효기간을 설정해줘야한다.

const token = jwt.sign(
    {
        email: loginUser.email,
        name: loginUser.name,
    },
    process.env.PRIVATE_KEY,
    {
        expiresIn: "5m",
        issuer: "yunss", //issuer: 토큰 생성한 사람
    }
);

 

토큰 값을 복사해서

 

 

jwt.io에서 유효기간이 설정된 것을 확인할 수 있다.