본문 바로가기
IT

JWT (토큰 기반 인증 방식) 의 이해

by 최고영회 2019. 8. 23.
728x90
반응형
SMALL

JWT


Jason Web Token의 약자로 전자 서명 된 URL-safe의 JSON이다.
거대하고 복잡한 단일 어플리케이션 개발에서 서비스를 작은 단위로 분할하여 민첩한 개발을 할 수 있는 
Microservice Architecture 로 전향할 때 각각의 API server 들은
client에 대한 인증(Authentication)과 인가(Authorization)를 위한 매커니즘이 필요한데
JWT는 MSA의 인증,인가에 사용할 수 있는 서명된 JSON 이기 때문에 안성맞춤이다.
서버측 부하를 낮출 수 있고 능률적인 접근 권한 관리를 할 수 있으며 분산환경에 더 잘 대응할 수 있다.
MSA로 여러 API 웹 서비스를 만들려면토큰을 사용한 인증 방식에 대해 이해해야 한다.

 

서버 기반 인증


기존의 인증방식은 서버측에 유저 정보를 저장한다.
대표적으로 세션이 이에 해당하고 필요에 의해 파일, DB 등에도 저장할 수 있다.
일반적인 웹어플리케이션 개발 시 이 방법을 사용한다.

서버 기반 인증의 문제점

사용자 수가 늘어날 수록 세션에 담아야 할 정보가 함께 증가하기 때문에 메모리 과부화 문제가 발생한다.
우리가 만드는 어플리케이션은 사용자가 많지 않기 때문에 문제 될 것이 없지만
아주 많은 사용자가 이용하는 대규모 포털 서비스를 개발한다고 생각해 보면 문제의 소지가 있다.
많은 트래픽을 감당하기 위해 프로세스를 늘리거나 서버 장비를 추가하는 경우 확장이 쉽지 않다. 
쿠키는 단일 도메인 및 서브 도메인에서만 작동하기 때문에 여러 도메인에서 관리하는 것은 번거롭다. 
(Cross-origin resource sharing)

 

토큰 기반 인증 원리


  1. 사용자가 로그인한다.
  2. 서버측에서 로그인을 인증하고 맞을 경우 클라이언트 측에 signed 토큰을 발급해 준다.
     signed 란? 해당 토큰이 서버에서 정상적으로 발급된 토큰임을 증명하는 signature를 담고 있다는 것을 의미한다.
  3. 클라이언트측에서 서버로 부터 전달받은 토큰을 저장하고 서버에 요청할 때마다 해당 토큰을 함께 서버에 전달 한다.
  4. 서버는 요청이 올때 마다 토큰을 검증한다.

토큰 기반 인증의 장점

  • 확장성이 뛰어나다. 서버가 늘어나도 토큰을 인증하는 방식만 알고 있다면 영향이 없다.
  • 클라이언트가 서버로 요청할 때 더이상 쿠키를 전달하지 않기 때문에 쿠키를 사용함으로써 발생하는 취약점이 사라진다.
    물론 토큰을 사용하는 환경에서도 취약점이 존재할 수 있으니 대비해야 한다.
  • 다른 서비스에서도 권한을 공유할 수 있다. (ex. OAuth 를 통해 Facebook 에서 인증하고 Facebook의 일부 권한만 사용가능)
  • CORS 문제가 해결되나. 어떤 도메인에서도 토큰만 유효하다면 처리가 가능하다.
  • JWT는 웹표준 RFC7591에 등록되어 있다. 따라서 여러 환경에서 지원이 가능하다. (.NET, Ruby, Java, Node.js, Python 등)

단점

  • 더 많은 필드가 추가되면 토큰이 커질 수 있다.
  • 모든 요청에 대해 토큰이 전송되므로 데이터 트리팩 크기에 영향이 있다.

 

JWT 구조


JWT는 header.payload.signature 로 이루어져 있다.  . 을 구분자로 사용한다.

header

토큰의 타입(type)과 해싱 알고리즘(alg)을 지정합니다. 보통 HMAC SHA256 혹은 RSA가 사용되며 토큰을 검증할 때 사용한다.

const header = { "type": "JWT", "alg": "HS256" };

payload

토큰에 담을 정보

  • 토큰은 클레임으로 이루어져 있으며 name / value 쌍으로 이루어져 있다.
  • registered claim: 서비스에 필요한 정보가 아니라 토큰에 대한 정보들을 담기위해 이름이 이미 정해진 claim
    iss(발급자), sub(제목), aud(대상자), exp(만료시간), nbf(토큰의 활성날짜), iat(발급된시간), jti(JWT 고유식별자, 일회용 토큰에 사용)
  • public claim: 충돌을 방지된 이름을 가지고 있어야 하며 이를 위해 URI 형식으로 이름을 짓는다.
  • private claim: server/client 간 협의하에 사용되는 claim
const payload = { 
  "iss": "pnpsecure.com", 
  "exp": "1685270000000", 
  "https://pnpsecure.com/jwt/is_admin": true, 
  "userId": "yhkim", "username": "younghoi" 
}; 
const encodPayload = new Buffer(JSON.stringify(payload)).toString('base64').replace('=', '');

signature

header의 인코딩값, 정보의 encoding 값을 합친 후 주어진 비밀키로 해쉬를 하여 생성함

HMACHASH256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secretKey)

 

사용


실제 개발 시에는 다양한 라이브러리가 있기 때문에 쉽게 token을 만들고 검증할 수 있다.

JJWT

한가지 예로 JJWT 라이브러리를 활용한 방법을 살펴 보자.

private static final String headerStr = "X-JWT"; 
private String secretKey = Properties.getProperty("pnp.jwt.secretKey").trim(); 
private byte[] keyBytes = Converter.parseBase64Binary(secretKey); 
private SignatureAlgorithm al = SignatureAlgorithm.HS256; 
private final Key KEY = new SecretKeySpec(keyBytes, al.getJcaName()); 

// 토큰 생성 (로그인 시 생성) 
public String getUserToken(HttpServletRequest req, HttpServletResponse res, UserDto user) {
  String jwt = Jwts.builder()
                   .headerParam("type", "JWT") 
                   .subject(user.getEmail()) 
                   .claim("uid", user.getId()) 
                   .expriation(new Date(System.currentTimeMillis() + 1 * (1000 * 60 * 60 * 24))) 
                   .signWith(KEY, al) 
                   .compact(); 
  return jwt; 
} 

// 토큰 검사 (Interceptor 에서 사용) 
public boolean validToken(String jwt) { 
  if (CommonUtil.isNotNull(jwt)) { 
    String userKey = Jwts.parser().setSigningKey(KEY).parseClaimsJws(jwt).getBody().get("uid").toString(); 
    String key = RedisClientTemplate.get(RedisSourceType.USER_KEY.getKey(userKey), redisTemplate); 
    
    // 만료 검사
    Date now = new Date();  
    if (Jwst.parser().setSigningKey(KEY).parseClaimsJws(jwt).getBody().getExpiration().before(now)) { 
      return false; 
    } 
    if (key.equals(jwt)) { 
      return true; 
    } 
    return false; 
  } 
  return false; 
}

 

728x90
반응형
LIST