Json Web Token의 커스텀 처리를 위해 닷넷은 아래 두 개의 네임스페이스를 제공합니다.
인터넷이나 ChatGpt 가 보여주는 JWT 예제들은 주로 전자에 기반한 것들이 많을 것입니다. 그러나, 전자는 레거시로 후자를 사용하도록 권고하고 있습니다.
후자가 나중에 나온 것이라 그런지, 뭔가 좀 더 정리된 느낌이고, 속도도 빠르다고 합니다.
후자를 바탕으로 JWT 토큰을 처리하는 방법을 알아 봅니다.
토큰의 처리는 크게 생성(Creation)과 검증(Validation)으로 나뉩니다.
생성
생성은 토큰이 가져야 하는 데이터를 제공해서 토큰 문자열을 생성하는 과정으로, 이 도구를 사용할 때는 아래의 두 단계를 따른다고 보시면 됩니다.
- 보안 토큰(SecurityToken)을 기술(Describe)
- 기술을 바탕으로 실물 토큰 생성
토큰을 기술하기 위해 SecurityTokenDescriptor 객체를 사용합니다.
이 객체를 실물 토큰 핸들러에게 전달하는 방식으로 실물 토큰을 생성합니다.
이 예제는 JWT 토큰을 다루므로, 핸들러는 JsonWebTokenHandler 입니다.
코드
var userId = // ...
var firstName = //...
var lastName = // ....
// Microsoft.IdentityModel.Tokens
var secret = "123456179-123456789-123456789-12";
var keyCode = Encoding.UTF8.GetBytes(secret);
var symkey = new SymmetricSecurityKey(keyCode);
var signingCredentials = new SigningCredentials(symkey, SecurityAlgorithms.HmacSha256Signature);
var tokenDescriptor = new SecurityTokenDescriptor
{
Issuer = "http://YourCompany.Com",
Expires = DateTime.UtcNow.AddMinutes(120),
SigningCredentials = signingCredentials,
Claims = new Dictionary<string, object>()
{
["sub"] = userId,
["jti"] = Guid.NewGuid(),
["given_name"] = firstName,
["family_name"] = lastName,
}
};
// Microsoft.IdentityModel.JsonWebTokens
tokenDescriptor.Claims = new Dictionary<string, object>()
{
[JwtRegisteredClaimNames.Sub] = userId,
[JwtRegisteredClaimNames.Jti] = Guid.NewGuid(),
[JwtRegisteredClaimNames.GivenName] = firstName,
[JwtRegisteredClaimNames.FamilyName] = lastName,
};
var jwtHandler = new JsonWebTokenHandler{
SetDefaultTimesOnTokenCreation = false
};
var tokenString = jwtHandler.CreateToken(tokenDescriptor);
코드에 나타난 주석의 의미는 아래와 같습니다.
Microsoft.IdentityModel.Tokens
보안 토큰과 관련한 도구들을 제공합니다.
여기에는 암호화와 관련한 객체들과, 보안 토큰을 기술하는 SecurityTokenDescriptor 객체가 포함되어 있습니다.
Microsoft.IdentityModel.JsonWebTokens
Json Web Token 과 관련된 도구들을 제공하는데, 대표적인 것이JsonWebTokenHandler 입니다.
이 객체의 CreateToken 메서드에 앞서 설정한 SecurityTokenDescriptor 객체를 제공하면 토큰 문자열을 얻을 수 있습니다.
주의
var secret = "123456179-123456789-123456789-12";
var keyCode = Encoding.UTF8.GetBytes(secret);
var symkey = new SymmetricSecurityKey(keyCode);
시크릿 값을 ASCII 코드 범위의 문자 32자로 이뤄진 문자열을 설정했는데, 이 문자열을 바탕으로 생성된 대칭키(symKey)는 문자열의 전체 바이트의 합산과 같아, 256 비트가 됩니다.
256 비트 길이의 값을 사용하는 이유는 아래의 코드 때문입니다.
var signingCredentials = new SigningCredentials(
symkey,
SecurityAlgorithms.HmacSha256Signature);
암호화 알고리즘 Sha256을 설정하기 위해 SecurityAlgorithms.HmacSha256Signature 상수를 사용했는데, 이와 유사한 값으로 SecurityAlgorithms.HmacSha256 도 있습니다.
전자는 서명을 위한 Sha256 해시 알고리즘을 의미한다는 차이점이 있습니다.
이 의미에 따라 달라지는 부분은 암호화를 위한 키의 길이(Key size)가 SHA256 의 해시 코드 길이(256)보다 길거나 같아야 함을 강제하는데, 이와 반대로, 키 길이가 256 비트보다 짧으면 에러가 납니다.
Claim, ClaimsIdentity, JsonWebToken
많은 예제들이 토큰을 생성할 때, 이 객체들을 사용하는 것을 봐 왔을 것입니다.
그러나, 이 예제에는 전혀 나타나지 않습니다.
보안 토큰 기술자가 Claims 라는 속성을 가지고 있지만, 이는 사전 객체입니다.
tokenDescriptor.Claims = new Dictionary<string, object>()
// ...
이러한 방식은 코드 입장에서는 매우 효율적입니다.
첫째로, 토큰을 생성하기 위해 아래와 같은 번잡한 생성 과정이 필요 없다는 점이죠.
claim data => Claim => ClaimsIdentity => JsonWebToken => string (JWT)
물론 토큰의 검증에는 위 객체들이 등장합니다.
string (JWT) => JsonWebToken => ClaimsIdentity => Claim
둘째로, 사전의 Value가 object 형식이라, ToString() 메서드를 사용하기 때문에, 값을 코딩하기 위한 번잡함도 줄어 듭니다.
JwtRegisteredClaimNames
Json Web Token 표준에는 몇 가지 예약된 key name 이 있는데, 그 값들을 문자열 상수로 제공합니다.
참고로, IANA 에 등록된 등록된 클래임들은 아래의 사이트에서 확인 가능합니다.
JSON Web Token (JWT) (iana.org)
마지막으로, 예제 코드는 예약된 key name 을 문자열로 한번, 상수 값으로 또 한번 설정하고 있는데, 이는 상수에 매칭되는 문자열 값을 보여주기 위한 것이지, 예제처럼 중복적으로 설정할 필요는 없습니다.
[토큰의 검증으로 이어집니다]