JWT là viết tắt của và nó là một cơ chế ủy quyền chứ không phải xác thực. Vì vậy, hãy tìm hiểu sự khác biệt giữa hai điều đó. JSON Web Token là cơ chế cho phép xác minh rằng người dùng chính xác là người mà họ tuyên bố. Đó là một quá trình đăng nhập trong đó người dùng cung cấp tên người dùng và mật khẩu và hệ thống sẽ xác minh chúng. Vì vậy, xác thực trả lời câu hỏi: người dùng là ai? Xác thực là cơ chế cho phép xác minh quyền truy cập nào mà người dùng có đối với một tài nguyên nhất định. Đó là một quá trình cấp cho người dùng một số vai trò và một tập hợp các quyền mà một vai trò cụ thể có. Vì vậy, ủy quyền trả lời câu hỏi đó: người dùng có những quyền gì trong hệ thống? Ủy quyền Điều quan trọng là phải hiểu rằng Xác thực luôn được đặt lên hàng đầu và Ủy quyền là thứ hai. Nói cách khác, bạn không thể nhận được sự cho phép trước khi xác minh danh tính của mình. Nhưng các phương pháp ủy quyền phổ biến nhất là gì? Có hai cách tiếp cận chính để xử lý ủy quyền cho ứng dụng web. Phiên Cách tiếp cận truyền thống trên web dành cho người dùng được ủy quyền là phiên phía máy chủ dựa trên cookie. Quá trình bắt đầu khi người dùng đăng nhập và máy chủ xác thực anh ta. Sau đó, máy chủ tạo một phiên có ID phiên và lưu nó ở đâu đó trong bộ nhớ của máy chủ. Máy chủ gửi lại ID phiên cho máy khách và máy khách lưu ID phiên trong cookie. Đối với mọi yêu cầu, máy khách sẽ gửi ID phiên như một phần của yêu cầu và máy chủ xác minh ID phiên trong bộ nhớ của nó cũng như các quyền của người dùng liên quan đến phiên này. Mã thông báo Một cách tiếp cận phổ biến khác là sử dụng mã thông báo để ủy quyền. Quá trình bắt đầu tương tự khi người dùng nhập thông tin đăng nhập, mật khẩu và máy khách gửi yêu cầu đăng nhập đến máy chủ. Thay vì tạo phiên, máy chủ tạo mã thông báo được ký bằng mã thông báo bí mật. Sau đó, máy chủ gửi lại mã thông báo cho máy khách và máy khách phải lưu trữ mã thông báo đó trong bộ nhớ cục bộ. Tương tự như cách tiếp cận dựa trên phiên, máy khách phải gửi mã thông báo đến máy chủ cho mọi yêu cầu. Tuy nhiên, máy chủ không lưu trữ bất kỳ thông tin bổ sung nào về phiên người dùng. Máy chủ phải xác thực rằng mã thông báo không thay đổi kể từ khi nó được tạo và ký bằng khóa bí mật. Phiên so với mã thông báo Phương pháp ủy quyền dựa trên phiên có thể dễ bị tấn công được gọi là Giả mạo yêu cầu chéo trang web (CSRF). Đó là một kiểu tấn công khi kẻ tấn công trỏ đến một trang web mà chúng đăng nhập để thực hiện các hành động mà chúng không có ý định thực hiện, chẳng hạn như gửi khoản thanh toán hoặc thay đổi mật khẩu. Một điều nữa là khi sử dụng phương pháp ủy quyền dựa trên phiên sẽ tạo ra một phiên có trạng thái giữa máy khách và máy chủ. Vấn đề là nếu một máy khách muốn truy cập các máy chủ khác nhau trong phạm vi của cùng một ứng dụng thì các máy chủ đó phải chia sẻ trạng thái phiên. Trong trường hợp khác, máy khách sẽ cần được cấp phép trên mỗi máy chủ vì phiên sẽ khác nhau. Mặt khác, phương pháp ủy quyền dựa trên mã thông báo không yêu cầu lưu trữ dữ liệu phiên ở phía máy chủ và có thể đơn giản hóa việc ủy quyền giữa nhiều máy chủ. Tuy nhiên, mã thông báo vẫn có thể bị kẻ tấn công đánh cắp và cũng khó có thể vô hiệu hóa mã thông báo. Chúng ta sẽ xem chi tiết hơn và cách xử lý tình trạng vô hiệu trong bài viết này. JWT Mã thông báo Web JSON (JWT) là một tiêu chuẩn mở xác định một cách nhỏ gọn và khép kín để truyền thông tin một cách an toàn giữa các bên dưới dạng đối tượng JSON. Thông tin này có thể được xác minh và tin cậy vì nó được ký điện tử. JWT có thể được ký bằng bí mật (với thuật toán ) hoặc cặp khóa công khai/riêng bằng cách sử dụng hoặc . HMAC RSA ECDSA Cấu trúc JWT Mã thông báo Web JSON bao gồm ba phần được phân tách bằng dấu chấm . tiêu đề { "alg": "HS256", "typ": "JWT" } Tiêu đề thường bao gồm hai phần: loại mã thông báo và thuật toán ký đang được sử dụng. Khối hàng { "sub": "1234567890", "name": "John Doe", "admin": true } Tải trọng chứa các xác nhận quyền sở hữu, là các tuyên bố về người dùng. Sau đó, tải trọng được mã hóa để tạo thành phần thứ hai của Mã thông báo Web JSON. Bạn có thể tìm thấy mô tả về các trường tiêu chuẩn được sử dụng làm xác nhận quyền sở hữu . Base64Url tại đây Chữ ký HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret) Để tạo phần chữ ký, bạn phải lấy tiêu đề được mã hóa, tải trọng được mã hóa, bí mật và thuật toán được chỉ định trong tiêu đề và ký tên vào đó. Mã thông báo thường trông giống như sau: xxxxx.yyyyy.zzzzz Bạn có thể điều hướng đến và gỡ lỗi mã thông báo mẫu hoặc mã thông báo của riêng bạn. Chỉ cần dán mã thông báo của bạn vào trường và chọn của chữ ký mã thông báo. jwt.io Mã hóa Thuật toán dự án .NET Bây giờ chúng ta đã có kiến thức lý thuyết về cách hoạt động của JWT, chúng ta có thể áp dụng nó vào dự án thực tế. Giả sử chúng ta có một API đơn giản thể hiện các hoạt động CRUD cho thực thể cà phê. Chúng tôi sẽ tạo một dự án API ASP.NET Core đại diện cho Coffee API. Sau đó, chúng tôi sẽ tạo một dự án API ASP.NET Core khác đại diện cho API nhận dạng có thể tạo JWT. Trong cuộc sống thực, bạn có thể sẽ sử dụng hoặc hoặc cho mục đích Xác thực/Ủy quyền. Tuy nhiên, chúng tôi sẽ tạo API nhận dạng của riêng mình để trình bày cách tạo JWT. Khi Identity API hoàn tất, chúng ta có thể gọi bộ điều khiển của nó và tạo JWT dựa trên dữ liệu của người dùng. Ngoài ra, chúng tôi có thể bảo vệ Coffee API bằng cấu hình ủy quyền yêu cầu chuyển JWT theo từng yêu cầu. Máy chủ nhận dạng Okta Auth0 API cà phê Đầu tiên, chúng ta sẽ tạo một dự án API ASP.NET Core đơn giản đại diện cho Coffee API. Đây là cấu trúc của dự án này: Hãy bắt đầu với trong thư mục . Nó là một thực thể đơn giản với các thuộc tính và . Coffee.cs Model Id Name namespace Hackernoon.Coffee.API.Model; public class Coffee { public int Id { get; set; } public string Name { get; set; } } Chúng tôi cần lưu trữ các thực thể của mình trong khi làm việc với API. Vì vậy, hãy giới thiệu một bộ lưu trữ trong bộ nhớ đơn giản. Nó nằm trong tệp trong thư mục . Storage.cs Data namespace Hackernoon.Coffee.API.Data; public static class Storage { private static readonly List<Model.Coffee> Data = new(); public static List<Model.Coffee> GetAll() { return Data; } public static bool Create(Model.Coffee model) { if (Data.Any(c => c.Id == model.Id || c.Name == model.Name)) return false; Data.Add(new Model.Coffee { Id = model.Id, Name = model.Name }); return true; } public static bool Delete(int id) { if (Data.All(c => c.Id != id)) return false; Data.Remove(Storage.Data.First(c => c.Id == id)); return true; } public static bool Update(Model.Coffee model) { if (Data.All(c => c.Id != model.Id)) return false; Data.First(c => c.Id == model.Id).Name = model.Name; return true; } } Chúng tôi cần một lớp đại diện cho các yêu cầu đối với Coffee API. Vì vậy, hãy tạo trong thư mục . CoffeeRequest.cs Contracts namespace Hackernoon.Coffee.API.Contracts; public class CoffeeRequest { public int Id { get; set; } public string Name { get; set; } } Khi hoàn tất, chúng ta có thể triển khai trong thư mục đại diện cho các hoạt động CRUD cho thực thể cà phê. CoffeeController.cs Controller using Hackernoon.Coffee.API.Contracts; using Hackernoon.Coffee.API.Data; using Microsoft.AspNetCore.Mvc; namespace Hackernoon.Coffee.API.Controllers; [Route("coffee")] [ApiController] public class CoffeeController : ControllerBase { [HttpGet] public IList<Model.Coffee> GetAll() { return Storage.GetAll(); } [HttpPost] public IActionResult Create([FromBody]CoffeeRequest request) { var model = new Model.Coffee { Id = request.Id, Name = request.Name }; if (!Storage.Create(model)) return new BadRequestResult(); return new OkResult(); } [HttpDelete] public IActionResult Delete(int id) { if (!Storage.Delete(id)) return new BadRequestResult(); return new OkResult(); } [HttpPut] public IActionResult Update([FromBody] CoffeeRequest request) { var model = new Model.Coffee() { Id = request.Id, Name = request.Name }; if (!Storage.Update(model)) return new BadRequestResult(); return new OkResult(); } } Coffee API đã hoàn tất, chúng ta có thể chạy dự án và xem Swagger UI như sau: API nhận dạng Hãy tạo một dự án API ASP.NET Core khác đại diện cho API nhận dạng. Đây là cấu trúc của dự án này: Hãy bắt đầu với trong thư mục , thể hiện yêu cầu tạo JWT mới với các thuộc tính và . TokenGenerationRequest.cs Contracts Email Password namespace Hackernoon.Identity.API.Contracts; public class TokenGenerationRequest { public string Email { get; set; } public string Password { get; set; } } Chúng ta chỉ cần triển khai đại diện cho logic của việc tạo JWT. Nhưng trước khi chúng tôi thực hiện điều đó, gói NuGet cần phải được cài đặt. TokenController.cs Microsoft.AspNetCore.Authentication.JwtBearer using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; using Hackernoon.Identity.API.Contracts; using Microsoft.AspNetCore.Mvc; using Microsoft.IdentityModel.Tokens; namespace Hackernoon.Identity.API.Controllers; [Route("token")] public class TokenController : ControllerBase { private const string SecretKey = "VerySecretAndLongKey-NeedMoreSymbolsHere-123"; private const string Issuer = "IdentityServerIssuer"; private const string Audience = "IdentityServerClient"; private static readonly TimeSpan Lifetime = TimeSpan.FromMinutes(20); [HttpPost] public string Create([FromBody]TokenGenerationRequest request) { var claims = new List<Claim> {new Claim(ClaimTypes.Email, request.Email) }; var jwt = new JwtSecurityToken( issuer: Issuer, audience: Audience, claims: claims, expires: DateTime.UtcNow.Add(Lifetime), signingCredentials: CreateSigningCredentials()); return new JwtSecurityTokenHandler().WriteToken(jwt); } private static SigningCredentials CreateSigningCredentials() { return new SigningCredentials( new SymmetricSecurityKey(Encoding.UTF8.GetBytes(SecretKey)), SecurityAlgorithms.HmacSha256); } } Lưu ý rằng các hằng số nhạy cảm như , và phải được đặt ở đâu đó trong cấu hình. Chúng được mã hóa cứng chỉ để đơn giản hóa dự án thử nghiệm này. Trường được đặt thành 20 phút, có nghĩa là mã thông báo sẽ có hiệu lực trong thời gian đó. Bạn cũng có thể cấu hình tham số này. SecretKey Issuer Audience Lifetime Bây giờ chúng ta có thể chạy dự án và xem Swagger UI như sau: Hãy thực hiện cuộc gọi đến điểm cuối và tạo JWT mới. Hãy thử tải trọng sau: /token { "email": "john.doe@gmail.com", "password": "password" } API nhận dạng sẽ tạo JWT tương ứng: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9lbWFpbGFkZHJlc3MiOiJqb2huLmRvZUBnbWFpbC5jb20iLCJJc0dvdXJtZXQiOiJmYWxzZSIsImV4cCI6MTcwNzc4Mzk4MCwiaXNzIjoiSWRlbnRpdHlTZXJ2ZXJJc3N1ZXIiLCJhdWQiOiJJZGVudGl0eVNlcnZlckNsaWVudCJ9.4odXsbWak1C0uK3Ux-n7f58icYQQwlHjM54OjgMCVPM Kích hoạt ủy quyền trong API cà phê Giờ đây, khi API nhận dạng đã sẵn sàng và cung cấp mã thông báo cho chúng tôi, chúng tôi có thể bảo vệ API cà phê bằng ủy quyền. Một lần nữa gói NuGet cần được cài đặt. Microsoft.AspNetCore.Authentication.JwtBearer Chúng ta cần đăng ký các dịch vụ cần thiết bằng dịch vụ xác thực. Thêm mã sau vào tệp ngay sau khi tạo trình tạo. Program.cs var builder = WebApplication.CreateBuilder(args); builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidIssuer = "IdentityServerIssuer", ValidateAudience = true, ValidAudience = "IdentityServerClient", ValidateLifetime = true, IssuerSigningKey = new SymmetricSecurityKey( Encoding.UTF8.GetBytes("VerySecretAndLongKey-NeedMoreSymbolsHere-123")), ValidateIssuerSigningKey = true, }; }); builder.Services.AddAuthorization(); Điều quan trọng cần nhớ là thứ tự trong phần mềm trung gian là quan trọng. Chúng tôi kích hoạt xác thực bằng cách gọi phương thức và chỉ định làm lược đồ xác thực. Nó là một hằng số chứa giá trị . AddAuthentication() JwtBearerDefaults.AuthenticationScheme Bearer namespace Microsoft.AspNetCore.Authentication.JwtBearer { /// <summary>Default values used by bearer authentication.</summary> public static class JwtBearerDefaults { /// <summary> /// Default value for AuthenticationScheme property in the JwtBearerAuthenticationOptions /// </summary> public const string AuthenticationScheme = "Bearer"; } } Chúng ta cần chỉ định mô tả tham số nào của JWT sẽ được xác thực trong quá trình ủy quyền. Chúng tôi cũng chỉ định tương tự như trong Identity API để xác minh chữ ký JWT. Kiểm tra thêm chi tiết về . TokenValidationParameters IssuerSigningKey signingCredentials TokenValidationParameters tại đây Đoạn mã tiếp theo bổ sung phần mềm trung gian vào trình tạo để kích hoạt khả năng xác thực và ủy quyền. Nó nên được thêm vào giữa các phương thức và . UseHttpsRedirection() MapControllers() app.UseHttpsRedirection(); app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); Bây giờ, chúng ta có thể sử dụng thuộc tính trên bộ điều khiển hoặc các hành động của nó. Bằng cách áp dụng mã này, giờ đây tất cả các hành động trong đều được bảo vệ bằng cơ chế ủy quyền và JWT phải được gửi như một phần của yêu cầu. Authorize CoffeeController [Route("coffee")] [ApiController] [Authorize] public class CoffeeController : ControllerBase { .. Nếu chúng tôi thực hiện lệnh gọi tới bất kỳ điểm cuối nào của API Cà phê, chúng tôi có thể gỡ lỗi và thấy rằng nó đã được điền và có với các xác nhận quyền sở hữu mà chúng tôi đã chỉ định trong JWT. Điều quan trọng là phải hiểu cách ASP.NET Core xử lý Ủy quyền một cách sâu sắc. HttpContext.User Identity Thêm ủy quyền vào giao diện người dùng Swagger Chúng tôi đã làm rất tốt việc bảo vệ Coffee API bằng sự ủy quyền. Nhưng nếu bạn chạy dự án Coffee API và mở Swagger UI, bạn sẽ không thể gửi JWT như một phần của yêu cầu. Để khắc phục điều đó, chúng ta cần cập nhật tệp bằng đoạn mã sau: Program.cs builder.Services.AddSwaggerGen(option => { option.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme { In = ParameterLocation.Header, Description = "Please enter a valid token", Name = "Authorization", Type = SecuritySchemeType.Http, BearerFormat = "JWT", Scheme = "Bearer" }); option.AddSecurityRequirement(new OpenApiSecurityRequirement { { new OpenApiSecurityScheme { Reference = new OpenApiReference { Type=ReferenceType.SecurityScheme, Id="Bearer" } }, new string[]{} } }); }); Sau đó, chúng ta sẽ thấy nút ở góc trên cùng bên phải: Ủy quyền Khi click vào nút bạn sẽ có thể vào JWT như sau: Authorize Sử dụng Postman để kiểm tra Bạn không thể giới hạn bản thân trong việc sử dụng Swagger UI và có thể thực hiện kiểm tra API thông qua công cụ Postman. Trước tiên, hãy gọi điểm cuối của API nhận dạng. Chúng ta cần chỉ định tiêu đề với giá trị trong phần vì chúng ta sẽ sử dụng JSON làm tải trọng. /token Content-Type application/json Tiêu đề Sau đó, chúng ta có thể gọi điểm cuối và nhận JWT mới. /token Bây giờ, chúng ta có thể sao chép JWT và sử dụng nó để gọi Coffee API. Chúng ta cần chỉ định tiêu đề tương tự như Identity API nếu muốn kiểm tra, tạo và cập nhật điểm cuối. Tiêu đề cũng phải được đặt bằng giá trị . Sau đó, chỉ cần nhấn nút và xem kết quả. Content-Type Authorization Bearer [your JWT value] Gửi Ủy quyền dựa trên vai trò Như bạn còn nhớ, phần tải trọng của JWT là một tập hợp các xác nhận quyền sở hữu với các giá trị chính xác là các cặp khóa-giá trị. Ủy quyền dựa trên vai trò cho phép bạn phân biệt quyền truy cập vào tài nguyên ứng dụng tùy thuộc vào vai trò của người dùng. Nếu chúng tôi cập nhật phương thức trong tệp trong Identity API bằng mã bổ sung xác nhận quyền sở hữu mới cho vai trò; chúng tôi có thể xử lý xác thực dựa trên vai trò trong API Cà phê. là tên được xác định trước của yêu cầu vai trò. Create() TokenController.cs ClaimTypes.Role var claims = new List<Claim> { new Claim(ClaimTypes.Email, request.Email), new Claim(ClaimTypes.Role, "Barista") }; Cập nhật thuộc tính trong tệp chỉ định tên vai trò: Authorize CoffeeController.cs [Authorize(Roles = "Barista")] Giờ đây, tất cả người dùng thực hiện lệnh gọi tới Coffee API đều phải có quyền xác nhận vai trò với giá trị . Nếu không, họ sẽ nhận được mã trạng thái . Barista 403 Forbidden Ủy quyền dựa trên yêu cầu Thuộc tính có thể dễ dàng xử lý xác thực dựa trên vai trò. Nhưng điều gì sẽ xảy ra nếu điều đó là chưa đủ và chúng tôi muốn phân biệt quyền truy cập dựa trên một số thuộc tính người dùng như tuổi hoặc bất kỳ thuộc tính nào khác? Bạn có thể đã đoán rằng bạn có thể thêm các xác nhận quyền sở hữu của mình vào JWT và sử dụng chúng để xây dựng logic ủy quyền. Bản thân ủy quyền dựa trên vai trò là trường hợp đặc biệt của ủy quyền dựa trên yêu cầu, giống như vai trò là cùng một đối tượng yêu cầu thuộc loại được xác định trước. Authorize Hãy cập nhật phương thức trong tệp trong Identity API với mã thêm xác nhận quyền sở hữu mới . Create() TokenController.cs IsGourmet var claims = new List<Claim> { new Claim(ClaimTypes.Email, request.Email), new Claim("IsGourmet", "true") }; Trong tệp Program.cs trong Coffee API, chúng ta cần tạo một chính sách xác minh xác nhận quyền sở hữu và có thể được sử dụng trong thuộc tính . Đoạn mã sau phải được thêm ngay sau lệnh gọi phương thức . Authorize AddAuthentication() builder.Services.AddAuthorization(opts => { opts.AddPolicy("OnlyForGourmet", policy => { policy.RequireClaim("IsGourmet", "true"); }); }); Cập nhật thuộc tính trong tệp chỉ định tên chính sách: Authorize CoffeeController.cs [Authorize(Policy = "OnlyForGourmet")] Bản tóm tắt Chúc mừng! Bạn đã nỗ lực rất nhiều trong việc học JWT trong .NET. Bây giờ, bạn phải hiểu rõ về các nguyên tắc JWT và lý do tại sao việc sử dụng nó để thực hiện ủy quyền trong các ứng dụng .NET lại quan trọng. Nhưng chúng tôi mới chỉ bắt đầu trong lĩnh vực xác thực và ủy quyền trong các ứng dụng ASP.NET Core. Tôi khuyên bạn nên xem tài liệu của Microsoft về các chủ đề chúng ta đã thảo luận trong bài viết này. Ngoài ra còn có rất nhiều khả năng tích hợp sẵn để ủy quyền và quản lý vai trò trong nền tảng .NET. Một bổ sung hay cho bài viết này có thể là của Microsoft về ủy quyền. tài liệu