研究嵌入式设备访问Asp.Net Core Web Api的简单认证方式

研究嵌入式设备访问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

 

posted on 2024-06-09 21:47  SunnyTrudeau  阅读(22)  评论(0编辑  收藏  举报