You will be fine

<Spring Security> 10. JWT 살펴보기

by BFine
반응형

가. JWT

 a. 무엇인가?

  -  JSON Web Token 의 약자로 토큰을 통해 서버에 대한 엑세스를 관리하는 방법 중 하나이다.

  -  HTTP 모든 요청에 이 JWT를 포함하여 서버로 전달하며 서버는 이 JWT를 통해 인증된 사용자(+정보)인지 확인하는 방법이다.

 

 b. 구조

  -  암호화된 헤더.페이로드.서명 형태로 구성 되어있고 이 https://jwt.io/  사이트에서 테스트 해볼 수 있다.

 

JWT.IO

JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties.

jwt.io

  -  위의 사이트에서 한번 만들어 보면 아래와 같은 구조로 만들 수가 있다.

  -  여기서 iat는 issued_at 으로 JWT 발급 날짜를 나타내며 아래는 서명 key에 대한 값을 지정해 줄 수가 있다. 

  -  다른 값이나 타입은 대체가 가능하지만 암호화 알고리즘을 나타내는 alg값은 필수로 들어가야 한다.

 

 c. 특징 

  -  클라이언트와 서버간 규정한 JWT 자체를 통해서 검증을 하기 때문에 DB를 공유 할 필요가 없다.  

  -  또한 매번 JWT 를 보내서 인증을 확인하기 때문에 CSRF 공격을 보완할 수 있다.(CSRF Filter 필요없음)

  -  사용자정보를 JWT로 확인하기 때문에 Session을 상태를 저장하지 않는 Stateless 형태로 운용이 가능하다.

 

나. Spring Security JWT

 a. 라이브러리

implementation 'org.springframework.security:spring-security-jwt:1.1.1.RELEASE'

  -  https://mvnrepository.com/ 에 보면 다양한 JWT 라이브러리를 볼 수가 있다. 그중에서도 default 느낌이나는 security-jwt 라이브러리를 살펴보자

 

 b. 패키지 구조

  -  당황스러웠던 부분은 주요한 클래스들이 거의 deprecated 되었다는 것을 볼수가 있다. 이유 대한 자세한 내용은 아래에 나와있다.

      => https://github.com/spring-projects/spring-security/wiki/OAuth-2.0-Migration-Guide

  -  완전히 사라지지는 않았기 때문에 JWT를 어떻게 만들고 있는지는 확인이 가능하다. 

  

 c. JWT 만들기

import org.springframework.security.jwt.Jwt;
import org.springframework.security.jwt.JwtHelper;
import org.springframework.security.jwt.crypto.sign.MacSigner;

public class JWTtest {
    public static void main(String[] args) {

        MacSigner macSigner = new MacSigner("will-b-fine");

        String payload = "{ \"sub\":\"user\" }";

        Jwt encode = JwtHelper.encode(payload,macSigner);

        System.out.println("\nObject : "+encode);
        System.out.println("Payload :"+encode.getClaims());
        System.out.println("JWT : "+encode.getEncoded());

    }
}

  -  생각보다 더 간단하게 라이브러리를 이용하여 JWT를 만들어 볼 수 있었다. 아래 인코딩 결과를 디코딩해보자

  -  정상적으로 디코딩 되었고 텍스트 형태로 그대로 노출되기 때문에 Header나 Payload에는 일반적으로 중요정보는 담으면 안된다.

import org.springframework.security.jwt.Jwt;
import org.springframework.security.jwt.JwtHelper;
import org.springframework.security.jwt.crypto.sign.MacSigner;

public class JWTtest {
    public static void main(String[] args) {

        MacSigner macSigner = new MacSigner("will-b-fine");
        MacSigner invalidSigner = new MacSigner("will-b-angry");

        String payload = "{ \"sub\":\"user\" }";

        Jwt encode = JwtHelper.encode(payload,macSigner);

        System.out.println("\nObject : "+encode);
        System.out.println("Payload :"+encode.getClaims());
        System.out.println("JWT : "+encode.getEncoded());

        Jwt decodedJwt = JwtHelper.decodeAndVerify(encode.getEncoded(), macSigner);
        System.out.println("\ndecode JWT : "+decodedJwt);

        decodedJwt = JwtHelper.decodeAndVerify(encode.getEncoded(), invalidSigner);
        System.out.println(decodedJwt);

    }
}

  -  해당 JWT에 대한 서명을 검증해보면 key값이 다를경우 InvalidSignatureException이 발생하는 것을 볼 수가 있다.

  -  아쉬운 부분은 Payload 생성에 대한 부분(발급일자 등)은 이 라이브러리 내에 처리하는 부분은 따로 없었다. 

 

 d. 생성 과정

  -  .encode에서 과연 어떤일이 일어나고 있는지 확인해보자

  -  직관적으로 봐도 삼분할로 헤더 생성 -> claim(payload) 인코딩 -> 서명 순서로 진행되는것을 볼 수 있다.

  -  먼저 Header 생성하는 부분부터 살펴보자

  -  Signer와 헤더에 들어갈 params를 이용하여 헤더를 구성하는것을 볼 수 있다. (Default 외에 따로 추가가 없기 때문에 Params Size는 0)

  -  여기서 .sigAlg 메서드는 알고리즘명을 치환하는 부분이다. (ex HMACSHA256 -> HS256)

  -  그리고 .serializeParams 메서드는 map의 데이터를 JSON String형태로 만들고 UTF8로 인코딩 처리하고 byte[] 형태로 리턴한다. 

  -  비슷하게 claim(payload) 인코딩 하는 부분도 간단하게 확인할 수 있다.

  -  마지막으로 가장 중요한 서명 부분을 살펴보자

  -  .sign 메서드를 실행하기 전에 앞서서 base64로 인코딩하는 것을 볼수가 있고 파라미터로 헤더 값, PERIOD(.), payload를 concat한 값을 가진다.

  -  .sign 메서드 내부를 간략하게 살펴보면 알고리즘에 대한 Mac 객체를 만들고 초기화를 하는데 이때 key는 내가 임의로 지정했던 부분이다.(will-b-fine)

  -  따라가보면 key값을 보여지는 헥사값으로 XOR 하여 두 byte 배열에 저장한다. (암호화 알고리즘에 따라 크기를 padding 하는걸로 보인다)

  -  그리고 .doFianal 메세드 내부로 상세하게 들어가보면 위에서 만든 byte 배열을 이용하여 다이제스트를 만드는 것을 볼 수 있다. 

       => 암호화 처리는 SHA2 클래스에서 bit를 밀고 땅기고 과정을 거치는 것을 볼 수 있었다. 

  -  즉 서명 부분은 지정했던 key(will-b-fine)와 헤더.페이로드를 인코딩한 값을 섞어서만든 암호화키라고 볼 수 있다.

  -  마지막으로 JWT .verify 메서드 부분을 살펴보면 의외로 간단한다. 

  -  앞에는 생략했는데 JWT를 디코딩 해서 삼분할로 나눈뒤 그값으로 위의 그림과 같이 인코딩 할떄와 동일하게 보내준다.

  -  그리고 이를 서명할때와 동일하게 .sign 메서드로 암호화된 byte 값을 생성해서 crypto값이 동일한지를 체크하여 검증을 진행한다.

    => 내 생각에는 복호화 과정이 있지 않을까 생각했는데 역시 암호화라 그런지 단방향으로 처리하고 있었다. (고정적인 사고 금지...)

반응형

블로그의 정보

57개월 BackEnd

BFine

활동하기