研究嵌入式裝置訪問Asp.Net Core Web Api的簡單認證方式
Asp.Net Core可以採用JWT保護Web Api,但是有的嵌入式裝置硬體效能比較低,缺乏互動介面,無法使用JWT這麼複雜的方案。根據嵌入式裝置的約束條件,可以採用一些簡單的網路安全保護措施:
1,採用https傳輸;
2,在header自定義認證引數;
3,裝置端定期更換秘鑰,加密儲存;
服務端可以採用中介軟體、自定義認證、篩選器等方式對Web Api進行保護。
建立專案
VS2022建立Asp.Net Core Web Api服務端專案DeviceAuth和控制檯專案DevClient。
DeviceAuth服務端定義一個簡單的控制器,上傳WeatherForecast。
建立裝置認證服務類DevAuthService,從HttpRequest獲取認證頭,解碼得到裝置端認證引數,並跟服務端認證引數比較。如果相等則認證成功,否則失敗。
D:\Software\gitee\DeviceAuth\DeviceAuth\DevAuthService.cs
/// <summary> /// 裝置認證服務 /// </summary> public class DevAuthService { private readonly IConfiguration _configuration; private readonly ILogger<DevAuthService> _logger; public DevAuthService(IConfiguration configuration, ILogger<DevAuthService> logger) { _configuration = configuration; _logger = logger; } /// <summary> /// 處理裝置認證 /// </summary> /// <param name="request"></param> /// <returns></returns> public bool HandleDevAuth(HttpRequest request) { //獲取裝置認證頭 var devAuthHeader = request.Headers.Authorization.FirstOrDefault(x => !string.IsNullOrWhiteSpace(x) && x.StartsWith("DevAuth")); if (string.IsNullOrWhiteSpace(devAuthHeader)) { _logger.LogWarning("找不到裝置認證頭"); return false; } //獲取裝置認證引數base64,實際專案中應該用加密取代base64 var devAuthHeaderValue = AuthenticationHeaderValue.Parse(devAuthHeader!); var paramBase64 = devAuthHeaderValue?.Parameter; if (string.IsNullOrWhiteSpace(paramBase64)) { _logger.LogWarning("裝置認證引數為空"); return false; } //獲取裝置認證引數 byte[] paramAry = Convert.FromBase64String(paramBase64!); var devSecretKey = Encoding.ASCII.GetString(paramAry); var serverSecretKey = _configuration["DevAuth:SecretKey"]; if (devSecretKey == serverSecretKey) { _logger.LogInformation("裝置認證成功"); return true; } else { _logger.LogWarning("裝置認證引數錯誤"); return false; } } }
DevClient裝置端透過HttpClient上傳資料,新增裝置認證頭。
D:\Software\gitee\DeviceAuth\DevClient\Program.cs
/// <summary> /// 上傳資料 /// </summary> /// <param name="isWithDevAuthHeader">是否攜帶裝置認證頭</param> /// <returns></returns> private static async Task<bool> UploadData(bool isWithDevAuthHeader = true) { try { HttpClient client = new HttpClient { BaseAddress = new Uri("https://localhost:7259") }; var forecast = new WeatherForecast() { Date = DateTimeOffset.Now, TemperatureC = System.Random.Shared.Next(0, 30), Summary = "多雲" }; JsonContent jsonContent = JsonContent.Create(forecast); if (isWithDevAuthHeader) { //新增裝置認證頭 string authBase64 = Convert.ToBase64String(Encoding.ASCII.GetBytes("secret")); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(DevAuthConst.DevAuth, authBase64); } for (int i = 1; i <= 3; i++) { string requestUri = $"/WeatherForecast/Add{i}"; var response = await client.PostAsync(requestUri, jsonContent); response.EnsureSuccessStatusCode(); string responseStr = await response.Content.ReadAsStringAsync(); Console.WriteLine($"{requestUri}上傳資料成功, {responseStr}"); } return true; } catch (Exception ex) { Console.WriteLine($"上傳資料失敗, {ex}"); return false; } }
中介軟體方案
建立DevAuthMiddleware裝置認證中介軟體,從HttpContext獲取端點屬性,如果標記了DevAuthAttribute屬性,就呼叫DevAuthService服務進行裝置認證。
D:\Software\gitee\DeviceAuth\DeviceAuth\DevAuthMiddleware.cs
/// <summary> /// 裝置認證中介軟體 /// </summary> public class DevAuthMiddleware { //將服務注入中介軟體的 Invoke 或 InvokeAsync 方法。 使用建構函式注入會引發執行時異常 //private readonly DevAuthService _devAuthService; private readonly RequestDelegate _next; private readonly ILogger<DevAuthMiddleware> _logger; public DevAuthMiddleware(RequestDelegate next, //DevAuthService devAuthService, ILogger<DevAuthMiddleware> logger) { _next = next; //_devAuthService = devAuthService; _logger = logger; } public async Task InvokeAsync(HttpContext context, DevAuthService devAuthService) { bool isAuthSuccess = false; try { //如果端點標記了DevAuthAttribute屬性,需要做裝置認證 var devAuthAttr = context.GetEndpoint()?.Metadata?.GetMetadata<DevAuthAttribute>(); if (devAuthAttr is not null) isAuthSuccess = devAuthService.HandleDevAuth(context.Request); else isAuthSuccess = true; } catch (Exception ex) { _logger.LogWarning(ex, "裝置認證錯誤"); isAuthSuccess = false; } if (isAuthSuccess) { //繼續請求管道 await _next(context); } else { _logger.LogWarning($"{context.Request.Path}, 裝置認證失敗"); context.Response.StatusCode = (int)HttpStatusCode.Unauthorized; } } } public static class DevAuthMiddlewareExtensions { public static IApplicationBuilder UseDevAuth(this IApplicationBuilder app) { ///使用裝置認證中介軟體 return app.UseMiddleware<DevAuthMiddleware>(); } }
定義一個簡單的DevAuthAttribute屬性。
D:\Software\gitee\DeviceAuth\DeviceAuth\DevAuthAttribute.cs
D:\Software\gitee\DeviceAuth\DeviceAuth\DevAuthAttribute.cs /// <summary> /// 需要裝置認證 /// </summary> [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] public class DevAuthAttribute : Attribute { }
在控制器函式上面標記DevAuthAttribute屬性,表明需要做裝置認證。
D:\Software\gitee\DeviceAuth\DeviceAuth\Controllers\WeatherForecastController.cs /// <summary> /// 採用中介軟體對標記了DevAuth屬性的端點進行認證 /// </summary> /// <param name="weatherForecast"></param> /// <returns></returns> [DevAuth] [HttpPost("Add1")] public int Add1(WeatherForecast weatherForecast) { _logger.LogInformation($"{DateTimeOffset.Now}: Add1收到資料{weatherForecast}"); return 1; }
Program使用中介軟體app.UseDevAuth()。
自定義認證方案
建立DevAuthorizationHandler自定義裝置認證處理器,獲取Request的認證頭,如果認證透過,建立一個AuthenticationTicket身份標識,包含裝置角色Claim(ClaimTypes.Role, DevAuthConst.DevRole)。
D:\Software\gitee\DeviceAuth\DeviceAuth\DevAuthorizationHandler.cs
/// <summary> /// 自定義裝置認證處理器 /// </summary> public class DevAuthorizationHandler : AuthenticationHandler<AuthenticationSchemeOptions> { public const string DevAuthSchemeName = "DevAuthScheme"; public const string DevAuthPolicyName = "DevAuthPolicy"; private readonly IServiceScopeFactory _serviceScopeFactory; private readonly DevAuthService _devAuthService; private readonly ILogger<DevAuthorizationHandler> _logger; public DevAuthorizationHandler(IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory loggerFactory, UrlEncoder encoder, ISystemClock clock, IServiceScopeFactory serviceScopeFactory, DevAuthService devAuthService) : base(options, loggerFactory, encoder, clock) { _serviceScopeFactory = serviceScopeFactory; _devAuthService = devAuthService; _logger = loggerFactory.CreateLogger<DevAuthorizationHandler>(); } protected override async Task<AuthenticateResult> HandleAuthenticateAsync() { await Task.CompletedTask; bool isAuthSuccess = false; try { //處理裝置認證 isAuthSuccess = _devAuthService.HandleDevAuth(Request); } catch (Exception ex) { _logger.LogWarning(ex, "裝置認證錯誤"); isAuthSuccess = false; } if (isAuthSuccess) { var claims = new List<Claim>() { new Claim(ClaimTypes.Role, DevAuthConst.DevRole) }; var identities = new List<ClaimsIdentity>() { new ClaimsIdentity(claims, Scheme.Name) }; var principal = new ClaimsPrincipal(identities); var ticket = new AuthenticationTicket(principal, Scheme.Name); return AuthenticateResult.Success(ticket); } else { _logger.LogWarning($"{Request.Path}, 裝置認證失敗"); return AuthenticateResult.Fail("裝置認證失敗"); } } }
在program註冊自定義認證方案。
D:\Software\gitee\DeviceAuth\DeviceAuth\Program.cs
builder.Services.TryAddScoped<DevAuthorizationHandler>(); builder.Services.AddAuthentication(DevAuthorizationHandler.DevAuthSchemeName) //builder.Services.AddAuthentication(options => //{ // options.DefaultAuthenticateScheme = DevAuthorizationHandler.DevAuthSchemeName; // options.DefaultChallengeScheme = null; //}) .AddScheme<AuthenticationSchemeOptions, DevAuthorizationHandler>(DevAuthorizationHandler.DevAuthSchemeName, null); app.UseAuthentication(); app.UseAuthorization();
在控制器函式上面標記Authorize(Roles = DevAuthConst.DevRole),表明需要裝置角色的身份標記的認證。
D:\Software\gitee\DeviceAuth\DeviceAuth\Controllers\WeatherForecastController.cs
/// <summary> /// 採用DevAuthorizationHandler自定義認證方案 /// </summary> /// <param name="weatherForecast"></param> /// <returns></returns> [Authorize(Roles = DevAuthConst.DevRole)] //[Authorize(DevAuthorizationHandler.DevAuthPolicyName)] [HttpPost("Add2")] public int Add2(WeatherForecast weatherForecast) { _logger.LogInformation($"{DateTimeOffset.Now}: Add2收到資料{weatherForecast}"); return 1; }
篩選器方案
建立DevAuthorizeFilter裝置認證篩選器,檢查HttpContext.Request的認證頭。
D:\Software\gitee\DeviceAuth\DeviceAuth\DevAuthorizeFilter.cs
/// <summary> /// 裝置認證篩選器 /// </summary> public class DevAuthorizeFilter : IAsyncAuthorizationFilter { private readonly DevAuthService _devAuthService; private readonly ILogger<DevAuthorizeFilter> _logger; public DevAuthorizeFilter(DevAuthService devAuthService, ILogger<DevAuthorizeFilter> logger) { _devAuthService = devAuthService; _logger = logger; } public async Task OnAuthorizationAsync(AuthorizationFilterContext context) { await Task.CompletedTask; bool isAuthSuccess = false; try { //處理裝置認證 isAuthSuccess = _devAuthService.HandleDevAuth(context.HttpContext.Request); } catch (Exception ex) { _logger.LogWarning(ex, "裝置認證錯誤"); isAuthSuccess = false; } if (isAuthSuccess) { } else { _logger.LogWarning($"{context.HttpContext.Request.Path}, 裝置認證失敗"); context.Result = new UnauthorizedResult(); } } }
在控制器函式上面標記ServiceFilter<DevAuthorizeFilter>篩選器,表明需要裝置認證。
D:\Software\gitee\DeviceAuth\DeviceAuth\Controllers\WeatherForecastController.cs
/// <summary> /// 採用DevAuthorizeFilter篩選器方案 /// </summary> /// <param name="weatherForecast"></param> /// <returns></returns> [ServiceFilter<DevAuthorizeFilter>] [HttpPost("Add3")] public int Add3(WeatherForecast weatherForecast) { _logger.LogInformation($"{DateTimeOffset.Now}: Add3收到資料{weatherForecast}"); return 1; } }
總結
測試3種認證方案都可以。中介軟體,自定義認證,篩選器都可以實現裝置認證的目標。
中介軟體需要獲取每次訪問的端點,感覺比較麻煩。
自定義認證程式碼比較複雜。
推薦採用篩選器,程式碼簡單。
DEMO程式碼地址:https://gitee.com/woodsun/deviceauth