.net8 使用 license 证书授权案例解析
创建 webapi 项目
使用 .NET CLI
创建一个 ASP.NET Core Web API
应用,并添加指定的 NuGet
包,可以按照以下步骤操作:
- 创建
ASP.NET Core Web API
项目:
dotnet new webapi -n WebAppLicense
cd WebAppLicense
- 添加
Standard.Licensing
包:
dotnet add package Standard.Licensing --version 1.2.1
- 添加
Swashbuckle.AspNetCore
包:
dotnet add package Swashbuckle.AspNetCore --version 7.3.1
- 验证
csproj
文件: 打开WebAppLicense.csproj
文件,确保以下内容已添加:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Standard.Licensing" Version="1.2.1" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="7.3.1" />
</ItemGroup>
</Project>
到此处可以启动运行项目,目的是验证默认环境是否正确,确保进入下一环节的正常运行。
改造项目使用 license 授权
项目结构
依照如下项目结构改造,信息如下:
修改 Program.cs 文件
修改 Program.cs
文件,代码如下:
using System.Net.Mime;
using WebAppLicense.Services;
using WebAppLicense.Environments;
using WebAppLicense.Middlewares;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
// 添加服务到容器
builder.Services.AddSingleton<ILicenseService, LicenseService>();
var app = builder.Build();
// 使用自定义许可证验证中间件
// app.UseLicenseValidation();
// 在应用启动时验证许可证(此处可封装扩展中间件UseLicenseValidation)
#region 在应用启动时验证许可证
// 定义响应报文类型
string contentType = $"{MediaTypeNames.Text.Plain};charset=utf-8";
// 定义要跳过的路由路径
List<string> skipPaths = ["/api/License/activate", "/api/License/types"];
app.Use(async (context, next) =>
{
HttpContext httpContext = context;
var pathString = httpContext.Request.Path;
// 检查当前请求路径是否与 Swagger 相关
if (pathString.StartsWithSegments("/swagger")
|| pathString.StartsWithSegments("/scalar")
|| pathString.StartsWithSegments("/favicon.ico"))
{
await next(httpContext);
return;
}
// 检查当前请求路径是否在跳过列表中
if (skipPaths.Contains(pathString))
{
await next(httpContext);
return;
}
// 假设许可证内容存储在环境变量中
var licenseContentBase64 = Environment.GetEnvironmentVariable(LicenseEnv.LICENSE_CONTENT_SECRET);
if (string.IsNullOrEmpty(licenseContentBase64))
{
httpContext.Response.StatusCode = StatusCodes.Status403Forbidden;
httpContext.Response.ContentType = contentType;
await httpContext.Response.WriteAsync("许可证未提供。");
return;
}
var licenseService = httpContext.RequestServices.GetRequiredService<ILicenseService>();
var (isValid, msg) = licenseService.ValidateLicense(licenseContentBase64);
if (!isValid)
{
httpContext.Response.StatusCode = StatusCodes.Status403Forbidden;
httpContext.Response.ContentType = contentType;
await httpContext.Response.WriteAsync($"许可证无效。{msg}");
return;
}
// 如果许可证有效,继续处理请求
await next(httpContext);
});
#endregion
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseAuthorization();
app.MapControllers();
await app.RunAsync();
项目相关接口
- 默认天气接口,
WeatherForecastController
using Microsoft.AspNetCore.Mvc;
using WebAppLicense.Model.ViewModel;
namespace WebAppLicense.Controllers;
[ApiController]
[Route("[controller]")]
public class WeatherForecastController(ILogger<WeatherForecastController> logger) : ControllerBase
{
private static readonly string[] Summaries =
[
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
];
[HttpGet(Name = "GetWeatherForecast")]
public IEnumerable<WeatherForecast> Get()
{
return Enumerable.Range(1, 3).Select(index => new WeatherForecast
{
Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
}).ToArray();
}
}
- 新增
license
接口,LicenseController
using Microsoft.AspNetCore.Mvc;
using WebAppLicense.Model.ViewModel;
using WebAppLicense.Services;
namespace WebAppLicense.Controllers;
[Route("api/[controller]")]
[ApiController]
public class LicenseController(ILicenseService licenseService) : ControllerBase
{
// 提供的 license 授权类型
[HttpGet("types")]
public Dictionary<string, int> GetLicenseTypes()
{
var licenseTypes = licenseService.GetLicenseTypes();
return licenseTypes;
}
// 激活 license 授权
[HttpPost("activate")]
public async Task<IActionResult> ActivateLicense([FromBody] LicenseActivationRequest request)
{
var licenseContent = await licenseService.GenerateLicenseAsync(request);
return Ok(new
{
request.LicenseType,
License = licenseContent,
});
}
}
ILicenseService
服务
using WebAppLicense.Model.ViewModel;
namespace WebAppLicense.Services;
public interface ILicenseService
{
Dictionary<string, int> GetLicenseTypes();
ValueTask<string> GenerateLicenseAsync(LicenseActivationRequest request, bool isSaveLicenseFile = false);
(bool state, string msg) ValidateLicense(string licenseContentBase64);
}
项目基本信息解释到此,其他详细信息,请查看 WebAppLicense 项目。
授权测试
启动项目,显示页面信息如下:
项目相关接口信息说明:
WeatherForecast
默认的天气接口,用于测试项目是否使用授权模式,如果为授权则提示相应的信息。
License
新增的接口,分别用于提供授权类型和授权激活(通过密钥方式),因此这两个接口被设置为 白名单
。
测试流程如下:
首先访问接口 /WeatherForecast
,执行如下命令,
curl -X 'GET' \
'http://localhost:5089/WeatherForecast' \
-H 'accept: text/plain'
输出信息:
从接口返回信息,可以看出该接口在未获得授权 license
的时候被请求。
此时我们使用授权激活接口,通过密钥方式激活授权 license
,然后再次访问接口 /WeatherForecast
,查看接口响应信息,验证接口是否已经授权。
api/License/types
访问接口,执行如下命令:
curl -X 'GET' \
'http://localhost:5089/api/License/types' \
-H 'accept: text/plain'
接口输出信息:
api/License/activate
访问接口,执行如下命令:
curl -X 'POST' \
'http://localhost:5089/api/License/activate' \
-H 'accept: */*' \
-H 'Content-Type: application/json' \
-d '{
"licenseType": "trial",
"utilization": 5,
"passPhrase": "123456",
"expirationDate": "2025-03-03T13:30:02.644Z",
"userName": "jeff",
"email": "jeff@qq.com",
"productFeatures": {
"additionalProp1": "yes",
"additionalProp2": "no",
"additionalProp3": "yes"
}
}'
接口输出信息:
可以看出,上面两个白名单
中的接口均可访问,接下来我们再次访问默认的天气接口 /WeatherForecast
,验证是否被授权激活。
WeatherForecast
再次执行同样的命令:
curl -X 'GET' \
'http://localhost:5089/WeatherForecast' \
-H 'accept: text/plain'
输出信息:
通过我们上面两次的接口测试验证,默认天气接口已经正常响应数据,说明该应用程序已经使用了 License
授权模式。