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.

154 lines
6.0 KiB
C#

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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 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 = Context.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, Scheme.Name);
//当事人/主角是身份Identity的包装对应多个身份
var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
//票据对Principal的包装一对一
var ticket = new AuthenticationTicket(claimsPrincipal, Scheme.Name);
//认证结果:认证信息会写入 当前请求的 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", "无效的认证");
Context.Response.StatusCode = 401;
if (Context?.Response.Body.CanWrite ?? false)
{
var msg = UTF8Encoding.UTF8.GetBytes("认证无效");
var t = Context!.Response.Body.WriteAsync(msg);
}
Context!.Items.Add("认证结束时间", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
return Task.CompletedTask;
}
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;
}
}
}