Trong bài viết này, tôi sẽ cung cấp hướng dẫn về cách xây dựng REST API bằng ASP.NET Core, kết nối với cơ sở dữ liệu hiện có bằng Entity Framework Core và bảo mật API bằng JWT (Json Web Token). Tôi sẽ phát triển một ứng dụng REST API cho hàng tồn kho với các thao tác cơ bản.
Lưu ý: Trong hướng dẫn này, tôi đã sử dụng ASP.NET Core 3.1, Entity Framework Core 3.1 với Visual Studio 2019 16.4.0, SQL Server 2017 và Postman.
Do ngày càng có nhiều loại ứng dụng client khác nhau (ứng dụng dành cho thiết bị di động, ứng dụng SPA dựa trên trình duyệt, ứng dụng dành cho máy tính để bàn, ứng dụng IOT, v.v.), chúng ta cần những cách tốt hơn để truyền dữ liệu từ máy chủ đến máy khách, không phụ thuộc vào công nghệ và ngăn xếp máy chủ.
REST API giải quyết vấn đề này. REST là viết tắt của chuyển trạng thái đại diện (REpresentational State Transfer). Các REST API dựa trên HTTP và cung cấp cho các ứng dụng khả năng giao tiếp bằng cách sử dụng định dạng JSON nhẹ. Chúng chạy trên máy chủ web.
REST bao gồm các thực thể sau:
Resource: Tài nguyên là các thực thể được định danh duy nhất (ví dụ: dữ liệu từ cơ sở dữ liệu, hình ảnh hoặc bất kỳ dữ liệu nào).
Endpoint: Điểm cuối là một tài nguyên có thể được truy cập thông qua một URL.
HTTP method: Giao thức HTTP là loại yêu cầu mà máy khách gửi đến máy chủ. Các hoạt động chúng ta thực hiện trên tài nguyên phải tuân theo điều này.
HTTP header: Tiêu đề HTTP là một cặp khóa-giá trị được sử dụng để chia sẻ thông tin bổ sung giữa máy khách và máy chủ, chẳng hạn như:
Định dạng dữ liệu: JSON là định dạng phổ biến để gửi và nhận dữ liệu thông qua các REST API.
Trong phần trước, chúng ta đã biết REST API là gì, và ở đây chúng ta sẽ xem mã thông báo JWT là gì, và cách bảo mật cho các REST API.
JWT là viết tắt của JSON Web Token. Nó là tiêu chuẩn mở và xác định một cách tốt hơn để truyền dữ liệu an toàn giữa hai thực thể (máy khách và máy chủ).
JWT được ký điện tử bằng khóa bí mật của nhà cung cấp mã thông báo hoặc máy chủ xác thực. JWT giúp máy chủ tài nguyên xác minh mã thông báo bằng cùng một khóa bí mật để bạn có thể tin tưởng vào dữ liệu.
JWT bao gồm ba phần sau:
Header: Tiêu đề là dữ liệu được mã hóa bằng base64url của loại mã thông báo và thuật toán được sử dụng để ký dữ liệu.
Payload: là dữ liệu được mã hóa bằng base64url của các xác nhận quyền sở hữu dự định chia sẻ.
Signature: Chữ ký được tạo bằng cách mã hóa (Header được mã hóa + Payload được mã hóa) sử dụng khóa bí mật.
Mã thông báo JWT cuối cùng sẽ giống như sau: Header.Payload.Signature
Bước 1: Khách hàng yêu cầu mã thông báo
Máy khách gửi yêu cầu đến máy chủ xác thực với thông tin cần thiết để chứng minh danh tính của nó.
Bước 2: Tạo mã thông báo
Máy chủ xác thực nhận yêu cầu mã thông báo và xác minh danh tính. Nếu nó được tìm thấy hợp lệ, mã thông báo sẽ được tạo (như đã giải thích trước đó) với các claim cần thiết và mã thông báo JWT sẽ được gửi lại cho máy khách.
Bước 3: Máy khách gửi mã thông báo đến máy chủ tài nguyên
Đối với mỗi yêu cầu tới Tài nguyên hoặc máy chủ API, máy khách cần kèm theo mã thông báo trong tiêu đề và yêu cầu tài nguyên bằng cách sử dụng URI của nó.
Bước 4: Máy chủ tài nguyên xác minh mã thông báo
Làm theo các bước sau để xác minh mã thông báo:
Không chia sẻ thông tin bí mật bằng JWT, vì header và payload của JWT có thể được giải mã và có thể xem các thông tin này.
Phần sau giải thích cách tạo REST API và bảo mật nó bằng mã thông báo.
Làm theo các bước sau để tạo ứng dụng ASP.NET Core trong Visual Studio 2019:
Bước 1: Đi tới File > New , sau đó chọn Project.
Bước 2: Chọn Create a new project.
Bước 3: Chọn mẫu ASP.NET Core Web Application.
Bước 4: Nhập tên dự án, sau đó nhấp vào Create. Hộp thoại Mẫu dự án sẽ được hiển thị.
Bước 5: Chọn .NET Core, ASP.NET Core 3.1 và mẫu API (được đánh dấu trong phần sau).
Bước 6: Nhấp vào Create. Ứng dụng ASP.NET Core API mẫu sẽ được tạo. Xem cấu trúc dự án trong ảnh chụp màn hình sau.
Theo mặc định, một API mẫu là WeatherForecast được tạo. Chúng ta có thể xóa nó.
Thêm các gói NuGet sau để thao tác với cơ sở dữ liệu SQL Server bằng cách chạy các lệnh sau trong Package Manager Console (Nhấp vào Tools -> NuGet Package Manager -> Package Manager Console).
Gói này giúp tạo bộ điều khiển và chế độ xem.
Install-Package Microsoft.VisualStudio.Web.CodeGeneration.Design -Version 3.1.4
Gói này giúp tạo ngữ cảnh cơ sở dữ liệu và các lớp mô hình từ cơ sở dữ liệu.
Install-Package Microsoft.EntityFrameworkCore.Tools -Version 3.1.8
Nhà cung cấp cơ sở dữ liệu cho phép Entity Framework Core hoạt động với SQL Server.
Install-Package Microsoft.EntityFrameworkCore.SqlServer -Version 3.1.8
Nó cung cấp hỗ trợ để tạo và xác thực mã thông báo JWT.
Install-Package System.IdentityModel.Tokens.Jwt -Version 5.6.0
Đây là phần mềm trung gian cho phép ứng dụng ASP.NET Core nhận mã thông báo mang trong đường dẫn yêu cầu.
Install-Package Microsoft.AspNetCore.Authentication.JwtBearer -Version 3.1.8
Hoặc bạn có thể tìm tên gói trong cửa sổ NuGet Package Manager và cài đặt.
Lưu ý: phiên bản của các gói NuGet có thể mới hơn khi bạn làm theo hướng dẫn này.
Tôi hy vọng bạn đã cài đặt SQL Server 2017 trong máy của mình. (Bạn cũng có thể sử dụng SQL Server 2008, 2012 hoặc 2016.)
Nếu chưa cài SQL Server thì bạn có thể tham khảo hướng dẫn sau để cài đặt.
Bước 1: Tạo cơ sở dữ liệu mới (Inventory).
Bước 2: Đối với ứng dụng này, tôi sẽ tạo các bảng có tên là Product và UserInfo với các thuộc tính cơ bản. Dán truy vấn SQL sau vào cửa sổ Query để tạo các bảng cần thiết.
Create Table Products
(
ProductId Int Identity(1,1) Primary Key,
Name Varchar(100) Not Null,
Category Varchar(100),
Color Varchar(20),
UnitPrice Decimal Not Null,
AvailableQuantity Int Not Null
)
GO
Create Table UserInfo
(
UserId Int Identity(1,1) Not null Primary Key,
FirstName Varchar(30) Not null,
LastName Varchar(30) Not null,
UserName Varchar(30) Not null,
Email Varchar(50) Not null,
Password Varchar(20) Not null,
CreatedDate DateTime Default(GetDate()) Not Null
)
GO
Insert Into UserInfo(FirstName, LastName, UserName, Email, Password)
Values ('Inventory', 'Admin', 'InventoryAdmin', 'InventoryAdmin@abc.com', '$admin@2017')
Bước 3: Chạy lệnh sau trong Package Manager Console để thiết kế ngược cơ sở dữ liệu nhằm tạo context cho cơ sở dữ liệu và các lớp thực thể POCO từ các bảng. Lệnh sẽ chỉ tạo lớp POCO cho các bảng có khóa chính.
Scaffold-DbContext “Server=******;Database=Inventory;Integrated Security=True” Microsoft.EntityFrameworkCore.SqlServer -OutputDir Models
Trong trường hợp của chúng ta, lớp Product, lớp UserInfo và lớp context InventoryContext sẽ được tạo như thể hiện trong ảnh chụp màn hình sau.
Trong tệp lớp InventoryContext được tạo tự động, thông tin đăng nhập cơ sở dữ liệu mà bạn có thể thấy được hard code trong phương thức OnConfiguring.
Việc lưu trữ thông tin đăng nhập SQL Server trong lớp C# không phải là một phương pháp hay. Vì vậy, hãy xóa phương thức OnConfiguring và phương thức khởi tạo không tham sốkhỏi lớp InventoryContext (được đánh dấu ở hình dưới).
Thêm một chuỗi kết nối vào appsetting.json.
Sau đó, đăng ký dịch vụ DbContext (InventoryContext) khi khởi động ứng dụng. Trong đoạn mã sau, chuỗi kết nối được đọc từ tập tin appsetting và được truyền cho các dịch vụ DbContext.
Thêm các namespace sau vào file Startup.cs
:
using InventoryService.Models;
using Microsoft.EntityFrameworkCore;
Sau đó, dịch vụ DbContext này được đưa vào các controller được yêu cầu thông qua dependency injection.
Bây giờ, chúng ta có cơ sở dữ liệu và các lớp thực thể. Làm theo các bước sau để tạo Products Api:
Bước 1: Nhấp chuột phải vào thư mục Controllers, chọn Add, và sau đó bấm Controller.
Bước 2: Chọn APIController with actions bằng cách sử dụng mẫu Entity Framework.
Bước 3: Chọn lớp model Products và lớp ngữ cảnh InventoryContext, sau đó đặt tên cho controller là ProductsController.
Khi chúng ta nhấp vào Add, API sẽ tự động được tạo bằng kỹ thuật tạo mã ASP.NET CORE scaffolding.
Các API sau được tạo:
Theo best practice của REST, mỗi điểm cuối được gán với các phương thức HTTP tương ứng dựa trên hoạt động của nó.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using InventoryService.Models;
using Microsoft.AspNetCore.Authorization;
namespace InventoryService.Controllers
{
[Authorize]
[Route("api/[controller]")]
[ApiController]
public class ProductsController : ControllerBase
{
private readonly InventoryContext _context;
public ProductsController(InventoryContext context)
{
_context = context;
}
[HttpGet]
public async Task<ActionResult<IEnumerable<Products>>> GetProducts()
{
return await _context.Products.ToListAsync();
}
// GET: api/Products/5
[HttpGet("{id}")]
public async Task<ActionResult<Products>> GetProducts(int id)
{
var products = await _context.Products.FindAsync(id);
if (products == null)
{
return NotFound();
}
return products;
}
// PUT: api/Products/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see https://aka.ms/RazorPagesCRUD.
[HttpPut("{id}")]
public async Task<IActionResult> PutProducts(int id, Products products)
{
if (id != products.ProductId)
{
return BadRequest();
}
_context.Entry(products).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!ProductsExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return NoContent();
}
// POST: api/Products
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see https://aka.ms/RazorPagesCRUD.
[HttpPost]
public async Task<ActionResult<Products>> PostProducts(Products products)
{
_context.Products.Add(products);
await _context.SaveChangesAsync();
return CreatedAtAction("GetProducts", new { id = products.ProductId }, products);
}
// DELETE: api/Products/5
[HttpDelete("{id}")]
public async Task<ActionResult<Products>> DeleteProducts(int id)
{
var products = await _context.Products.FindAsync(id);
if (products == null)
{
return NotFound();
}
_context.Products.Remove(products);
await _context.SaveChangesAsync();
return products;
}
private bool ProductsExists(int id)
{
return _context.Products.Any(e => e.ProductId == id);
}
}
}
Bây giờ, chúng ta sẽ thực hiện các sửa đổi sau đối với Get Products API để lọc các sản phẩm:
[HttpGet]
public async Task<ActionResult<IEnumerable<Products>>> GetProducts(bool? inStock, int? skip, int? take)
{
var products = _context.Products.AsQueryable();
if (inStock != null) // Adds the condition to check availability
{
products = _context.Products.Where(i => i.AvailableQuantity > 0);
}
if (skip != null)
{
products = products.Skip((int)skip);
}
if (take != null)
{
products = products.Take((int)take);
}
return await products.ToListAsync();
}
Thay đổi URL khởi chạy thành api/products trong file launcherSettings.js
.
Bấm Run để xem dịch vụ Inventory. Một tab trình duyệt mới sẽ mở ra và chúng ta sẽ có thể xem danh sách sản phẩm.
Dịch vụ REST Inventory của chúng ta đang hoạt động.
Lưu ý: Số cổng localhost có thể khác trên máy của bạn.
Trong phần này, chúng ta sẽ xem cách sử dụng dịch vụ của mình bằng Postman (Postman là một công cụ thử nghiệm API giúp các nhà phát triển sử dụng và kiểm tra cách hoạt động của một API).
Bước 1: Mở Postman và nhập điểm cuối này: https://localhost:44305/api/products.
Bước 2: Chọn phương thức GET và nhấp vào Send. Bây giờ, tất cả các sản phẩm sẽ được liệt kê như trong ảnh chụp màn hình sau.
Bây giờ, hãy áp dụng bộ lọc để trả lại các sản phẩm còn hàng và giới hạn số lượng các mặt hàng bằng cách sử dụng ignore và take.
Nhập điểm cuối sau vào Postman: https://localhost:44305/api/products?Instock=true&ignore=2&take=3
Bây giờ, bạn chỉ có thể xem các sản phẩm có trong kho. Nó bỏ qua hai bản ghi đầu tiên và hiển thị ba bản ghi tiếp theo.
Bước 1: Mở Postman và nhập điểm cuối này: https://localhost:44305/api/products/1.
Bước 2: Chọn phương thức GET và nhấp vào Send. Bây giờ, bạn có thể xem chi tiết của sản phẩm. Ở đây, id sản phẩm là 1.
Lưu ý: Thay đổi ProductId dựa trên các bản ghi có sẵn trong cơ sở dữ liệu của bạn.
Bước 1: Nhập điểm cuối này vào Postman: https://localhost:44305/api/products.
Bước 2: Chọn phương thức POST và đặt tiêu đề: 'Content-Type': 'application/json'.
Bước 3: Trong phần Body>Raw, chọn loại JSON (application/javascript) và nhập thông tin chi tiết sản phẩm như sau.
Nhấp vào Send, một sản phẩm được tạo. Bạn thực hiện lại bước xem danh sách sản phẩm ở phía trên sẽ thấy sản phẩm mới được tạo.
Bước 1 : Nhập điểm cuối này vào Postman: https: // localhost: 44305 / api / products / 7 .
Bước 2 : Chọn phương thức PUT và đặt làm tiêu đề: 'Content-Type': 'application / json' .
Bước 3 : Trong phần Body> Raw, chọn loại JSON (ứng dụng / javascript) và dán chi tiết sản phẩm.
Bằng cách nhấp vào Gửi , chi tiết sản phẩm được cập nhật.
Lưu ý: ProductId phải giống nhau ở điểm cuối và chi tiết sản phẩm (xem phần đánh dấu màu đỏ trong hình trước), nếu không API sẽ đưa ra yêu cầu xấu.
Bước 1: Nhập điểm cuối này vào Postman: https://localhost:44305/api/products/7.
Bước 2: Chọn phương thức DELETE và nhấp vào Send. Bây giờ, sản phẩm sẽ bị xóa khỏi cơ sở dữ liệu.
Chúng ta đã có thể chạy và thử nghiệm API của mình bằng Postman, nhưng vấn đề ở đây là bất kỳ ai biết điểm cuối đều có thể sử dụng nó.
Vì vậy, chúng ta cần một tùy chọn để kiểm soát ai có thể sử dụng dịch vụ của chúng ta. Điều này có thể đạt được nhờ sử dụng mã thông báo JWT.
Bước 1: Tạo một controller có tên là TokenController.
Bước 2: Dán cấu hình JWT bên dưới vào tệp appsetting.json.
Lưu ý: bạn nên thay thông tin ở phần Key
bằng thông tin của bạn.
Bước 3: Thêm action method trong TokenController để thực hiện thao tác sau:
Nhận tên người dùng và mật khẩu làm đầu vào. Sau đó kiểm tra thông tin đăng nhập của người dùng với cơ sở dữ liệu để đảm bảo danh tính của người dùng:
Ví dụ mã sau đây trình bày cách tạo mã thông báo.
using InventoryService.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
namespace InventoryService.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class TokenController : ControllerBase
{
public IConfiguration _configuration;
private readonly InventoryContext _context;
public TokenController(IConfiguration config, InventoryContext context)
{
_configuration = config;
_context = context;
}
[HttpPost]
public async Task<IActionResult> Post(UserInfo _userData)
{
if (_userData != null && _userData.Email != null && _userData.Password != null)
{
var user = await GetUser(_userData.Email, _userData.Password);
if (user != null)
{
//create claims details based on the user information
var claims = new[] {
new Claim(JwtRegisteredClaimNames.Sub, _configuration["Jwt:Subject"]),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.Iat, DateTime.UtcNow.ToString()),
new Claim("Id", user.UserId.ToString()),
new Claim("FirstName", user.FirstName),
new Claim("LastName", user.LastName),
new Claim("UserName", user.UserName),
new Claim("Email", user.Email)
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Jwt:Key"]));
var signIn = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(_configuration["Jwt:Issuer"], _configuration["Jwt:Audience"], claims, expires: DateTime.UtcNow.AddDays(1), signingCredentials: signIn);
return Ok(new JwtSecurityTokenHandler().WriteToken(token));
}
else
{
return BadRequest("Invalid credentials");
}
}
else
{
return BadRequest();
}
}
private async Task<UserInfo> GetUser(string email, string password)
{
return await _context.UserInfo.FirstOrDefaultAsync(u => u.Email == email && u.Password == password);
}
}
}
Bước 1: Nhập điểm cuối này https://localhost: 44305/api/token.
Bước 2: Chọn phương thức POST và đặt tiêu đề thành ‘Content-Type’: ‘application/json’.
Bước 3: Trong phần Body>Raw, chọn loại JSON (application/javascript) và truyền thông tin đăng nhập.
Bằng cách nhấp vào Send, thông tin đăng nhập của người dùng sẽ được kiểm tra và mã thông báo sẽ được tạo và trả lại. Xem phần nội dung được đánh dấu.
Bây giờ, chúng tôi có mã thông báo JWT và sẽ xem cách bảo mật API của chúng ta.
Bước 1: Thêm các namespace sau vào tệp Startup.cs
:
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;
Bước 2: Cấu hình bổ sung Authentication vào ứng dụng trong phương thức ConfigureService của lớp Startup
.
Chúng tôi đã truyền khóa bảo mật được sử dụng khi tạo mã thông báo và chúng tôi cũng đã bật xác thực Nhà phát hành (Issuer) và Đối tượng (Audience).
Ngoài ra, chúng tôi đã đặt SaveToken thành true để lưu trữ mã thông báo trong HTTP Context. Vì vậy, chúng tôi có thể truy cập mã thông báo trong controller khi cần thiết.
Bước 3: Thêm middleware Authentication vào đường dẫn yêu cầu (request pipeline) trong phương thức Configure
của lớp Startup
như sau:
Bước 4: Thêm AuthorizeAttribute cho controller hoặc action method cần được bảo mật.
Ở đây tôi đã thêm ủy quyền cho toàn bộ controller, vì vậy tất cả các API trong controller này sẽ được bảo mật bằng mã thông báo JWT. Bạn cũng có thể thêm ủy quyền cho một phương thức API cụ thể.
Bước 1: Trong Postman, nhập điểm cuối này: https://localhost:44305/api/products.
Bước 2: Chọn phương thức GET và sau đó nhấp vào Send. Bây giờ, bạn có thể thấy mã trạng thái là 401 Unauthorized.
Quyền truy cập ẩn danh đã bị chặn và các API đã được bảo mật. Bây giờ, chúng ta sẽ xem cách truy cập các API bằng cách sử dụng mã thông báo.
Bước 3: Sao chép mã thông báo đã được tạo trong các bước trước đó.
Bước 4: Thêm Bearer vào đầu mã thông báo, bạn sẽ có một mã thông báo như sau.
Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJJbnZlbnRvcnlTZXJ2aWNlQWNjZXNzVG9rZW4iLCJqdGkiOiIwMGY2MjcwYy05NmFlLTQwMDUtOWUwOS00YWVkMjU5NWNjMTIiLCJpYXQiOiIxMS8yOS8yMDE5IDY6MTc6MDIgQU0iLCJJZCI6IjEiLCJGaXJzdE5hbWUiOiJJbnZlbnRvcnkiLCJMYXN0TmFtZSI6IkFkbWluIiwiVXNlck5hbWUiOiJJbnZlbnRvcnlBZG1pbiIsIkVtYWlsIjoiSW52ZW50b3J5QWRtaW5AYWJjLmNvbSIsImV4cCI6MTU3NTA5NDYyMiwiaXNzIjoiSW52ZW50b3J5QXV0aGVudGljYXRpb25TZXJ2ZXIiLCJhdWQiOiJJbnZldG9yeVNlcnZpY2VQb3N0bWFuQ2xpZW50In0.r3gDqAL9FmH2LA_-nfyLDrihfhuY5ODk1bGOLECaKcI
Bước 5: Bây giờ, quay lại danh sách sản phẩm. Trong tiêu đề Authorization, hãy dán mã thông báo trước đó vào trường Value, sau đó bấm Send. Bây giờ bạn có thể xem các sản phẩm từ API.
Trong bài viết này, chúng ta đã học cách tạo REST API bằng ASP.NET Core và Entity Framework Core để thực hiện các thao tác CRUD (Create - Read - Update - Delete) cơ bản, tạo mã thông báo JWT và bảo mật API. Tôi hy vọng bạn thấy bài viết này hữu ích.
Bạn có thể vui lòng tắt trình chặn quảng cáo ❤️ để hỗ trợ chúng tôi duy trì hoạt động của trang web.
Trong bài viết này, chúng ta sẽ tìm hiểu cách khởi tạo các service trong class Startup của ASP.NET Core.
Mục đích của bài viết này là hướng dẫn những điều cơ bản về các giao thức HTTP và cách bắt đầu một HTTP API với ASP.NET Core.
Trong bài viết này, bạn sẽ tìm hiểu sâu về Apache JMeter để load test cho một ứng dụng REST API được viết bằng ASP.NET Core.
Trong bài viết này, chúng ta sẽ tìm hiểu về tiêu chuẩn kỹ thuật open tracing và sử dụng Jaeger để theo dõi phân tán cho các microservices viết bằng ASP.NET Core.