using System.Security; using System.Security.Claims; using System.Security.Policy; using System.Security.Principal; using System.Text; using System.Text.RegularExpressions; using System.Text.Unicode; using System.Threading.Tasks.Sources; using Microsoft.AspNetCore; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Primitives; using UAParser; namespace AuthStudy.Authentication.Browser { /// /// 浏览器认证 处理器 /// 可实现子接口 IAuthenticationRequestHandler, 以控制后续中间件是否执行. /// public class BrowserAuthenticationHandler : IAuthenticationHandler, IAuthenticationRequestHandler, IAuthenticationSignInHandler, IAuthenticationSignOutHandler where TOptions : AuthenticationSchemeOptions, new() { public string DefaultSchemeName = BrowserAuthenticationDefault.SchemeName; public HttpContext? CurrentHttpContext; public List AllowBrowsers = BrowserAuthenticationDefault.AllowBrowsers; public BrowserAuthenticationOptions Options = BrowserAuthenticationDefault.DefaultOptions; public BrowserAuthenticationHandler() { } /// /// 认证 /// public Task AuthenticateAsync() { //认证结果 AuthenticateResult result = AuthenticateResult.NoResult(); //属性 var properties = new AuthenticationProperties(); properties.Items.Add("AuthenticationBrowser", "浏览器认证属性"); //获取请求浏览器信息,如果请头重复则以后面的为准 var userAgent = CurrentHttpContext?.Request.Headers["User-Agent"].LastOrDefault(); if (userAgent == null) { properties.UpdateTokenValue("AuthenticationBrowser", "失败:获取不到浏览器信息"); result = AuthenticateResult.Fail($"失败:获取不到浏览器信息", properties); return Task.FromResult(result); } ClientInfo clientInfo = Parser.GetDefault().Parse(userAgent); //移动设备认证 if (!Options.AllowMobile && IsMobile(clientInfo.UA.Family)) { properties.UpdateTokenValue("AuthenticationBrowser", "失败:不被允许的可移动设备"); result = AuthenticateResult.Fail($"不被允许的可移动设备:{clientInfo.UA.Family}", properties); return Task.FromResult(result); } //爬虫认证 if (!Options.AllowSpider && clientInfo.Device.IsSpider) { properties.UpdateTokenValue("AuthenticationBrowser", "失败:不允许爬虫"); result = AuthenticateResult.Fail($"不允许爬虫", properties); return Task.FromResult(result); } //浏览器类型认证 if (!AllowBrowsers.Contains(clientInfo.UA.Family)) { properties.UpdateTokenValue("AuthenticationBrowser", "失败:不支持的浏览器"); result = AuthenticateResult.Fail($"不支持的浏览器:{clientInfo.UA.Family}", properties); return Task.FromResult(result); } //声明(身份项) var browser = new Claim("Browser", clientInfo.UA.ToString()); //浏览器 var os = new Claim("OS", clientInfo.OS.ToString()); //操作系统 var device = new Claim("Device", clientInfo.Device.ToString()); //设备 //设备 //声明集合 var Claims = new List(); Claims.Add(browser); Claims.Add(os); Claims.Add(device); //身份:包含声明集合,是声明集合的包装类,一个身份对应多个声明 var claimsIdentity = new ClaimsIdentity(Claims, DefaultSchemeName); //当事人/主角:是身份Identity的包装,对应多个身份 var claimsPrincipal = new ClaimsPrincipal(claimsIdentity); //票据:对Principal的包装,一对一 var ticket = new AuthenticationTicket(claimsPrincipal, DefaultSchemeName); //认证结果:认证信息会写入 当前请求的 User属性中,供下一个授权中间件使用 result = AuthenticateResult.Success(ticket); //调用登陆 //CurrentHttpContext?.SignInAsync(BrowserAuthentication.DefaultAuthenticationScheme, claimsPrincipal, properties); return Task.FromResult(result); } /// /// 无认证:服务端向客户端(浏览器)发质询(要求提供一个新票据),质询体现为 htpp请求的响应。 /// public Task ChallengeAsync(AuthenticationProperties? properties) { properties?.Parameters.Add("x-itme", "无效的认证"); CurrentHttpContext!.Response.StatusCode = 401; if (CurrentHttpContext?.Response.Body.CanWrite ?? false) { var msg = UTF8Encoding.UTF8.GetBytes("认证无效"); CurrentHttpContext!.Response.Body.WriteAsync(msg); } CurrentHttpContext?.Items.Add("认证结束时间", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); return Task.CompletedTask; } /// /// 无权限:服务端向客户端(浏览器)发质询(要求提供一个新票据),质询体现为 htpp请求的响应。 /// public Task ForbidAsync(AuthenticationProperties? properties) { CurrentHttpContext!.Response.StatusCode = 403; if (CurrentHttpContext?.Response.Body.CanWrite ?? false) { var msg = UTF8Encoding.UTF8.GetBytes("无权访问"); CurrentHttpContext!.Response.Body.WriteAsync(msg); } return Task.CompletedTask; } /// /// IAuthenticationRequestHandler /// 返回true,立即反回,不执行后续中间件 /// public Task HandleRequestAsync() { return Task.FromResult(false); } /// /// 初始化 /// public async Task InitializeAsync(AuthenticationScheme scheme, Microsoft.AspNetCore.Http.HttpContext context) { //初始化工作,传递给认证方法和授权中间件 CurrentHttpContext = context; context.Items.Add("认证初始时间", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); await Task.CompletedTask; } /// /// 登陆方法 /// 写入Cookie和Session,认证信息持久化等 /// public Task SignInAsync(ClaimsPrincipal user, AuthenticationProperties? properties) { //导航到主页 //CurrentHttpContext?.Response.Redirect("/api/app/index"); return Task.CompletedTask; } /// /// 退出方法: 反操作登陆方法 /// 清除Cookie和Session,删除认证信息的持久化,作废票据等 /// public Task SignOutAsync(AuthenticationProperties? properties) { //导航到登陆页 CurrentHttpContext?.Response.Redirect("/api/auth/login"); return Task.CompletedTask; } private bool IsMobile(string deviceInfo) { bool isMobile = false; if (string.IsNullOrWhiteSpace(deviceInfo)) { return isMobile; } Regex phoneRegex = new Regex(@"(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino", RegexOptions.IgnoreCase | RegexOptions.Multiline); if (phoneRegex.IsMatch(deviceInfo)) { isMobile = true; } return isMobile; } } }