JWT(Jason Web Token)

김동욱·2025년 4월 18일

JWT(Json Web Token)

사용자 인증 및 권한 부여를 위해 사용되는 토큰 기반 인증 방식이다.
주로 RESTful API와 같은 무상태(stateless) 환경에서 데이터를 안전하게 주고받기 위해 사용된다.

JWT의 구조

JWT는 세 가지 주요 구성 요소로 이루어져 있다.

  • Header: 토큰 타입과 서명 알고리즘 정보를 포함하는 헤더 부분.
  • Payload: 사용자 정보와 클레임(claim)을 포함하는 본문 부분.
    토큰의 발급 시각(iat), 만료 시각(exp), 대상자(aud) 등 포함.
  • Signature: Header와 Payload를 합친 후, 지정된 알고리즘과 비밀키를 사용해 암호화한 값.

특징

  • 안전성: Signature를 통해 토큰의 무결성을 확인할 수 있다.
  • 독립성: 서버 상태를 유지하지 않아도 되므로 확장성이 뛰어나다.
  • 사용 편리성: HTTP 헤더를 통해 간단히 전송할 수 있다.

사용 사례

  • 사용자 인증: 로그인 후 발급된 토큰을 통해 사용자를 인증한다.
  • 권한 부여: 특정 리소스에 대한 접근 권한을 부여한다.
  • 정보 교환: 클라이언트와 서버 간에 데이터를 안전하게 주고받는다.

JWT의 처리

  • 클라이언트 : JWT를 로컬 스토리지나 쿠키에 저장. 이를 통해 서버와의 통신 시 토큰을 포함하여 요청.
  • 서버 : 클라이언트로부터 받은 JWT를 검증하여 요청의 유효성을 확인.

JWT 검증 방식

  • 서명 검증: 서버는 JWT의 서명을 확인하여 토큰이 변조되지 않았는지 검증한다. 이를 위해 비밀키를 사용하여 서명을 재생성하고, JWT의 서명과 비교한다.
  • 클레임 검증: JWT의 만료 시간(exp), 발급 시각(iat) 등을 확인하여 토큰이 유효한지 판단.
  • HTTP 헤더 사용: 클라이언트는 요청 시 JWT를 HTTP 헤더(보통 Authorization 헤더의 Bearer 스킴)에 포함시켜 서버에 전달.

재발급 방식과 주기 (Access Token과 Refresh Token)

  • Access Token은 짧은 유효기간을 가지며, 만료 시 Refresh Token을 사용해 새로운 Access Token을 발급받는다.
  • Refresh Token은 일반적으로 더 긴 유효기간을 가지며, 클라이언트가 서버에 요청하여 새로운 Access Token을 발급받을 때 사용된다.
  • 재발급 주기: 서버는 특정 시간마다 Access Token을 재발급하거나, 사용자가 로그아웃할 때 Refresh Token을 무효화한다.

다른 API 서비스 호출 시 인증 처리

  • API Gateway 활용: 클라이언트는 API Gateway에 요청을 보낼 때 JWT를 포함시킨다. Gateway는 JWT를 검증한 후 요청을 적절한 서비스로 전달한다.
  • 마이크로서비스 아키텍처: 여러 API 서비스가 있을 경우, 각 서비스는 JWT를 검증하여 사용자의 인증 상태를 확인한다.
  • 서비스 간 인증: 서비스 간 통신 시 JWT를 사용하여 요청의 출처와 유효성을 검증한다.

JWT 토큰 발급 예시 (로그인)


app.post('/login', (req, res) => {
  const { id, password } = req.body;
  const user = findUserById(id);
  if(!user) return res.status(401).jason({ message: 'Invalid credentials'});

  const accessToken = jwt.sign({id:user.id}, SECRET_KEY, {expiresIn:'15m'});
  const refreshToken = jwt.sign({id:user.id}, REFRESH_SECRET_KEY, {expiresIn:'7d'});

  refreshTokens.push(refreshToken);
  res.json({ accessToken, refreshToken});
});

JWT 토큰 검증 예시 (인가된 사용자 정보 조회)

app.get('/account', (req, res) => {
  const authHeader = req.headers.authorization;
  if(!authHeader) return res.status(401).json({ message: 'Authorization header missing' });

  const token = authHeader.split(' ')[1];
  try {
    const decoded = jwt.verify(token, SECRET_KEY);
    const user = findUserById(decoded.id);
    if(!user) return res.status(401).json("{ message: 'User not found' });
    res.json({id:user.id});
  } catch (err) {
    res.status(403).json({ message: 'Invalid or expired token'});
  }
});

JWT 토큰 갱신

app.post('/token', (req, res) => {
  const { refreshToken } = req.body;
  if(!refreshToken || !refreshTokens.includes(refreshToken)) {
    return res.status(403).json({ messasge: 'Invalid refresh token' });
  }

  try {
    const decoded = jwt.verify(refreshToken, REFRESH_SECRET_KEY);
    const newAccessToken = jwt.sign({id:decoded.id}, SECRET_KEY, {expiresIn:'15m'});
    res.json({ accessToken: newAccessToken });
  } catch (err) {
    res.status(403).json({ message: 'Invalid or expired refresh token' });
  }
});

클라이언트 로그인 요청 (JWT 토큰 발급)

  • http 메서드 : post
  • url : [hostIp]:[hostPort]/login
  • req : { id:id, password:password }

로그인 응답

  • res.data.accessToken, res.data.refreshToken

클라이언트 요청 (JWT 토큰 포함 및 검증)

  • http 메서드 : get
  • url : [hostIp]:[hostPort]/account
  • req : { headers: { Authorization: 'Bearer #{accessToken}'}

요청 응답

  • res.data

클라이언트 Access Token 갱신

  • http 메서드 : post
  • url : [hostIp]:[hostPort]/token
  • req : { refreshToken:refreshToken }

로그인 응답

  • res.data.accessToken
profile
갓겜만들어야지

0개의 댓글