ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 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로 인코딩하여 생성합니다.

     

    https://elfinlas.github.io/2018/08/12/whatisjwt-01/

    즉, 비밀키를 통해 다시 디코딩 될 수 있으며 가급적 토큰에는 중요한 정보를 담지 않아야 합니다.

     

    토큰의 실행구조

    지금까지 JWT에 대해서 핵심만 간략하게 알아보았습니다.

     

    JWT가 웹-서버 어떻게 동작하는지에 대해 알아보겠습니다.

     

    https://mangkyu.tistory.com/56

    1. 사용자가 서버에 접속하게 될때 sessionStorage에서 "access-token"에 대한 정보를 담아 "access-token" 헤더에 담아 서버에게 전송합니다.

    게시판의 글을 삭제하는 예시 API

     

    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

     

    Maven Repository: io.jsonwebtoken » jjwt » 0.9.1

     

    mvnrepository.com

     

    토큰의 생성법

     

    여기에서 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/

     

    JWT(JSON Web Token) 이란?

    인증과 토큰 그리고 JWT?최근들어 보안 및 인증을 위해서 JWT를 사용하게 되었다.그래서 사용만 하다가 이번에 JWT에 대한 개념과 구조, 사용법과 문제점 등을 알아보고자 한다. 일반 토큰 기반의

    elfinlas.github.io

    https://mangkyu.tistory.com/56

     

    [Server] JWT(Json Web Token)란?

    현대 웹서비스에서는 토큰을 사용하여 사용자들의 인증 작업을 처리하는 것이 가장 좋은 방법이다. 이번에는 토큰 기반의 인증 시스템에서 주로 사용하는 JWT(Json Web Token)에 대해 알아보도록 하

    mangkyu.tistory.com

    https://galid1.tistory.com/588

     

    Java - jjwt 사용법

    jjwt 사용법 이번 포스팅에서는 jjwt 를 사용하는 방법에 대해 알아보도록 하겠습니다. jwt 에 대해서 잘 모르시는 분은 이전 포스팅(https://galid1.tistory.com/581)을 참고해주세요. 1. dependency 추가 우선..

    galid1.tistory.com

     

     

    댓글

Designed by Tistory.