• Home
  • About
    • Pieces for Studying photo

      Pieces for Studying

      Moon is a minimal, one column jekyll theme.

    • Learn More
    • Email
    • LinkedIn
    • Github
  • Posts
    • All Posts
    • Baekjoon
    • CS231N
    • Study
  • Projects

[Node] JWT를 이용한 유저 인증

08 Dec 2020

Reading time ~3 minutes

1. JWT(JSON Web Token) 이란 ?

서버와 클라이언트 사이에 정보를 전달할 떄, 정보를 안전하고 가볍게 전달할 수 있는 표준 RFC7519 입니다.

토큰 내부에는 signature 가 존재해서, 이 토큰이 검증되었는지 확인할 수 있습니다. 또한 self-contained 해서, 정보가 내부에 자체적으로 저장되어 있어, 인증에 많이 사용됩니다.

토큰 구조
HEADER.PAYLOAD.SIGNATURE

점(.)으로 나누어진 세 부분을 갖는 문자열 구조로 되어 있습니다.

HEADER

JWT를 검증하는데 필요한 정보(해싱 알고리즘)와 토큰 타입을 담고 있는 부분입니다.

{
  "typ": "JWT",
  "alg": "HS256"
}

이 정보를 Base64 인코딩하면 헤더를 만들 수 있습니다.

HEADER = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
PAYLOAD

데이터가 담기는 부분입니다. 여기에 담기는 정보를 claim 이라 합니다.

{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}

마찬가지로 Base64 인코딩을 통해 페이로드를 만들 수 있습니다.

PAYLOAD = eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
SIGNATURE

마지막으로 가장 중요한 시그니쳐 부분입니다. 이 부분을 통해 토큰이 변조되었는지 확인할 수 있습니다.

헤더에 정의된 알고리즘과 secret key 를 이용하여 생성합니다.

SIGNATURE =
  HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  SECRET
)

이제 세 부분을 합쳐 JWT로 만들 수 있습니다.

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.XbPfbIHMI6arZ3Y922BhjWgQzWXcXNrz0ogtVhfEd2o

2. 유저 인증에서의 사용

1) 기존의 인증 방식

HTTP 프로토콜은 stateless 한 프로토콜이기 떄문에, 서버가 클라이언트를 식별 및 리소스에 인가하기 위해서는 매 요청마다 추가적인 절차가 필요합니다. 이를 위해서 사용하는 방법이 서버의 session을 이용한 방식입니다.

클라이언트가 인증 요청을 보내고, 서버가 확인하면 유저 정보를 session에 저장하고 클라이언트에 session ID을 발급합니다.

이제 다음 HTTP 요청부터는 session ID을 같이 보내게 되고, 이제 서버는 session 에 요청된 유저 정보가 있는지 확인하여, 요청을 받아들이거나 거부합니다.

이러한 방식은 서버가 세션을 거친다는 점에서 응답 속도가 느려지고, 요청과 사용자가 많아지면 메모리에 부담이 될 수 있습니다.

2) JWT

이러한 단점을 극복하기 위해, session 을 사용하지 않고 유저 정보 자체를 담아 응답하는 JWT(크게는 토큰) 방식이 사용되게 되었습니다.

클라이언트는 요청마다 토큰을 보내게 되고, 서버는 이 토큰을 받아 검증만 하고 추가적인 DB 요청 없이 인증 여부를 확인할 수 있습니다.

3. passport 를 이용한 JWT 인증

passport 는 다양한 인증방식(OAuth, local ..)을 지원하는 nodejs 미들웨어입니다.

1) 로그인 API

유저가 서버로 POST /auth/login 요청시, 서버는 passport strategy를 이용해 데이터(ID,PW) 가 유효한지 검사한 후 유효하면 클라이언트에 token을 내려보내 줍니다.

Local stategy

export const localStrategy = (passport: passport.PassportStatic) => {
  passport.use(new Strategy({
   usernameField: 'username',
   passwordField: 'password',
  }, async (username, password, done) => {
    try {
      const user = await User.findOne({where: {username}});
      if(user) {
        if(user.password === password) {
          done(null, user);
        }
        else{
          done(null, false);
        }
      }
      else {
        done(null, false);
      }
    } catch (err) {
      console.log(err);
      done(err);
    }
  }));
};

로그인 API

router.post('/login', (req: Request, res: Response, next: NextFunction) => {
  passport.authenticate('local', (err, user, info) => {
    if(err || !user) {
      res.status(401).send();
    }
    req.logIn(user, {session: false}, (err) => {
      if(err) {
        res.status(500).send();
      }
      else {
        const token = jwt.sign(JSON.stringify({id: user.id, username: user.username}),process.env.JWT_SECRET as string)
      }
    });
  })(req,res,next);
});
2) Jwt Strategy

토큰이 유효한지 검사하는 passport strategy 입니다.

const jwtOptions = {
  jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
  secretOrKey: process.env.JWT_SECRET,
}

export const JWTStrategy = (passport: passport.PassportStatic) => {
  passport.use(new jwtStrategy(jwtOptions, async (payload, done) => {
    try {
      const user = await User.findOne({where: {username: payload.username}});
      if(!user) {
        done(null, false);
      }
      else {
        done(null, user);
      }
    } catch (err) {
      done(err);
    }
  }))
};
3) 인증 미들웨어

인증이 필요한 API 접근시, isLoggedIn 미들웨어를 거쳐 사용자가 인증되어있는지 확인할 수 있습니다.

export const isLoggedIn = (req: Request, res: Response, next: NextFunction) => {
  passport.authenticate('jwt', {session: false}, (err, user) => {
    if(user){
      req.user = user;
      next();
    }
    else {
      res.status(403).send();
    }
  });
}
4) 다른 API 에서의 사용

이제 사용자 인증이 필요한 API 에서, isLoggedIn 미들웨어를 사용해 인증된 사용자만 리소스에 접근 가능하게 만들 수 있습니다.

import { isLoggedIn } from '../middleware';

router.get('/test', isLoggedIn, (req: Request, res: Response, next: NextFunction) => {
  res.status(200).send("인증 성공하였습니다.");
});
5) 클라이언트에서의 요청

이제 클라이언트는 로그인 시 받은 토큰을 HTTP 헤더에 담아 요청하면, 서버는 해당 토큰을 검증해 리소스에 접근하게 할 수 있습니다.



studyTokenauthentication Share Tweet +1