Mô hình Client Credentials là kịch bản cơ bản nhất trong IdentityServer4 (IS4), thường được sử dụng cho các tương tác máy-với-máy (machine-to-machine), nơi không có sự hiện diện của người dùng cuối. Trong hướng dẫn này, chúng ta sẽ xây dựng một hệ thống gồm ba thành phần chính: IdentityServer (Trạm cấp phát Token), Resource API (Tài nguyên cần bảo vệ) và Client (Ứng dụng yêu cầu truy cập).
1. Thiết lập dự án IdentityServer
Đầu tiên, tạo một dự án ASP.NET Core Empty để đóng vai trò là IdentityServer. Dự án này sẽ quản lý các định nghĩa về API và Client.
Cài đặt thư viện:
Install-Package IdentityServer4
Cấu hình tài nguyên và Client (Config.cs):
Chúng ta cần định nghĩa các phạm vi (Scopes) mà API cung cấp và thông tin định danh của ứng dụng khách.
public static class AuthConfig
{
// Định nghĩa các API Resource
public static IEnumerable<ApiResource> GetApiResources()
{
return new List<ApiResource>
{
new ApiResource("backend_api", "Hệ thống quản lý Backend")
};
}
// Cấu hình Client truy cập
public static IEnumerable<Client> GetClients()
{
return new List<Client>
{
new Client
{
ClientId = "service_worker_app",
// Sử dụng cơ chế Client Credentials
AllowedGrantTypes = GrantTypes.ClientCredentials,
// Mã bảo mật để xác thực Client
ClientSecrets =
{
new Secret("key_bao_mat_2024".Sha256())
},
// Các tài nguyên mà Client này được phép truy cập
AllowedScopes = { "backend_api" }
}
};
}
}
Cấu hình trong Startup.cs:
Đăng ký IdentityServer vào hệ thống Dependency Injection và Middleware.
public void ConfigureServices(IServiceCollection services)
{
var authBuilder = services.AddIdentityServer()
.AddDeveloperSigningCredential() // Sử dụng chứng chỉ tạm thời cho phát triển
.AddInMemoryApiResources(AuthConfig.GetApiResources())
.AddInMemoryClients(AuthConfig.GetClients());
}
public void Configure(IApplicationBuilder app)
{
app.UseIdentityServer();
}
2. Xây dựng Resource API
API này sẽ chứa các dữ liệu nhạy cảm mà chỉ những Client có Token hợp lệ mới được truy xuất. Giả sử API chạy tại http://localhost:5001.
Tạo Controller bảo mật:
[Route("api/secure")]
[Authorize]
public class SecureDataController : ControllerBase
{
[HttpGet]
public IActionResult GetData()
{
// Trả về các Claims có trong Token để kiểm tra
return new JsonResult(from c in User.Claims select new { c.Type, c.Value });
}
}
Cấu hình xác thực JWT Bearer:
API cần biết cách xác thực Token được gửi tới từ IdentityServer.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options =>
{
options.Authority = "http://localhost:5000"; // Địa chỉ IdentityServer
options.RequireHttpsMetadata = false;
options.Audience = "backend_api"; // Khớp với ApiResource đã định nghĩa
});
}
public void Configure(IApplicationBuilder app)
{
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
}
3. Xây dựng ứng dụng Client (Console App)
Client sẽ thực hiện quy trình: Khám phá Endpoint -> Yêu cầu Token -> Gọi API với Token đó.
Cài đặt thư viện hỗ trợ:
Install-Package IdentityModel
Triển khai mã nguồn gọi API:
static async Task Main(string[] args)
{
var httpClient = new HttpClient();
// 1. Khám phá các endpoint từ IdentityServer
var discovery = await httpClient.GetDiscoveryDocumentAsync("http://localhost:5000");
if (discovery.IsError) throw new Exception(discovery.Error);
// 2. Yêu cầu Access Token bằng ClientId và ClientSecret
var tokenResponse = await httpClient.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
{
Address = discovery.TokenEndpoint,
ClientId = "service_worker_app",
ClientSecret = "key_bao_mat_2024",
Scope = "backend_api"
});
if (tokenResponse.IsError)
{
Console.WriteLine($"Lỗi lấy token: {tokenResponse.Error}");
return;
}
Console.WriteLine($"Token nhận được: {tokenResponse.AccessToken}");
// 3. Sử dụng Token để gọi API bảo mật
var apiClient = new HttpClient();
apiClient.SetBearerToken(tokenResponse.AccessToken);
var apiResponse = await apiClient.GetAsync("http://localhost:5001/api/secure");
if (!apiResponse.IsSuccessStatusCode)
{
Console.WriteLine($"Lỗi truy cập API: {apiResponse.StatusCode}");
}
else
{
var content = await apiResponse.Content.ReadAsStringAsync();
Console.WriteLine("Dữ liệu từ API:");
Console.WriteLine(content);
}
}
4. Cơ chế hoạt động
Khi ứng dụng Client khởi chạy, nó không trực tiếp gửi thông tin xác thực đến API. Thay vào đó, nó gửi ClientId và ClientSecret đến IdentityServer. Nếu thông tin chính xác, IdentityServer trả về một chuỗi JWT (JSON Web Token).
Client đính kèm Token này vào header Authorization: Bearer [token] khi gọi API. Phía API sẽ gửi Token này (hoặc sử dụng public key từ Discovery Endpoint) để xác minh tính hợp lệ. Nếu thành công, tài nguyên sẽ được trả về.