You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

167 lines
6.5 KiB
C#

using System.Security;
using System.Security.Claims;
using System.Security.Policy;
using System.Security.Principal;
using System.Text;
using System.Text.Encodings.Web;
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.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;
using UAParser;
namespace AuthStudy.Authentication.Browser
{
/// <summary>
/// 浏览器认证处理器:基于默认类型实现
/// </summary>
public class BrowserAuthenticationHandler : AuthenticationHandler<BrowserAuthenticationOptions>
{
public string DefaultSchemeName = BrowserAuthenticationDefault.SchemeName;
public HttpContext? CurrentHttpContext;
public BrowserAuthenticationHandler
(
IOptionsMonitor<BrowserAuthenticationOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock
) : base(options, logger, encoder, clock)
{
}
/// <summary>
/// 认证
/// </summary>
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
//认证结果
AuthenticateResult result;
//属性
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 (!Options.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<Claim>
{
browser,
os,
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);
}
/// <summary>
/// 质询:服务端向客户端(浏览器)发质询(要求提供一个新票据),质询体现为 htpp请求的响应。
/// </summary>
protected override Task HandleChallengeAsync(AuthenticationProperties? properties)
{
properties?.Parameters.Add("x-itme", "无效的认证");
if (CurrentHttpContext != null)
{
CurrentHttpContext.Response.StatusCode = 401;
if (CurrentHttpContext?.Response.Body.CanWrite ?? false)
{
var msg = UTF8Encoding.UTF8.GetBytes("认证无效");
var t = CurrentHttpContext!.Response.Body.WriteAsync(msg);
}
CurrentHttpContext!.Items.Add("认证结束时间", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
}
return Task.CompletedTask;
}
protected override Task InitializeHandlerAsync()
{
CurrentHttpContext = base.Context;
return base.InitializeHandlerAsync();
}
private static bool IsMobile(string deviceInfo)
{
bool isMobile = false;
if (string.IsNullOrWhiteSpace(deviceInfo))
{
return isMobile;
}
Regex phoneRegex = new(@"(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;
}
}
}