ฉันตอบคำถามนี้: วิธีรักษาความปลอดภัย ASP.NET Web API 4 ปีที่แล้วโดยใช้ HMAC
ตอนนี้สิ่งต่างๆมากมายเปลี่ยนไปในเรื่องความปลอดภัยโดยเฉพาะ JWT กำลังได้รับความนิยม ที่นี่ฉันจะพยายามอธิบายวิธีการใช้ JWT ในวิธีที่ง่ายที่สุดและพื้นฐานที่ฉันสามารถทำได้ดังนั้นเราจะไม่หลงทางจาก OWIN, Oauth2, ASP.NET Identity ... :)
หากคุณไม่รู้จักโทเค็น JWT คุณจะต้องดูที่:
https://tools.ietf.org/html/rfc7519
โดยทั่วไปโทเค็น JWT ดูเหมือนว่า:
<base64-encoded header>.<base64-encoded claims>.<base64-encoded signature>
ตัวอย่าง:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImN1b25nIiwibmJmIjoxNDc3NTY1NzI0LCJleHAiOjE0Nzc1NjY5MjQsImlhdCI6MTQ3NzU2NTcyNH0.6MzD1VwA5AcOcajkFyKhLYybr3h13iZjDyHm9zysDFQ
โทเค็น JWT มีสามส่วน:
- ส่วนหัว: รูปแบบ JSON ซึ่งเข้ารหัสใน Base64
- การอ้างสิทธิ์: รูปแบบ JSON ซึ่งเข้ารหัสใน Base64
- ลายเซ็น: สร้างและเซ็นชื่อตามส่วนหัวและการอ้างสิทธิ์ซึ่งเข้ารหัสใน Base64
หากคุณใช้เว็บไซต์jwt.ioด้วยโทเค็นด้านบนคุณสามารถถอดรหัสโทเค็นและดูได้ดังนี้:
ในทางเทคนิค JWT ใช้ลายเซ็นซึ่งลงนามจากส่วนหัวและการเรียกร้องด้วยอัลกอริทึมความปลอดภัยที่ระบุไว้ในส่วนหัว (ตัวอย่าง: HMACSHA256) ดังนั้น JWT จะต้องถูกถ่ายโอนผ่าน HTTPs หากคุณเก็บข้อมูลที่ละเอียดอ่อนใด ๆ ไว้ในการอ้างสิทธิ์
ตอนนี้เพื่อที่จะใช้การพิสูจน์ตัวตน JWT คุณไม่จำเป็นต้องใช้มิดเดิลแวร์ OWIN จริงๆถ้าคุณมีระบบ Web Api ดั้งเดิม แนวคิดง่าย ๆ คือวิธีการมอบโทเค็น JWT และวิธีการตรวจสอบโทเค็นเมื่อมีคำขอมา แค่นั้นแหละ.
กลับไปที่การสาธิตเพื่อให้โทเค็น JWT มีน้ำหนักเบาฉันเก็บusername
และใช้expiration time
JWT เท่านั้น แต่ด้วยวิธีนี้คุณต้องสร้างข้อมูลประจำตัวในท้องถิ่นใหม่ (เงินต้น) เพื่อเพิ่มข้อมูลเพิ่มเติมเช่น: role .. หากคุณต้องการให้สิทธิ์ในบทบาท แต่ถ้าคุณต้องการเพิ่มข้อมูลเข้าไปใน JWT มันขึ้นอยู่กับคุณ: มันยืดหยุ่นมาก
แทนที่จะใช้มิดเดิลแวร์ OWIN คุณสามารถระบุจุดสิ้นสุดโทเค็น JWT โดยใช้การดำเนินการจากคอนโทรลเลอร์:
public class TokenController : ApiController
{
// This is naive endpoint for demo, it should use Basic authentication
// to provide token or POST request
[AllowAnonymous]
public string Get(string username, string password)
{
if (CheckUser(username, password))
{
return JwtManager.GenerateToken(username);
}
throw new HttpResponseException(HttpStatusCode.Unauthorized);
}
public bool CheckUser(string username, string password)
{
// should check in the database
return true;
}
}
นี่คือการกระทำที่ไร้เดียงสา; ในการผลิตคุณควรใช้คำขอ POST หรือจุดสิ้นสุดการรับรองความถูกต้องเบื้องต้นเพื่อให้โทเค็น JWT
จะสร้างโทเค็นได้username
อย่างไร?
คุณสามารถใช้แพคเกจ NuGet ที่เรียกSystem.IdentityModel.Tokens.Jwt
จาก Microsoft เพื่อสร้างโทเค็นหรือแม้แต่แพ็คเกจอื่นหากคุณต้องการ ในการสาธิตฉันใช้HMACSHA256
กับSymmetricKey
:
/// <summary>
/// Use the below code to generate symmetric Secret Key
/// var hmac = new HMACSHA256();
/// var key = Convert.ToBase64String(hmac.Key);
/// </summary>
private const string Secret = "db3OIsj+BXE9NZDy0t8W3TcNekrF+2d/1sFnWG4HnV8TZY30iTOdtVWJG8abWvB1GlOgJuQZdcF2Luqm/hccMw==";
public static string GenerateToken(string username, int expireMinutes = 20)
{
var symmetricKey = Convert.FromBase64String(Secret);
var tokenHandler = new JwtSecurityTokenHandler();
var now = DateTime.UtcNow;
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Name, username)
}),
Expires = now.AddMinutes(Convert.ToInt32(expireMinutes)),
SigningCredentials = new SigningCredentials(
new SymmetricSecurityKey(symmetricKey),
SecurityAlgorithms.HmacSha256Signature)
};
var stoken = tokenHandler.CreateToken(tokenDescriptor);
var token = tokenHandler.WriteToken(stoken);
return token;
}
ปลายทางเพื่อจัดเตรียมโทเค็น JWT เสร็จสิ้นแล้ว ตอนนี้วิธีการตรวจสอบ JWT เมื่อคำขอมา? ในการสาธิตฉันได้สร้าง
JwtAuthenticationAttribute
สิ่งที่สืบทอดมาIAuthenticationFilter
(รายละเอียดเพิ่มเติมเกี่ยวกับตัวกรองการรับรองความถูกต้องที่นี่ )
ด้วยแอตทริบิวต์นี้คุณสามารถรับรองความถูกต้องของการกระทำใด ๆ : คุณเพียงแค่ต้องใส่คุณลักษณะนี้ในการกระทำนั้น
public class ValueController : ApiController
{
[JwtAuthentication]
public string Get()
{
return "value";
}
}
คุณยังสามารถใช้มิดเดิลแวร์ OWIN หรือ DelegateHander หากคุณต้องการตรวจสอบคำขอขาเข้าทั้งหมดสำหรับ WebAPI ของคุณ (ไม่เฉพาะเจาะจงกับตัวควบคุมหรือการกระทำ)
ด้านล่างเป็นวิธีการหลักจากตัวกรองการรับรองความถูกต้อง:
private static bool ValidateToken(string token, out string username)
{
username = null;
var simplePrinciple = JwtManager.GetPrincipal(token);
var identity = simplePrinciple.Identity as ClaimsIdentity;
if (identity == null)
return false;
if (!identity.IsAuthenticated)
return false;
var usernameClaim = identity.FindFirst(ClaimTypes.Name);
username = usernameClaim?.Value;
if (string.IsNullOrEmpty(username))
return false;
// More validate to check whether username exists in system
return true;
}
protected Task<IPrincipal> AuthenticateJwtToken(string token)
{
string username;
if (ValidateToken(token, out username))
{
// based on username to get more information from database
// in order to build local identity
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, username)
// Add more claims if needed: Roles, ...
};
var identity = new ClaimsIdentity(claims, "Jwt");
IPrincipal user = new ClaimsPrincipal(identity);
return Task.FromResult(user);
}
return Task.FromResult<IPrincipal>(null);
}
ขั้นตอนการทำงานคือการใช้ห้องสมุด JWT (NuGet แพคเกจข้างต้น) เพื่อตรวจสอบ JWT ClaimsPrincipal
โทเค็นแล้วส่งกลับมา คุณสามารถทำการตรวจสอบเพิ่มเติมเช่นตรวจสอบว่ามีผู้ใช้อยู่ในระบบของคุณและเพิ่มการตรวจสอบความถูกต้องแบบกำหนดเองอื่น ๆ หากคุณต้องการ รหัสเพื่อตรวจสอบโทเค็น JWT และรับเงินต้นคืน:
public static ClaimsPrincipal GetPrincipal(string token)
{
try
{
var tokenHandler = new JwtSecurityTokenHandler();
var jwtToken = tokenHandler.ReadToken(token) as JwtSecurityToken;
if (jwtToken == null)
return null;
var symmetricKey = Convert.FromBase64String(Secret);
var validationParameters = new TokenValidationParameters()
{
RequireExpirationTime = true,
ValidateIssuer = false,
ValidateAudience = false,
IssuerSigningKey = new SymmetricSecurityKey(symmetricKey)
};
SecurityToken securityToken;
var principal = tokenHandler.ValidateToken(token, validationParameters, out securityToken);
return principal;
}
catch (Exception)
{
//should write log
return null;
}
}
หากโทเค็น JWT ได้รับการตรวจสอบความถูกต้องแล้วและคืนเงินต้นคุณควรสร้างข้อมูลประจำตัวในท้องถิ่นใหม่และใส่ข้อมูลเพิ่มเติมเพื่อตรวจสอบการอนุญาตบทบาท
อย่าลืมเพิ่มconfig.Filters.Add(new AuthorizeAttribute());
(การอนุญาตเริ่มต้น) ที่ขอบเขตทั่วโลกเพื่อป้องกันการร้องขอที่ไม่ระบุชื่อใด ๆ ไปยังแหล่งข้อมูลของคุณ
คุณสามารถใช้บุรุษไปรษณีย์เพื่อทดสอบตัวอย่าง:
คำขอโทเค็น (ไร้เดียงสาที่ฉันกล่าวถึงข้างต้นเพียงเพื่อการสาธิต)
GET http://localhost:{port}/api/token?username=cuong&password=1
ใส่โทเค็น JWT ในส่วนหัวสำหรับคำขอที่ได้รับอนุญาตตัวอย่าง:
GET http://localhost:{port}/api/value
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImN1b25nIiwibmJmIjoxNDc3NTY1MjU4LCJleHAiOjE0Nzc1NjY0NTgsImlhdCI6MTQ3NzU2NTI1OH0.dSwwufd4-gztkLpttZsZ1255oEzpWCJkayR_4yvNL1s
ตัวอย่างจะถูกวางไว้ที่นี่: https://github.com/cuongle/WebApi.Jwt