feat: 浏览器认证

main
bicijinlian 2 years ago
parent 5d7c56c1a8
commit 0c1ebef1b3

@ -17,28 +17,25 @@ using UAParser;
namespace AuthStudy.Authentication.Browser namespace AuthStudy.Authentication.Browser
{ {
/// <summary> /// <summary>
/// 浏览器认证 处理器 /// 浏览器认证处理器:基于接口实现
/// 可实现子接口 IAuthenticationRequestHandler, 以控制后续中间件是否执行. /// 可实现子接口 IAuthenticationRequestHandler, 以控制后续中间件是否执行.
/// </summary> /// </summary>
public class BrowserAuthenticationHandler2<TOptions> : public class BrowserAuthenticationBaseHandler :
IAuthenticationHandler, IAuthenticationHandler,
IAuthenticationRequestHandler, IAuthenticationRequestHandler,
IAuthenticationSignInHandler, IAuthenticationSignInHandler,
IAuthenticationSignOutHandler IAuthenticationSignOutHandler
where TOptions : AuthenticationSchemeOptions, new()
{ {
public string DefaultSchemeName = BrowserAuthenticationDefault.SchemeName; public string DefaultSchemeName = BrowserAuthenticationDefault.SchemeName;
public HttpContext? CurrentHttpContext; public HttpContext? CurrentHttpContext;
public List<string> AllowBrowsers = BrowserAuthenticationDefault.AllowBrowsers; public BrowserAuthenticationOptions Options;
public BrowserAuthenticationOptions Options = BrowserAuthenticationDefault.DefaultOptions;
public BrowserAuthenticationBaseHandler(BrowserAuthenticationOptions option)
public BrowserAuthenticationHandler2()
{ {
Options = option;
} }
/// <summary> /// <summary>
@ -65,7 +62,7 @@ namespace AuthStudy.Authentication.Browser
ClientInfo clientInfo = Parser.GetDefault().Parse(userAgent); ClientInfo clientInfo = Parser.GetDefault().Parse(userAgent);
//移动设备认证 //移动设备认证
if (!Options.AllowMobile && BrowserAuthenticationHandler2<TOptions>.IsMobile(clientInfo.UA.Family)) if (!Options.AllowMobile && IsMobile(clientInfo.UA.Family))
{ {
properties.UpdateTokenValue("AuthenticationBrowser", "失败:不被允许的可移动设备"); properties.UpdateTokenValue("AuthenticationBrowser", "失败:不被允许的可移动设备");
result = AuthenticateResult.Fail($"不被允许的可移动设备:{clientInfo.UA.Family}", properties); result = AuthenticateResult.Fail($"不被允许的可移动设备:{clientInfo.UA.Family}", properties);
@ -81,7 +78,7 @@ namespace AuthStudy.Authentication.Browser
} }
//浏览器类型认证 //浏览器类型认证
if (!AllowBrowsers.Contains(clientInfo.UA.Family)) if (!Options.AllowBrowsers.Contains(clientInfo.UA.Family))
{ {
properties.UpdateTokenValue("AuthenticationBrowser", "失败:不支持的浏览器"); properties.UpdateTokenValue("AuthenticationBrowser", "失败:不支持的浏览器");
result = AuthenticateResult.Fail($"不支持的浏览器:{clientInfo.UA.Family}", properties); result = AuthenticateResult.Fail($"不支持的浏览器:{clientInfo.UA.Family}", properties);
@ -91,11 +88,11 @@ namespace AuthStudy.Authentication.Browser
//声明(身份项) //声明(身份项)
var browser = new Claim("Browser", clientInfo.UA.ToString()); //浏览器 var browser = new Claim("Browser", clientInfo.UA.ToString()); //浏览器
var os = new Claim("OS", clientInfo.OS.ToString()); //操作系统 var os = new Claim("OS", clientInfo.OS.ToString()); //操作系统
var device = new Claim("Device", clientInfo.Device.ToString()); //设备 //设备 var device = new Claim("Device", clientInfo.Device.ToString()); //设备 //设备
//声明集合 //声明集合
var Claims = new List<Claim> var claims = new List<Claim>
{ {
browser, browser,
os, os,
@ -103,7 +100,7 @@ namespace AuthStudy.Authentication.Browser
}; };
//身份:包含声明集合,是声明集合的包装类,一个身份对应多个声明 //身份:包含声明集合,是声明集合的包装类,一个身份对应多个声明
var claimsIdentity = new ClaimsIdentity(Claims, DefaultSchemeName); var claimsIdentity = new ClaimsIdentity(claims, DefaultSchemeName);
//当事人/主角是身份Identity的包装对应多个身份 //当事人/主角是身份Identity的包装对应多个身份
var claimsPrincipal = new ClaimsPrincipal(claimsIdentity); var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
@ -121,16 +118,16 @@ namespace AuthStudy.Authentication.Browser
} }
/// <summary> /// <summary>
/// 无认证:服务端向客户端(浏览器)发质询(要求提供一个新票据),质询体现为 htpp请求的响应。 /// 无认证:服务端向客户端(浏览器)发质询(要求提供一个新票据),质询体现为 http请求的响应。
/// </summary> /// </summary>
public async Task ChallengeAsync(AuthenticationProperties? properties) public async Task ChallengeAsync(AuthenticationProperties? properties)
{ {
properties?.Parameters.Add("x-itme", "无效的认证"); properties?.Parameters.Add("x-item", "无效的认证");
CurrentHttpContext!.Response.StatusCode = 401; CurrentHttpContext!.Response.StatusCode = 401;
if (CurrentHttpContext?.Response.Body.CanWrite ?? false) if (CurrentHttpContext?.Response.Body.CanWrite ?? false)
{ {
var msg = UTF8Encoding.UTF8.GetBytes("认证无效"); var msg = Encoding.UTF8.GetBytes("认证无效");
await CurrentHttpContext!.Response.Body.WriteAsync(msg); await CurrentHttpContext!.Response.Body.WriteAsync(msg);
} }
CurrentHttpContext?.Items.Add("认证结束时间", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); CurrentHttpContext?.Items.Add("认证结束时间", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
@ -138,14 +135,14 @@ namespace AuthStudy.Authentication.Browser
} }
/// <summary> /// <summary>
/// 无权限:服务端向客户端(浏览器)发质询(要求提供一个新票据),质询体现为 htpp请求的响应。 /// 无权限:服务端向客户端(浏览器)发质询(要求提供一个新票据),质询体现为 http请求的响应。
/// </summary> /// </summary>
public async Task ForbidAsync(AuthenticationProperties? properties) public async Task ForbidAsync(AuthenticationProperties? properties)
{ {
CurrentHttpContext!.Response.StatusCode = 403; CurrentHttpContext!.Response.StatusCode = 403;
if (CurrentHttpContext?.Response.Body.CanWrite ?? false) if (CurrentHttpContext?.Response.Body.CanWrite ?? false)
{ {
var msg = UTF8Encoding.UTF8.GetBytes("无权访问"); var msg = Encoding.UTF8.GetBytes("无权访问");
await CurrentHttpContext!.Response.Body.WriteAsync(msg); await CurrentHttpContext!.Response.Body.WriteAsync(msg);
} }
//return Task.CompletedTask; //return Task.CompletedTask;
@ -163,7 +160,7 @@ namespace AuthStudy.Authentication.Browser
/// <summary> /// <summary>
/// 初始化 /// 初始化
/// </summary> /// </summary>
public async Task InitializeAsync(AuthenticationScheme scheme, Microsoft.AspNetCore.Http.HttpContext context) public async Task InitializeAsync(AuthenticationScheme scheme, HttpContext context)
{ {
//初始化工作,传递给认证方法和授权中间件 //初始化工作,传递给认证方法和授权中间件
CurrentHttpContext = context; CurrentHttpContext = context;

@ -12,6 +12,7 @@ namespace AuthStudy.Authentication.Browser
{ {
public static class BrowserAuthenticationExtensions public static class BrowserAuthenticationExtensions
{ {
#region 基于接口的扩展注册
public static IServiceCollection AddBrowserAuthentication public static IServiceCollection AddBrowserAuthentication
( (
this IServiceCollection builder, this IServiceCollection builder,
@ -36,7 +37,7 @@ namespace AuthStudy.Authentication.Browser
options.DefaultSignInScheme = AuthenticationSchemeName; options.DefaultSignInScheme = AuthenticationSchemeName;
options.DefaultSignOutScheme = AuthenticationSchemeName; options.DefaultSignOutScheme = AuthenticationSchemeName;
options.AddScheme<BrowserAuthenticationHandler>(AuthenticationSchemeName, AuthenticationDispalyName); options.AddScheme<BrowserAuthenticationBaseHandler>(AuthenticationSchemeName, AuthenticationDispalyName);
}); });
return builder; return builder;
@ -47,9 +48,14 @@ namespace AuthStudy.Authentication.Browser
{ {
BrowserAuthenticationOptions defaultOption = option ?? new(); BrowserAuthenticationOptions defaultOption = option ?? new();
builder.AddSingleton(defaultOption); builder.AddSingleton(defaultOption);
builder.AddSingleton<BrowserAuthenticationHandler>(); builder.AddSingleton<BrowserAuthenticationBaseHandler>();
return builder; return builder;
} }
#endregion
#region 基于默认基数的扩展注册
#endregion
} }
} }

@ -3,6 +3,7 @@ using System.Security.Claims;
using System.Security.Policy; using System.Security.Policy;
using System.Security.Principal; using System.Security.Principal;
using System.Text; using System.Text;
using System.Text.Encodings.Web;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Text.Unicode; using System.Text.Unicode;
using System.Threading.Tasks.Sources; using System.Threading.Tasks.Sources;
@ -10,6 +11,8 @@ using System.Threading.Tasks.Sources;
using Microsoft.AspNetCore; using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives; using Microsoft.Extensions.Primitives;
using UAParser; using UAParser;
@ -17,31 +20,30 @@ using UAParser;
namespace AuthStudy.Authentication.Browser namespace AuthStudy.Authentication.Browser
{ {
/// <summary> /// <summary>
/// 浏览器认证 处理器 /// 浏览器认证处理器:基于默认类型实现
/// 可实现子接口 IAuthenticationRequestHandler, 以控制后续中间件是否执行.
/// </summary> /// </summary>
public class BrowserAuthenticationHandler : public class BrowserAuthenticationHandler : AuthenticationHandler<BrowserAuthenticationOptions>
IAuthenticationHandler,
IAuthenticationRequestHandler,
IAuthenticationSignInHandler,
IAuthenticationSignOutHandler
{ {
public string DefaultSchemeName = BrowserAuthenticationDefault.SchemeName; public string DefaultSchemeName = BrowserAuthenticationDefault.SchemeName;
public HttpContext? CurrentHttpContext; public HttpContext? CurrentHttpContext;
public BrowserAuthenticationOptions Options;
public BrowserAuthenticationHandler(BrowserAuthenticationOptions option) public BrowserAuthenticationHandler
(
IOptionsMonitor<BrowserAuthenticationOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock
) : base(options, logger, encoder, clock)
{ {
Options = option;
} }
/// <summary> /// <summary>
/// 认证 /// 认证
/// </summary> /// </summary>
public Task<AuthenticateResult> AuthenticateAsync() protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{ {
//认证结果 //认证结果
AuthenticateResult result; AuthenticateResult result;
@ -88,11 +90,11 @@ namespace AuthStudy.Authentication.Browser
//声明(身份项) //声明(身份项)
var browser = new Claim("Browser", clientInfo.UA.ToString()); //浏览器 var browser = new Claim("Browser", clientInfo.UA.ToString()); //浏览器
var os = new Claim("OS", clientInfo.OS.ToString()); //操作系统 var os = new Claim("OS", clientInfo.OS.ToString()); //操作系统
var device = new Claim("Device", clientInfo.Device.ToString()); //设备 //设备 var device = new Claim("Device", clientInfo.Device.ToString()); //设备 //设备
//声明集合 //声明集合
var claims = new List<Claim> var Claims = new List<Claim>
{ {
browser, browser,
os, os,
@ -100,7 +102,7 @@ namespace AuthStudy.Authentication.Browser
}; };
//身份:包含声明集合,是声明集合的包装类,一个身份对应多个声明 //身份:包含声明集合,是声明集合的包装类,一个身份对应多个声明
var claimsIdentity = new ClaimsIdentity(claims, DefaultSchemeName); var claimsIdentity = new ClaimsIdentity(Claims, DefaultSchemeName);
//当事人/主角是身份Identity的包装对应多个身份 //当事人/主角是身份Identity的包装对应多个身份
var claimsPrincipal = new ClaimsPrincipal(claimsIdentity); var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
@ -118,78 +120,19 @@ namespace AuthStudy.Authentication.Browser
} }
/// <summary> /// <summary>
/// 无认证:服务端向客户端(浏览器)发质询(要求提供一个新票据),质询体现为 http请求的响应。 /// 质询:服务端向客户端(浏览器)发质询(要求提供一个新票据),质询体现为 htpp请求的响应。
/// </summary> /// </summary>
public async Task ChallengeAsync(AuthenticationProperties? properties) protected override Task HandleChallengeAsync(AuthenticationProperties? properties)
{ {
properties?.Parameters.Add("x-item", "无效的认证"); properties?.Parameters.Add("x-itme", "无效的认证");
CurrentHttpContext!.Response.StatusCode = 401; CurrentHttpContext!.Response.StatusCode = 401;
if (CurrentHttpContext?.Response.Body.CanWrite ?? false) if (CurrentHttpContext?.Response.Body.CanWrite ?? false)
{ {
var msg = Encoding.UTF8.GetBytes("认证无效"); var msg = UTF8Encoding.UTF8.GetBytes("认证无效");
await CurrentHttpContext!.Response.Body.WriteAsync(msg); var t = CurrentHttpContext!.Response.Body.WriteAsync(msg);
} }
CurrentHttpContext?.Items.Add("认证结束时间", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); CurrentHttpContext?.Items.Add("认证结束时间", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
//return Task.CompletedTask;
}
/// <summary>
/// 无权限:服务端向客户端(浏览器)发质询(要求提供一个新票据),质询体现为 http请求的响应。
/// </summary>
public async Task ForbidAsync(AuthenticationProperties? properties)
{
CurrentHttpContext!.Response.StatusCode = 403;
if (CurrentHttpContext?.Response.Body.CanWrite ?? false)
{
var msg = Encoding.UTF8.GetBytes("无权访问");
await CurrentHttpContext!.Response.Body.WriteAsync(msg);
}
//return Task.CompletedTask;
}
/// <summary>
/// IAuthenticationRequestHandler
/// 返回true,立即反回,不执行后续中间件
/// </summary>
public Task<bool> HandleRequestAsync()
{
return Task.FromResult(false);
}
/// <summary>
/// 初始化
/// </summary>
public async Task InitializeAsync(AuthenticationScheme scheme, HttpContext context)
{
//初始化工作,传递给认证方法和授权中间件
CurrentHttpContext = context;
context.Items.Add("认证初始时间", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
await Task.CompletedTask;
}
/// <summary>
/// 登陆方法
/// 写入Cookie和Session认证信息持久化等
/// </summary>
public Task SignInAsync(ClaimsPrincipal user, AuthenticationProperties? properties)
{
//导航到主页
//CurrentHttpContext?.Response.Redirect("/api/app/index");
return Task.CompletedTask;
}
/// <summary>
/// 退出方法: 反操作登陆方法
/// 清除Cookie和Session删除认证信息的持久化作废票据等
/// </summary>
public Task SignOutAsync(AuthenticationProperties? properties)
{
//导航到登陆页
CurrentHttpContext?.Response.Redirect("/api/auth/login");
return Task.CompletedTask; return Task.CompletedTask;
} }
@ -210,5 +153,6 @@ namespace AuthStudy.Authentication.Browser
return isMobile; return isMobile;
} }
} }
} }

@ -1,3 +1,4 @@
using AuthStudy.Authentication.Browser;
using AuthStudy.WebApp.VModels; using AuthStudy.WebApp.VModels;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
@ -15,7 +16,8 @@ namespace AuthStudy.WebApp.Controllers
} }
[Authorize] [Authorize(AuthenticationSchemes = BrowserAuthenticationDefault.SchemeName)]
[Authorize(AuthenticationSchemes = "BrowserAuthenticationHandlerByBase")]
[HttpGet] [HttpGet]
public IActionResult GetAll() public IActionResult GetAll()
{ {

@ -1,6 +1,8 @@
using AuthStudy.Authentication.Browser; using AuthStudy.Authentication.Browser;
using Microsoft.AspNetCore.Components.Forms;
namespace AuthStudy.WebApp namespace AuthStudy.WebApp
{ {
public class Program public class Program
@ -17,6 +19,7 @@ namespace AuthStudy.WebApp
builder.Services.AddSwaggerGen(); builder.Services.AddSwaggerGen();
#region 认证注册 #region 认证注册
//接口实现注册
builder.Services.AddBrowserAuthentication builder.Services.AddBrowserAuthentication
( (
BrowserAuthenticationDefault.SchemeName, BrowserAuthenticationDefault.SchemeName,
@ -26,6 +29,14 @@ namespace AuthStudy.WebApp
AllowBrowsers = new List<string>() { "Edge" } AllowBrowsers = new List<string>() { "Edge" }
} }
); );
builder.Services
.AddAuthentication("BrowserAuthenticationHandlerByBase")
.AddScheme<BrowserAuthenticationOptions, BrowserAuthenticationHandler>("BrowserAuthenticationHandlerByBase", t =>
{
t.AllowBrowsers = new List<string>() { "Edge", "Chrome", "Firefox" };
});
//默认基类实现注册
#endregion #endregion
WebApplication app = builder.Build(); WebApplication app = builder.Build();

Loading…
Cancel
Save