Developer Sang Guy

[JWT] Auth0을 활용한 JWT 생성 및 확인 본문

Java

[JWT] Auth0을 활용한 JWT 생성 및 확인

은크 2023. 4. 10. 17:48

참고 문서

https://jwt.io/introduction

 

JWT.IO

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

jwt.io

https://auth0.com/docs/secure/tokens/json-web-tokens

 

Auth0

Get started using Auth0. Implement authentication for any kind of application in minutes.

auth0.com

 

WIX 개발 연동하다가 JWT를 경험하게 되었다.

JWT란 Json Web Token라는 이름을 가진 당사자 간에 정보를 JSON 형식으로 안전하게 전송하기 위한 방법이다.

주로 인증 얘기 할때 많이 나오는게 Session 방식과 JWT 방식인데 차이를 한번 알아보자

Session의 경우는 처리 방식이 아래와 같다.

 

요청에 대한 정보를 서버 내 Session에 저장 후 식별 가능한 값(ID)로 응답하여 이 후 요청에 대해서도 동일한 사용자로 확인하여 알맞은 처리를 진행한다.

주로 로그인 사용자에 대한 인증 처리로 자주 사용한다.

 

위와 같은 Session 방식은 구현이 굉장히 쉽고 인증 방식도 굉장히 간단하지만 단점도 명확하다.

보통 기업 규모의 서비스는 1대의 서버로 서비스를 제공하지 않는다.

아래와 같이 L4 장비의 LB(로드 밸런싱) 기능을 사용하여 다 수의 서버로 서비스를 제공하는 방식이 일반적이다.

 

 

각 서버 별로 Session을 관리하며 Session은 공유되지 않는다.

Session을 공유하기 위해서는 앞 단에 세션 클러스터 장비가 필요한데 여기서 부터 인프라 장비 비용이 발생하게 된다.

 

JWT 방식을 사용하면 위와 같은 단점을 해소할 수 있다.

https://jwt.io/introduction

 

JWT.IO

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

jwt.io

 

JWT 주요 사용 시나리오는 권한 부여, 정보 교환이다.

 

권한 부여

JWT를 사용하는 가장 일반적인 시나리오이며 사용자가 로그인을 하면 이 후의 각 요청에는 JWT가 포함되어 사용자의 정보에 따라 허용 된 경로, 서비스 및 리소스에 접근할 수 있다.

 

정보 교환

공개 / 개인 키 쌍을 사용할 수 있으며 해당 키 쌍을 활용하여 서명이 포함 된 JWT로 안전하게 정보를 전송할 수 있다.

또한 헤더와 페이로드를 사용하여 서명하므로 데이터 변조에도 대응할 수 있다.

 

JWT 구조

헤더(Header), 페이로드(Payload), 서명(Signature)로 구분되며 일반적으로 아래와 같은 형태를 갖는다.

xxxxx.yyyyy.zzzzz

 

헤더

일반적으로 토큰 유형과 서명 알고리즘(ex. HMAC, SHA256, RSA)으로 구성되어 있다.

{
  "alg": "RS256",
  "typ": "JWT"
}

위 JSON이 Base64Url Encode되어 JWT 첫 번째 부분을 구성합니다.

 

페이로드

클레임(사용자 데이터) 및 추가 데이터를 포함하고 있으며 등록 된 클레임, 공개 클레임, 개인 클레임 세 가지 유형을 가지고 있다.

 

등록 된 클레임(Registered claims)

3글자 고정이며 필수 사양은 아니지만 유용하고 상호 운용 가능한 클레임을 제공하기 위해 권장되는 정의된 클레임 집합이다.

iss(issuer): JWT의 발급자

sub(제목): JWT의 제목(사용자)

aud(audience): JWT가 의도된 수신자

exp(만료 시간): JWT가 만료되는 시간

nbf(not before time): JWT가 처리를 위해 승인되지 않아야 하는 시간 이전

iat(issued at time): JWT가 발급된 시간. JWT의 수명을 결정하는 데 사용할 수 있습니다.

jti(JWT ID): 고유 식별자. JWT가 재생되는 것을 방지하는 데 사용할 수 있습니다(토큰을 한 번만 사용할 수 있음)

정의 된 전체 목록 : https://www.iana.org/assignments/jwt/jwt.xhtml#claims

 

JSON Web Token (JWT)

secboot Indicate whether the boot was secure (TEMPORARY - registered 2022-03-23, extension registered 2023-02-13, expires 2024-03-23) [IESG] [draft-ietf-rats-eat-12]

www.iana.org

 

공개 클레임(Public claims)

이름 및 이메일과 같은 일반 정보를 포함하는 공용 사용을 위한 사용자 지정 클레임이다.

쉽게 말해 JWT를 제공하는 사람들 끼리 공용으로 쓸만한 클레임을 미리 지정해 놓고 사용하는 것이다.

클레임 이름이 중복되지 않도록 이를 레지스트리에 등록하여 사용해야 한다.

아래 목록에서 3글자 빼고는 다 공개 클레임으로 보면 될 것 같다.

https://www.iana.org/assignments/jwt/jwt.xhtml#claims

 

JSON Web Token (JWT)

secboot Indicate whether the boot was secure (TEMPORARY - registered 2022-03-23, extension registered 2023-02-13, expires 2024-03-23) [IESG] [draft-ietf-rats-eat-12]

www.iana.org

 

 

개인 클레임(Private claims)

등록 또는 공개 클레임이 아니며 상호 간에 사용하기 위한 데이터로 이루어져 있다.

공개 클레임에는 이름 및 이메일과 같은 일반적인 정보가 포함되지만 개인 클레임에는 직원 ID 및 부서와 같이 구체적인 정보가 담긴다.

그냥 쉽게 말하면 내가 제공하는 서비스에서 실제 사용할 데이터들이라고 보면 된다.

 

페이로드 예시

{
  "sub": "1234567890",			-- 등록 된 클레임(주제)
  "exp": "1681123379094",		-- 등록 된 클레임(만료 시간)
  "name": "John Doe",			-- 공개 클레임(이름)
  "address": "Seoul xxx xxx",	-- 공개 클레임(주소)
  "paymethod" : "Card" 			-- 개인 클레임(결제 수단)
}

 

예제

토큰 생성

	public static String createJwt() throws Exception {

		Algorithm algorithm = Algorithm.RSA256(readPemPrivateKey());
		Map<String, Object> map = new HashMap<>();
		map.put("alg", "RS256");
		map.put("typ", "JWT");
		String token = JWT.create().withIssuer("ahnsangguy")
				.withHeader(map)
				.withClaim("paymethod", "CARD")
				.withExpiresAt(new Date(System.currentTimeMillis() + (1000 * 60 * 60 * 1))) // 1시간
				.sign(algorithm);
		System.out.println(token);
		return token;
	}
    
	public static RSAPrivateKey readPemPrivateKey() throws Exception {

		try (FileReader keyReader = new FileReader(new File("C:/Users/user/Documents/testprivkey.pem"))) {

			PEMParser pemParser = new PEMParser(keyReader);
			JcaPEMKeyConverter converter = new JcaPEMKeyConverter();
			PrivateKeyInfo privateKeyInfo = PrivateKeyInfo.getInstance(pemParser.readObject());
			return (RSAPrivateKey) converter.getPrivateKey(privateKeyInfo);
		}
	}
eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJhaG5zYW5nZ3V5IiwicGF5bWV0aG9kIjoiQ0FSRCIsImV4cCI6MTY4MTE4MjM0OH0.lisl4bLHdbqo4Kba2J0kkn1mr0S3NNlDLiTAAyiCuRE2AMS-qeeE4ckBUCoPseJavxViYIS6q1aOaK24BtVfEsvaEj30FL9RQwpX6QdJw2ttI4SbVKhLA0cwLtjFOVPUvbPRiTbAaQO_le4nGoPKZczXoNvzhTN4YO6vP5A-ow6MdbfdjnbszNEXFaqGwvXwrXYw85kpxo8zLvN0Tp9tv85kgaeSknqapLUu4SBHRF6QEmShBvbzJz_ik6UaFD7xFhSsI_P7_S9gfmh3MrFMBPw8-Hrmvyptky6BpdvC1LjNbVDufFxmu_gLSfeJnAXQFnj5LBprETWonu-Ec7LRCQ

 

토큰 확인

	public static void readJwt(String token) throws Exception {

		DecodedJWT decodedJWT = null;
		Algorithm algorithm = Algorithm.RSA256(readPemPublicKey());
		JWTVerifier verifier = JWT.require(algorithm).build();
		decodedJWT = verifier.verify(token);

		System.out.println(decodedJWT.getClaims());
	}
    
	public static RSAPublicKey readPemPublicKey() throws Exception {

		try (FileReader keyReader = new FileReader(new File("C:/Users/user/Documents/testpubkey.pem"))) {
			PEMParser pemParser = new PEMParser(keyReader);
			JcaPEMKeyConverter converter = new JcaPEMKeyConverter();
			SubjectPublicKeyInfo publicKeyInfo = SubjectPublicKeyInfo.getInstance(pemParser.readObject());
			return (RSAPublicKey) converter.getPublicKey(publicKeyInfo);
		}
	}
{iss="ahnsangguy", paymethod="CARD", exp=1681182348}
Comments