feat: 浏览器认证-接口方式

main
bicijinlian 2 years ago
parent d18fc2711f
commit aa092de9ac

@ -12,8 +12,8 @@ namespace AuthStudy.Authentication.Browser
public const string DispayName = "浏览器方案";
public static List<string> AllowBrowsers = new List<string>() { "Chrome", "Edge", "Firefox" };
public static List<string> AllowBrowsers { get; set; } = new() { "Chrome", "Edge", "Firefox" };
public static BrowserAuthenticationOptions DefaultOptions = new BrowserAuthenticationOptions();
public static BrowserAuthenticationOptions DefaultOptions = new();
}
}

@ -1,45 +1,62 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.DependencyInjection;
namespace AuthStudy.Authentication.Browser
{
public static class BrowserAuthenticationExtensions
{
public static AuthenticationBuilder AddBrowser(this AuthenticationBuilder builder)
public static IServiceCollection AddBrowserAuthentication
(
this IServiceCollection builder,
string AuthenticationSchemeName,
string AuthenticationDispalyName,
BrowserAuthenticationOptions Option
)
{
return builder.AddBrowser(BrowserAuthenticationDefault.SchemeName);
}
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
builder.AddService(Option.DefaultAuthenticateScheme, Option);
public static AuthenticationBuilder AddBrowser(this AuthenticationBuilder builder, string authenticationScheme)
{
return builder.AddBrowser(authenticationScheme, configureOptions: null);
}
builder.AddAuthentication(options =>
{
options.DefaultScheme = AuthenticationSchemeName;
options.DefaultAuthenticateScheme = AuthenticationSchemeName;
options.DefaultChallengeScheme = AuthenticationSchemeName;
options.DefaultForbidScheme = AuthenticationSchemeName;
options.DefaultSignInScheme = AuthenticationSchemeName;
options.DefaultSignOutScheme = AuthenticationSchemeName;
options.AddScheme<BrowserAuthenticationHandler>(AuthenticationSchemeName, AuthenticationDispalyName);
});
public static AuthenticationBuilder AddBrowser(this AuthenticationBuilder builder, Action<BrowserAuthenticationOptions> configureOptions)
{
return builder.AddBrowser(BrowserAuthenticationDefault.SchemeName, configureOptions);
return builder;
}
public static AuthenticationBuilder AddBrowser
(
this AuthenticationBuilder builder,
string authenticationScheme,
Action<BrowserAuthenticationOptions> configureOptions
)
private static IServiceCollection AddService(this IServiceCollection builder, string defaultSchemeName, BrowserAuthenticationOptions option)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
defaultSchemeName = defaultSchemeName ?? BrowserAuthenticationDefault.SchemeName;
BrowserAuthenticationOptions defaultOption = option ?? new();
defaultOption.DefaultScheme ??= defaultSchemeName;
defaultOption.DefaultAuthenticateScheme ??= defaultSchemeName;
defaultOption.DefaultChallengeScheme ??= defaultSchemeName;
defaultOption.DefaultForbidScheme ??= defaultSchemeName;
builder.AddSingleton(defaultOption);
builder.AddSingleton<BrowserAuthenticationHandler>();
return builder;
//return builder.AddScheme<BrowserAuthenticationOptions, BrowserAuthenticationHandler<BrowserAuthenticationOptions>>(authenticationScheme, "", configureOptions);
}
}
}

@ -20,24 +20,22 @@ namespace AuthStudy.Authentication.Browser
/// 浏览器认证 处理器
/// 可实现子接口 IAuthenticationRequestHandler, 以控制后续中间件是否执行.
/// </summary>
public class BrowserAuthenticationHandler<TOptions> :
IAuthenticationHandler, IAuthenticationRequestHandler,
public class BrowserAuthenticationHandler :
IAuthenticationHandler,
IAuthenticationRequestHandler,
IAuthenticationSignInHandler,
IAuthenticationSignOutHandler
where TOptions : AuthenticationSchemeOptions, new()
{
public string DefaultSchemeName = BrowserAuthenticationDefault.SchemeName;
public HttpContext? CurrentHttpContext;
public List<string> AllowBrowsers = BrowserAuthenticationDefault.AllowBrowsers;
public BrowserAuthenticationOptions Options;
public BrowserAuthenticationOptions Options = BrowserAuthenticationDefault.DefaultOptions;
public BrowserAuthenticationHandler()
public BrowserAuthenticationHandler(BrowserAuthenticationOptions option)
{
Options = option;
}
/// <summary>
@ -46,7 +44,7 @@ namespace AuthStudy.Authentication.Browser
public Task<AuthenticateResult> AuthenticateAsync()
{
//认证结果
AuthenticateResult result = AuthenticateResult.NoResult();
AuthenticateResult result;
//属性
var properties = new AuthenticationProperties();
@ -80,7 +78,7 @@ namespace AuthStudy.Authentication.Browser
}
//浏览器类型认证
if (!AllowBrowsers.Contains(clientInfo.UA.Family))
if (!Options.AllowBrowsers.Contains(clientInfo.UA.Family))
{
properties.UpdateTokenValue("AuthenticationBrowser", "失败:不支持的浏览器");
result = AuthenticateResult.Fail($"不支持的浏览器:{clientInfo.UA.Family}", properties);
@ -90,14 +88,16 @@ namespace AuthStudy.Authentication.Browser
//声明(身份项)
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 os = new Claim("OS", clientInfo.OS.ToString()); //操作系统
var device = new Claim("Device", clientInfo.Device.ToString()); //设备 //设备
//声明集合
var Claims = new List<Claim>();
Claims.Add(browser);
Claims.Add(os);
Claims.Add(device);
var Claims = new List<Claim>
{
browser,
os,
device
};
//身份:包含声明集合,是声明集合的包装类,一个身份对应多个声明
var claimsIdentity = new ClaimsIdentity(Claims, DefaultSchemeName);
@ -120,7 +120,7 @@ namespace AuthStudy.Authentication.Browser
/// <summary>
/// 无认证:服务端向客户端(浏览器)发质询(要求提供一个新票据),质询体现为 htpp请求的响应。
/// </summary>
public Task ChallengeAsync(AuthenticationProperties? properties)
public async Task ChallengeAsync(AuthenticationProperties? properties)
{
properties?.Parameters.Add("x-itme", "无效的认证");
@ -128,24 +128,24 @@ namespace AuthStudy.Authentication.Browser
if (CurrentHttpContext?.Response.Body.CanWrite ?? false)
{
var msg = UTF8Encoding.UTF8.GetBytes("认证无效");
CurrentHttpContext!.Response.Body.WriteAsync(msg);
await CurrentHttpContext!.Response.Body.WriteAsync(msg);
}
CurrentHttpContext?.Items.Add("认证结束时间", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
return Task.CompletedTask;
//return Task.CompletedTask;
}
/// <summary>
/// 无权限:服务端向客户端(浏览器)发质询(要求提供一个新票据),质询体现为 htpp请求的响应。
/// </summary>
public Task ForbidAsync(AuthenticationProperties? properties)
public async Task ForbidAsync(AuthenticationProperties? properties)
{
CurrentHttpContext!.Response.StatusCode = 403;
if (CurrentHttpContext?.Response.Body.CanWrite ?? false)
{
var msg = UTF8Encoding.UTF8.GetBytes("无权访问");
CurrentHttpContext!.Response.Body.WriteAsync(msg);
await CurrentHttpContext!.Response.Body.WriteAsync(msg);
}
return Task.CompletedTask;
//return Task.CompletedTask;
}
/// <summary>
@ -193,7 +193,7 @@ namespace AuthStudy.Authentication.Browser
return Task.CompletedTask;
}
private bool IsMobile(string deviceInfo)
private static bool IsMobile(string deviceInfo)
{
bool isMobile = false;
@ -202,7 +202,7 @@ namespace AuthStudy.Authentication.Browser
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);
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;

@ -0,0 +1,217 @@
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
{
/// <summary>
/// 浏览器认证 处理器
/// 可实现子接口 IAuthenticationRequestHandler, 以控制后续中间件是否执行.
/// </summary>
public class BrowserAuthenticationHandler2<TOptions> :
IAuthenticationHandler,
IAuthenticationRequestHandler,
IAuthenticationSignInHandler,
IAuthenticationSignOutHandler
where TOptions : AuthenticationSchemeOptions, new()
{
public string DefaultSchemeName = BrowserAuthenticationDefault.SchemeName;
public HttpContext? CurrentHttpContext;
public List<string> AllowBrowsers = BrowserAuthenticationDefault.AllowBrowsers;
public BrowserAuthenticationOptions Options = BrowserAuthenticationDefault.DefaultOptions;
public BrowserAuthenticationHandler2()
{
}
/// <summary>
/// 认证
/// </summary>
public Task<AuthenticateResult> AuthenticateAsync()
{
//认证结果
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 && BrowserAuthenticationHandler2<TOptions>.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<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>
public async Task ChallengeAsync(AuthenticationProperties? properties)
{
properties?.Parameters.Add("x-itme", "无效的认证");
CurrentHttpContext!.Response.StatusCode = 401;
if (CurrentHttpContext?.Response.Body.CanWrite ?? false)
{
var msg = UTF8Encoding.UTF8.GetBytes("认证无效");
await CurrentHttpContext!.Response.Body.WriteAsync(msg);
}
CurrentHttpContext?.Items.Add("认证结束时间", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
//return Task.CompletedTask;
}
/// <summary>
/// 无权限:服务端向客户端(浏览器)发质询(要求提供一个新票据),质询体现为 htpp请求的响应。
/// </summary>
public async Task ForbidAsync(AuthenticationProperties? properties)
{
CurrentHttpContext!.Response.StatusCode = 403;
if (CurrentHttpContext?.Response.Body.CanWrite ?? false)
{
var msg = UTF8Encoding.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, Microsoft.AspNetCore.Http.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;
}
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;
}
}
}

@ -8,7 +8,7 @@ using Microsoft.AspNetCore.Authentication;
namespace AuthStudy.Authentication.Browser
{
public class BrowserAuthenticationOptions : AuthenticationSchemeOptions
public class BrowserAuthenticationOptions : AuthenticationOptions
{
/// <summary>
/// 允许的浏览器

@ -1,4 +1,4 @@
using AuthStudy.WebApp.VModels;
using AuthStudy.WebApp.VModels;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
@ -10,7 +10,10 @@ namespace AuthStudy.WebApp.Controllers
[ApiController]
public class AccountsController : ControllerBase
{
public AccountsController() { }
public AccountsController()
{
}
[Authorize]
[HttpGet]
@ -22,7 +25,7 @@ namespace AuthStudy.WebApp.Controllers
Console.WriteLine($"{claim.Type}={claim.Value}");
}
List<AccountVM> accounts = new List<AccountVM>()
List<AccountVM> accounts = new()
{
new AccountVM(){ Name="张三", Email="zhangsan@qq.com", Password="123456"},
new AccountVM(){ Name="小明", Email="xiaoming@qq.com", Password="123456"},
@ -33,9 +36,9 @@ namespace AuthStudy.WebApp.Controllers
}
[HttpPost]
public IActionResult Login(string loginName, string loginPassword)
public IActionResult Login(string LoginName, string LoginPassword)
{
var info = new { Name = "", Roles = "Admin" };
var info = new { Name = LoginName, Roles = "Admin" };
return new JsonResult(info);
}

@ -7,7 +7,7 @@ namespace AuthStudy.WebApp
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
WebApplicationBuilder? builder = WebApplication.CreateBuilder(args);
// Add services to the container.
@ -17,25 +17,30 @@ namespace AuthStudy.WebApp
builder.Services.AddSwaggerGen();
#region 认证注册
builder.Services.AddAuthentication(configOption =>
{
configOption.AddScheme<BrowserAuthenticationHandler<BrowserAuthenticationOptions>>(BrowserAuthenticationDefault.SchemeName, BrowserAuthenticationDefault.DispayName);
});
//builder.Services.AddAuthentication(configOption =>
//{
// configOption.AddScheme<BrowserAuthenticationHandler2<BrowserAuthenticationOptions>>(BrowserAuthenticationDefault.SchemeName, BrowserAuthenticationDefault.DispayName);
//});
builder.Services.AddBrowserAuthentication
(
BrowserAuthenticationDefault.SchemeName,
BrowserAuthenticationDefault.DispayName,
new BrowserAuthenticationOptions()
{
AllowBrowsers = new List<string>() { "Edge" }
}
);
#endregion
var app = builder.Build();
WebApplication? app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseSwagger();
app.UseSwaggerUI();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();

@ -2,10 +2,10 @@
{
public class AccountVM
{
public string Name { get; set; }
public string? Name { get; set; }
public string Email { get; set; }
public string? Email { get; set; }
public string Password { get; set; }
public string? Password { get; set; }
}
}

Loading…
Cancel
Save