-
JWT란? JWT 원리, 사용법프로젝트/게시판 프로젝트 2022. 5. 24. 10:43
JWT란?
JWT란 Json Web Token의 약자로써 Json 포맷을 이용하여 사용자에 대한 속성을 저장하는 Claim 기반의 Web Token입니다.
Claim이란 사용자 정보나 데이터 속성 등을 의미합니다.
즉, Claim 토큰이라 하면 토큰 안에 정보를 담고 있는 토큰을 의미합니다.
주로 회원 인증이나 정보 전달에 사용되는 됩니다.
일반 토큰 vs Claim 토큰의 차이점
일반 토큰
과거에 많이 사용하던 방식으로 주로 의미가 없는 문자열(Random String) 기반으로 구성되어 있으면 아래와 같이 표현됩니다.
a9ace025c90c0da2161075da6ddd3492a2fca776
단순한 문자열이기 때문에 정보를 담거나 할 수 없습니다.
Claim 토큰
사용자의 정보를 담고 있는 토큰을 의미합니다.
{ "id":"mhlab", "role":["admin","hr"], "company":"hexlant" }
사용자의 정보를 담고 있는데 이러한 토큰 중 가장 대표적인 것이 바로 JWT입니다.
JWT의 구조
JWT는 헤더(header), 페이로드(payload), 서명(signature) 세 가지로 나눠져 있으며 아래와 같은 형태로 구성되어 있습니다.
eyJ0eXAiOiJKV1QiLCJyZWdEYXRlIjoxNjUzMzUxNTEzMzg4LCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2NTMzNTUxMTMsInN1YiI6ImFjY2Vzcy10b2tlbiIsInVzZXJpZCI6InNzYWZ5MSJ9.mzgX4De3FEXJW4n3rXEk0WbbJMGQto0vQih5-mTL-C8
"헤더. 페이로드. 서명"으로 이루어져 있으며 각 구분은 .구분자로 나눠 표현됩니다.
예를 들면 xxxxx.yyyyy.zzzzz로 이루어져 있습니다.
JWT의 값들은 BASE64로 인코딩 되어 있습니다.
BASE64를 간단하게 설명하자면 64진수 기반으로 표현함을 의미합니다.
즉, Base64는 암호화된 문자열은 아니고 같은 문자열에 대해 항상 같은 인코딩 문자열을 반환합니다.
헤더, 페이로드, 서명의 역할
헤더에는 토큰의 타입과 해싱 알고리즘 방식을 지정할 수 있습니다.
페이로드에는 토큰 정보를 표현하기 위해 Json형태로 다수의 정보를 넣을 수 있습니다.
서명은 위에서 만든 Header와 Payload의 각 값을 Base64로 인코딩하고 그 값을 비밀키를 이용해 헤더에서 정의한 알고리즘으로 해싱을 하고 이 값을 다시 Base64로 인코딩하여 생성합니다.
즉, 비밀키를 통해 다시 디코딩 될 수 있으며 가급적 토큰에는 중요한 정보를 담지 않아야 합니다.
토큰의 실행구조
지금까지 JWT에 대해서 핵심만 간략하게 알아보았습니다.
JWT가 웹-서버 어떻게 동작하는지에 대해 알아보겠습니다.
1. 사용자가 서버에 접속하게 될때 sessionStorage에서 "access-token"에 대한 정보를 담아 "access-token" 헤더에 담아 서버에게 전송합니다.
2. 만약 서버는 클라이언트가 access-token에 대한 정보를 가지고 있다면 해당 정보를 통해 유저의 정보를 확인할 수 있으며 이를 통해 유저가 해당 게시글을 삭제할 수 있는지 검사합니다.
String token = request.getHeader("access-token"); logger.debug("token : {}",token); if(!jwtService.isUsable(token)){ return new ResponseEntity<String>(FAIL, HttpStatus.UNAUTHORIZED); } String userId = jwtService.getUserIdByToken(token);
3. 만약 유저가 처음 로그인한다면 당연히 "access-token"에 대한 정보는 sessionStorage에서 얻어올 수 없습니다.
따라서 로그인시 토큰을 생성하여 유저에게 넘겨줍니다.
MemberDto loginUser = memberService.login(memberDto); if (loginUser != null) { String token = jwtService.create("userid", loginUser.getUserid(), "access-token");// key, data, subject logger.debug("로그인 토큰정보 : {}", token); resultMap.put("access-token", token); resultMap.put("message", SUCCESS); status = HttpStatus.ACCEPTED; }
4. 유저는 이 값을 받아 sessionStorage에 저장합니다.
토큰의 원리 및 사용법
토큰이 어떻게 생성되고 어떻게 토큰에서 userID를 꺼내올 수 있는지 자세하게 알아보겠습니다.
우선 gradle, maven에 토큰에 대한 의존성을 추가해주어야 합니다.
https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt/0.9.1
토큰의 생성법
여기에서 SALT 는 비밀키입니다. (나중에 유효성 검사에 사용됩니다)
외부에 노출하면 안됩니다
private static final String SALT = "SecretKey"; private static final int EXPIRE_MINUTES = 60; @Override public <T> String create(String key, T data, String subject) { String jwt = Jwts.builder() .setHeaderParam("typ", "JWT") .setHeaderParam("regDate", System.currentTimeMillis()) .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * EXPIRE_MINUTES)) .setSubject(subject) .claim(key, data) .signWith(SignatureAlgorithm.HS256, this.generateKey()) .compact(); return jwt; }
순서대로 다음과 같은 의미를 가집니다.
- typ는 "JWT"로 설정합니다.
- 토큰의 생성일을 설정합니다.
- 토큰의 만료일을 설정합니다.
- 토큰의 용도를 설정합니다.
- Cliam을 key, data로 구성합니다.
- HS256과 비밀키로 Sign 합니다.
- 토큰을 생성합니다.
다음과 같이 메서드를 활용할 수 있습니다.
String token = jwtService.create("userid", loginUser.getUserid(), "access-token");// key, data, subject
Claims 즉, 정보를 담고 있는 부분은 userid : "exampleUserId"이며 "access-token"의 용도로 사용됨을 지정합니다.
generateKey() 메서드
private byte[] generateKey() { byte[] key = null; try { key = SALT.getBytes("UTF-8"); } catch (UnsupportedEncodingException e) { if (logger.isInfoEnabled()) { e.printStackTrace(); } else { logger.error("Making JWT Key Error ::: {}", e.getMessage()); } } return key; }
SALT(비밀키)를 바이트 형태로 변환하고 이를 반환합니다.
토큰의 유효성 검증법
@Override public boolean isUsable(String jwt) { try { Jws<Claims> claims = Jwts.parser() .setSigningKey(this.generateKey()) .parseClaimsJws(jwt); return true; } catch (Exception e) { logger.error(e.getMessage()); return false; } }
Jwt는 String 형태로 생성되기 때문에 우리가 사용하기 위해 Jwts.parser()를 이용합니다.
Token을 생성할 때 사용했던 key를 set 합니다.
parseClaimsJws() 메서드를 활용하여 토큰을 Jsw로 파싱 합니다.
이 작업에서 Exception이 발생하면 해당 토큰은 유효하지 않습니다. (다른 키로 만들어지거나 조작되었음)
토큰에서 정보 얻어오기
@Override public String getUserIdByToken(String token) throws UnsupportedEncodingException { Jws<Claims> claims = null; claims = Jwts.parser() .setSigningKey(SALT.getBytes("UTF-8")) .parseClaimsJws(token); String userId = (String) claims.getBody().get("userid"); return userId; }
유효성 검증과 비슷하지만 구문 하나가 추가되었습니다.
claims.getBody()를 하게 되면 다음과 같은 정보를 얻게 됩니다.
{exp=1653359528, sub=access-token, userid=junwoo}
즉, 여기서 get("userid")를 하면 junwoo라는 아이디를 얻을 수 있게 됩니다.
다음은 테스트 코드입니다.
@Test @DisplayName("토큰에서 userId 꺼내오기 테스트") public void getUserIdByToken() throws Exception { //given String SALT = "ssafySecret"; MemberDto memberDto = makeMemberDto(); String token = jwtService.create("userid", memberDto.getUserid(), "access-token");// key, data, subject //when Jws<Claims> claims = null; claims = Jwts.parser() .setSigningKey(SALT.getBytes("UTF-8")) .parseClaimsJws(token); System.out.println(claims.getBody()); Assertions.assertThat(claims.getBody().get("userid")).isEqualTo("junwoo"); }
makeMemberDto() 메서드는 UserId = "junwoo"라는 정보를 가지고 있습니다.
출력 : {exp=1653359528, sub=access-token, userid=junwoo}
출처
https://elfinlas.github.io/2018/08/12/whatisjwt-01/
https://mangkyu.tistory.com/56
https://galid1.tistory.com/588
'프로젝트 > 게시판 프로젝트' 카테고리의 다른 글
테스트 코드 리팩토링하기 (0) 2022.05.31 뉴스 정보를 가져와 보자(크롤링, 네이버 뉴스 API 사용법) (0) 2022.05.25 Optional을 올바르게 활용해보기 (0) 2022.05.24 비밀번호를 암호화 하자! (0) 2022.05.19 intellij 자동 포맷팅 시 줄 바꿈 처리 적용해주기 (0) 2022.05.19