دو سیستم مختلف داریم. یک API و یک سایت دیگر که هر کدام با تکنولوژی و زبان برنامهنویسی خاص خود نوشتهشدهاند. برای مثال سایتی وجود دارد که تعدادی کاربر دارد و به زبان PHP نوشتهشده است (و ما کاری به آن نداریم) و میخواهد از یک API یک سری اطلاعات بگیرد. این API با داتنت نوشتهشده و نیاز دارد که بداند به چه کاربری قرار است اطلاعات بدهد اما کاربران در سیستم دیگر ثبتنام و احراز هویت میشوند و کاری با API ندارند. برای به دست آوردن اطلاعات کاربر چندین راه وجود دارد ولی بهترین راه آن استفاده از JWT یا Json Web Token است. یکی از کاربردهای JWT استفاده از آن برای تبادل اطلاعات بین دو سیستم همکار است.
JWT چیست؟
Json Web Token یا به صورت خلاصه JWT که ("jot") خواندهمیشود استانداری امن برای انتقال Claimها در محیطهای constrained است. این استاندارد در تمام فریمورکها و زبانهای مهم پیادهسازی شده و کتابخانههای بسیار خوبی برای آن نوشتهشده است.
این توکن به صورت زیر است: (البته اینترها برای راحتتر دیدن اضافه شده)
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.
TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
که شامل ۳ بخش است که با نقطه از هم جدا شدهاند. هر سه بخش یک Json هستند که با استفاده از Base64Url انکود (Encode) شدهاند. این ۳ بخش شامل موارد زیر است:
- Header: یک Json شامل الگوریتم Sign کردن اطلاعات که برای Decrypt کردن اطلاعات مورد استفاده قرار خواهدگرفت.
{
"alg": "HS256",
"typ": "JWT"
}
- Payload: یک Json شامل Claimهایی که میخواهیم به طرف مقابل ارسال کنیم (یا در هنگام اعتبارسنجی از آنها استفاده کنیم). این Json یک سری ویژگی پیشفرض دارد و یک سری ویژگی دیگر نیز میتواند از سوی استفاده کننده از JWT بر اساس نیاز به آن استفاده شود که خودش به سه مدل متفاوت تقسیم میشود:
-
- Registered claims: که شامل یک سری claim از پیشتعریفشده است که اجباری نیستند ولی بهتر است از آنها استفاده کنیم. iss (issuer) و exp (expiration time) و sub (subject) و aud (audience) چهار claim مهم هستند و البته تعداد دیگری که میتوانید لیست آنها را اینجا پیدا کنید.
- Public claims: این Claim ها را خودمان تعریف میکنیم و شامل تمام اطلاعاتی است که به صورت اختصاصی علاوه بر Registered Claims میخواهیم در توکن ذخیره کنیم. برای نامگذاری این توکنها بهتر است از نامهایی که در IANA JSON Web Token Registry استفاده کنیم و یا اینکه نامها را به صورت یک URI اختصاصی مربوط به اپلیکیشن خود استفاده کنیم که تا در آینده مشکلی با تداخل اسامی پیدا نکنیم.
- Private claims: اینها Claimهای اختصاصی هستند که بین کسانی که قرار است از توکن استفاده کنند بر روی نحوه استفاده از آن تفاوق صورت گرفتهاست و جزو دو گروه قبلی نیستند.
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
- Signature: برای امضا کردن توکن خود بایدبا استفاده از انکود شده Header و انکود شده Payload و کد مخفی خود (Private Key یا Secret Key) و الگوریتم مشخص شده در هدر امضای اختصاصی خود را تولید کنید. مثلا اگر بخواهید توکن خود را با الگوریم HMAC SHA256 امضا کنید از دستوری مشابه زیر استفاده خواهید کرد:
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
در نهایت با چسباندن این ۳ بخش یک JWT تولید میشود.
ساخت و استفاده از JWT با استفاده از الگوریتم RSA و روش کلیدعمومی و اختصاصی
برای این کار ابتدا با استفاده از ابزار Putty یا با استفاده از خود دات نت یک کلید عمومی و یک کلید خصوصی ایجاد کنید. سپس با استفاده از موارد عنوان شده در بالا و نمونهکدهای زیر میتوانید یک JWT تولید و سپس آن را Decode کرده و از اطلاعات آن استفاده کنید. برای این کدها از کتابخانههای زیر استفاده شدهاست اما میتوانید از هر ابزار دیگری برای این کار استفاده کنید. نمونهای از ابزارهای مورد استفاده برای JWT در داتنت در پایان این مطلب آورده شدهاست.
https://github.com/dvsekhvalnov/jose-jwt
http://www.bouncycastle.org/csharp/
public static string CreateToken(List<Claim> claims, string privateRsaKey)
{
RSAParameters rsaParams;
using (var tr = new StringReader(privateRsaKey))
{
var pemReader = new PemReader(tr);
var keyPair = pemReader.ReadObject() as AsymmetricCipherKeyPair;
if (keyPair == null)
{
throw new Exception("Could not read RSA private key");
}
var privateRsaParams = keyPair.Private as RsaPrivateCrtKeyParameters;
rsaParams = DotNetUtilities.ToRSAParameters(privateRsaParams);
}
using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
{
rsa.ImportParameters(rsaParams);
Dictionary<string, object> payload = claims.ToDictionary(k => k.Type, v => (object)v.Value);
return Jose.JWT.Encode(payload, rsa, Jose.JwsAlgorithm.RS256);
}
}
public static string DecodeToken(string token, string publicRsaKey)
{
RSAParameters rsaParams;
using (var tr = new StringReader(publicRsaKey))
{
var pemReader = new PemReader(tr);
var publicKeyParams = pemReader.ReadObject() as RsaKeyParameters;
if (publicKeyParams == null)
{
throw new Exception("Could not read RSA public key");
}
rsaParams = DotNetUtilities.ToRSAParameters(publicKeyParams);
}
using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
{
rsa.ImportParameters(rsaParams);
// This will throw if the signature is invalid
return Jose.JWT.Decode(token, rsa, Jose.JwsAlgorithm.RS256);
}
}
منابع:
ابزارهای داتنت برای کار با JWT (توصیه من استفاده از یکی از ۳ مورد اول است چرا که از بقیه کاملتر هستند و الگوریتمهای بیشتری را پشتیبانی میکنند):
https://www.nuget.org/packages/System.IdentityModel.Tokens.Jwt/
http://www.nuget.org/packages/jose-jwt/
https://www.nuget.org/packages/JsonWebToken
http://www.nuget.org/packages/jose-rt/
https://www.nuget.org/packages/JWT
https://www.nuget.org/packages/Trivial
نمونه کدها از از سوال زیر در Stack Overflow آورده شدهاست:
https://stackoverflow.com/questions/38794670/how-to-create-encrypted-jwt-in-c-sharp-using-rs256-with-rsa-private-key
سایت و کتابچه معرفی شده در https://jwt.io راهنمای بسیار خوبی برای شروع و آشنایی کامل با مفاهیم JWT است.