본문 바로가기

World Wide Web/HTTP

쿠키, 세션, 토큰 각 차이점에 대해

 

NodeJS - 인증(쿠키, 세션, 토큰)

: 모바일 / 웹 서비스에 대부분 사용하는 HTTP는 무상태 프로토콜이다       \--> 즉, 통신 이후에 어떠한 연결도 남지 않는다결과적으로, 사용자는 각각의 HTTP통신에 자신을 알릴 수 있는 정보

velog.io

위 출처를 통해 아주 쉽게 개념을 이해할 수 있었다.

 

일반적으로 회원가입/로그인 인증을 위해선 3가지 개념이 사용된다.

 

1) 세션

2) 쿠키

3) 토큰

 

여기에 추가적으로 소셜로그인(구글 로그인)을 이용하는 경우가 있다. 이 경우를 제외하고 세션, 쿠키, 토큰 각각의 경우에 대해 정리를 해볼 필요성을 느꼈다.

 

1. 쿠키 방식

 

서버로 로그인을 하면 쿠키를 준다

가장 기본이 되는 방식으로 쿠키 방식이 존재한다. 

 

1) 클라이언트가 서버에 로그인을 하면,

2) 서버는 클라이언트에 쿠키를 준다

3) 그 후 클라이언트는 서버에 http요청을 보낼 때마다 헤더에 발급받은 쿠키를 넣어 request를 보낸다

 

이 방식의 경우, 클라이언트가 쿠키를 가지고 있기 때문에 서버에 부담이 없으나, 클라이언트 입장에서는 쿠키가 쌓이면 이것들을 지워주어야 하고, 또 클라이언트가 쿠키를 가지고 있는 만큼 보안에 문제가 생길 수 있다.

 

 

2. 세션 방식

서버에 로그인을 하면 클라이언트는 쿠키를 갖고, 서버는 세션을 갖는다

이를 보완하기 위한 방식이 세션 방식의 인증이다. 즉, 세션 방식은 쿠키와 완전히 다른 방식이 아니라, 쿠키를 보완한 방식이라 이해하면 된다.

 

1) 클라이언트가 서버에 로그인을 한다

2) 서버는 클라이언트에 쿠키를 발급하고, 또 서버 자신은 세션을 가진다

3) 클라이언트가 서버에 http 요청을 할 때 마다, header에 쿠키를 넣어 request를 보낸다.

 

앞선 쿠키 방식이 보안상에 취약점이 있어, 이를 보완한 것이 세션 방식이다. 클라이언트가 쿠키를 변조해 침입하려고 해도, 서버가 세션을 가지고 있는 이상, 변조된 쿠키로 침입할 수 없다. 다만, 로그인의 횟수가 증가될 때마다, 서버에 세션 데이터가 쌓이게 되어, 서버에 부담이 가는 것이 단점이다. 또한 서버가 꺼지는 경우, 이러한 세션 데이터가 날라가게 되는데, 이것을 stateful하다고 한다.

 

 

3. 토큰 방식

로그인을 하면 토큰을 발급해주고, request마다 이 토큰을 검증한다

앞선 세션 방식의 단점은 두 가지 였다. 서버에 계속해서 세션 데이터가 쌓이는 문제가 있고, 또 서버에 변화가 있으면 세션 데이터가 날아가는 문제점이 있었다. 이것을 보완하는 것이 토큰 방식이다. 이 때 토큰 내의 데이터는 주로 Json타입의 데이터가 많기 때문에 Json Web Token이라 불리기도 한다.

 

1) 클라이언트가 서버에 로그인을 하면,

2) 서버는 클라이언트에 토큰을 발급해준다. 이 때 이 토큰을 인증할 수 있는 시크릿키는 서버가 가지고 있다.

3) 클라이언트는 발급받은 토큰을 가지고, 서버에 http요청을 보낼 때마다 이 토큰을 header에 함께 넣어 보낸다.

4) 서버는 클라이언트가 http요청의 header에 넣은 토큰을 꺼내서, 이것이 시크릿키와 연동되는 토큰인지 확인한 뒤, verify한다.

 

이 과정을 소스코드로 본다면 다음과 같이 된다.

const jwt = require("jsonwebtoken");

router.post("/auth", async (req, res) => {
  
  //로그인 요청 시 아이디와 비밀번호를 확인한다
  const { email, password } = req.body;

  const user = await User.findOne({ email });

  
  if (!user || password !== user.password) {
    res.status(400).send({
      errorMessage: "이메일 또는 패스워드가 틀렸습니다.",
    });
    return;
  }

  //로그인 정보가 제대로 되었다면, 클라이언트에 토큰을 발급해준다
  //이 때의 토큰은 로그인한 id 정보를 담고 있다
  res.send({
    token: jwt.sign({ userId: user.userId }, "customized-secret-key"),
  });
});

위 과정은 클라이언트가 서버에 로그인 요청 시, 로그인이 제대로 된다면, 클라이언트에게 토큰을 발급해주는 부분이다. 이렇게 토큰을 발급해주면, 클라이언트는 http요청 때 마다 이 토큰을 http요청의 header에 넣어 서버에 보낼 것이다. 그렇다면 서버는 http요청의 header에 담긴 토큰을 꺼내서 이것을 검증해주어야 한다. 검증이 되면, 클라이언트가 요청한 동작을 수행할 것이다. 이렇게 request와 response 사이에서 토큰 검증을 해주는 middleware의 코드는 다음과 같다.

 

const jwt = require("jsonwebtoken");
const User = require("../models/user");

module.exports = (req, res, next) => {
  
  //request의 header에서 토큰을 꺼낸다
  const { authorization } = req.headers;
  const [authType, authToken] = (authorization || "").split(" ");

  //header에 토큰이 없다면, 토큰을 발급받으라 말한다
  if (!authToken || authType !== "Bearer") {
    res.status(401).send({
      errorMessage: "로그인 후 이용 가능한 기능입니다.",
    });
    return;
  }

  //header에 토큰이 있다면, 우선은 이 토큰을 서버가 가지고 있는 시크릿키로 decode한다
  //앞서서 로그인시 발급해준 토큰에는 로그인한 id가 담겨있었다.
  //해독해서 뽑아낸 id가, 즉 토큰에 담겨있던 로그인 id가 실제 db에도 존재하는지 확인한다
  //존재하다면, 회원정보를 local storage에 저장해준다
  //그 후 next()로 response를 처리해준다
  try {
    const { userId } = jwt.verify(authToken, "customized-secret-key");
    User.findById(userId).then((user) => {
      res.locals.user = user;
      next();
    });
  } catch (err) {
    res.status(401).send({
      errorMessage: "로그인 후 이용 가능한 기능입니다.",
    });
  }
};

앞서서 로그인이 이루어졌다면, 토큰 안에 로그인한 id정보를 넣어주었다. 그렇다면 이 토큰을 가지고 클라이언트가 http요청을 보낼 때, 해당 토큰에 담긴 id가 실제로 db에 저장된 id와 동일한지 체크해주어야 한다. 그리고 동일하다면, middleware이므로 response를 수행하도록 next()을 입력해주면 된다. 최종적으로 middleware 즉 클라이언트가 보낸 토큰을 검증한 서버는 다음과 같이 response를 수행할 것이다.

 

//http request를 보내면 토큰을 검증하는 middleware가 작동한다
router.get("/users/me", authMiddleware, async (req, res) => {
  
  //토큰이 검증되었다면, 이 검증된 토큰을 local storage에 저장해주었고,
  //이제는 이 정보를 꺼내서 렌더링 해준다
  const { user } = res.locals;
  res.send({
    user,
  });
});

 

헌데 이 토큰 방식은, 오래된 토큰을 사용할 때 문제가 생긴다. 한참전에 발급해준 토큰인데, 이 토큰을 지금까지 사용할 수 있다면 문제가 발생한다. 이를 보완하기 위해 생긴 개념이 refresh token이다. 토큰의 만료일자를 정해주고, 이 일자가 지나면 새로운 토큰을 발급받도록 하는 것이다.

 

'World Wide Web > HTTP' 카테고리의 다른 글

HTTP와 HTTPS  (0) 2021.09.05
프록시, 게이트웨이에 관해  (0) 2021.09.04
응용 프로토콜에 대한 이야기  (0) 2021.07.17
HTTP와 WebSocket 비교 | 실시간 통신에 대해서  (0) 2021.07.03