.netcore入门10:分析aspnetcore自带的cookie认证原理

2024-03-07 14:10

本文主要是介绍.netcore入门10:分析aspnetcore自带的cookie认证原理,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

环境

  • netcore 3.1.1.0

一. 认证相关名词解释

1.1 Scheme、Claim、ClaimsIdentity、ClaimsPrincipal介绍

  • Scheme(独立于另外三个):身份认证方案(应用中可以有多个方案,比如:AddCookie(options=>{})表示cookie方案,AddJwtBear(options=>{})表示token方案)
  • Claim:一条关键信息,比如:身份证上的身份证号、身份证号上的姓名、驾照上的身份证号
  • ClaimsIdentity:比如你的身份证或驾照
  • ClaimsPrincipal:你自己(HttpContext.User)

总结:一个ClaimsPrincipal可以拥有多个ClaimsIdentity,每个ClaimsIdentity又可以有多个Claim
参照:https://www.cnblogs.com/dudu/p/6367303.html
https://andrewlock.net/introduction-to-authentication-with-asp-net-core/

1.2 关于Authentication和Authorization

  • Authentication:身份认证,能证明"你是谁"
  • Authorization:授权,能决定你“你有没有权限”

授权依赖于身份认证,尽管它们之间有联系,但是在aspnetcore中这是两块内容。

二. aspnetcore自带的cookie认证的使用方法(asp.net core api 3.1)

  • 第一步: 注册Authentication服务、Cookie认证方案startup.cs
public void ConfigureServices(IServiceCollection services)
{services.AddControllers();services.AddAuthentication(options =>{options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;}).AddCookie(options =>{//将未登陆的请求重定向到静态页面options.LoginPath = "/login.html";});
}
  • 第二步:注册中间件startup.cs
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{if (env.IsDevelopment()){app.UseDeveloperExceptionPage();}//这里开启静态资源服务器用来承载login.html页面app.UseStaticFiles();//开启认证中间件app.UseAuthentication();app.UseRouting();//开启授权中间件app.UseAuthorization();app.UseEndpoints(endpoints =>{endpoints.MapControllers();});
}
  • 第三步:新建控制器,并打上Authorize标记
namespace WebApplication1.Controllers
{[Route("api/[controller]/[action]")][ApiController]public class DemoController : ControllerBase{[HttpGet][Authorize]public string Index(){return $"DemoController.Index() {DateTime.Now}";}}
}
  • 第四步:编写 /Account/Login
namespace WebApplication1.Controllers
{[ApiController][Route("api/[controller]/[action]")]public class AccountController : ControllerBase{[HttpPost]public string Login([FromForm] string username, [FromForm] string pwd){if (username == "admin" && pwd == "1"){var identity = new ClaimsIdentity(new Claim[] { new Claim("name", "admin") }, CookieAuthenticationDefaults.AuthenticationScheme);var user = new ClaimsPrincipal(identity);HttpContext.SignInAsync(user);return "登录成功!";}return "登录失败";}}
}
  • 第五步:编写登录页面 wwwroot/login.html
    新建文件夹wwwroot,然后再新建文件wwwroot/login.html,如下:
<!DOCTYPE html>
<html>
<head><meta charset="utf-8" /><title>登录</title>
</head>
<body><form action="/api/account/login" method="post" enctype="application/x-www-form-urlencoded">登录名:<input type="text" name="username" /><br />密码:<input type="password" name="pwd" /><input type="submit" value="登录" /></form>
</body>
</html>
  • 第六步:实现效果
    先访问http://localhost:5000/api/demo/index,被拦截跳转到了login.html页面
    然后,输入admin1后提交登录,显示登录成功
    在这里插入图片描述

三. 关于Cookie的几个路径配置说明

  • LoginPath:这个应该指向的是一个页面和一个后台处理方法(默认:/Account/Login)
    a. 当验证失败的时候,会重定向到这个页面,比如:https://localhost:5001/Account/Login?ReturnUrl=%2F
    b. 发向这个地址的登录请求经验证成功后会自动跳转到其他的页面地址(ReturnUrl参数中指定的)
    c. LoginPath的使用场景
    • 使用场景1(aspx): /Account/Login.aspx (既是页面也是处理请求)
    • 使用场景2(mvc):/Account/Login(get),当有用户名和密码时处理登录完跳转ReturnUrl,否则显示登录页(Return View())
    • 使用场景3(mvc):/Account/Login(get)登录页面,/Account/Login(post)处理登录请求后跳转ReturnUrl
  • LogoutPath:指向一个地址(退出登录处理地址,默认:/Account/Logout),在这里处理退出登录的请求后自动跳转到ReturnUrl页面上

四、Cookie认证源码解析

参照:

  • https://www.cnblogs.com/jionsoft/p/12304479.html
  • https://www.cnblogs.com/liyouming/p/9916777.html
  • https://www.cnblogs.com/fuyouchen/p/9561285.html

4.1 流程概述:

首先在http的请求管道里注册身份认证和授权中间件(app.UseAuthentication();app.UseAuthorization();),中间件拦截之后就根据你注入的认证服务(services.AddAuthentication())和认证方案(AddCookie())进行身份认证,如果获取到了身份的话就包装成ClaimsPrincipal放在HttpContext.User上,没获取到的话也要继续向下走。接着授权中间件找到了Controller.Action观察是否有[Authorize]标记,如果有的话就检查是否认证了身份,没有认证身份的就按照LoginPath加上ReturnUrl重定向(比如:https://localhost:5001/Account/Login?ReturnUrl=%2F)

4.2 源码骨架分析:

这里将Authentication和Authorization作为框架对待,而Cookie认证方式作为自定义的扩展对待。代码在它们之间的执行流程和关键点如下所示:

在这里插入图片描述

4.3 源码分析:

  • app.UseAuthentication()说起
    这里向Http请求管道注册了中间件AuthenticationMiddleware,查看它的源代码:
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;namespace Microsoft.AspNetCore.Authentication
{public class AuthenticationMiddleware{private readonly RequestDelegate _next;public AuthenticationMiddleware(RequestDelegate next, IAuthenticationSchemeProvider schemes){if (next == null){throw new ArgumentNullException(nameof(next));}if (schemes == null){throw new ArgumentNullException(nameof(schemes));}_next = next;Schemes = schemes;}public IAuthenticationSchemeProvider Schemes { get; set; }public async Task Invoke(HttpContext context){context.Features.Set<IAuthenticationFeature>(new AuthenticationFeature{OriginalPath = context.Request.Path,OriginalPathBase = context.Request.PathBase});// Give any IAuthenticationRequestHandler schemes a chance to handle the requestvar handlers = context.RequestServices.GetRequiredService<IAuthenticationHandlerProvider>();foreach (var scheme in await Schemes.GetRequestHandlerSchemesAsync()){var handler = await handlers.GetHandlerAsync(context, scheme.Name) as IAuthenticationRequestHandler;if (handler != null && await handler.HandleRequestAsync()){return;}}var defaultAuthenticate = await Schemes.GetDefaultAuthenticateSchemeAsync();if (defaultAuthenticate != null){var result = await context.AuthenticateAsync(defaultAuthenticate.Name);if (result?.Principal != null){context.User = result.Principal;}}await _next(context);}}
}

重点查看Invoke里面的内容,那个IAuthenticationRequestHandler的处理逻辑在cookie中没有用到,这里跳过,直接看var defaultAuthenticate = await Schemes.GetDefaultAuthenticateSchemeAsync();,这里是获取默认的认证方案的,Schemes是我们在AddAuthentication里注册进去的,为什么这么说呢?看下面的代码:
在这里插入图片描述在这里插入图片描述
接下来就查看GetDefaultAuthenticateSchemeAsync方法是从哪获取到默认方案的,查看AuthenticationSchemeProvider的部分代码:

// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;namespace Microsoft.AspNetCore.Authentication
{public class AuthenticationSchemeProvider : IAuthenticationSchemeProvider{public AuthenticationSchemeProvider(IOptions<AuthenticationOptions> options): this(options, new Dictionary<string, AuthenticationScheme>(StringComparer.Ordinal)){}protected AuthenticationSchemeProvider(IOptions<AuthenticationOptions> options, IDictionary<string, AuthenticationScheme> schemes){_options = options.Value;_schemes = schemes ?? throw new ArgumentNullException(nameof(schemes));_requestHandlers = new List<AuthenticationScheme>();foreach (var builder in _options.Schemes){var scheme = builder.Build();AddScheme(scheme);}}private readonly AuthenticationOptions _options;private readonly object _lock = new object();private readonly IDictionary<string, AuthenticationScheme> _schemes;private readonly List<AuthenticationScheme> _requestHandlers;private IEnumerable<AuthenticationScheme> _schemesCopy = Array.Empty<AuthenticationScheme>();private IEnumerable<AuthenticationScheme> _requestHandlersCopy = Array.Empty<AuthenticationScheme>();private Task<AuthenticationScheme> GetDefaultSchemeAsync()=> _options.DefaultScheme != null? GetSchemeAsync(_options.DefaultScheme): Task.FromResult<AuthenticationScheme>(null);public virtual Task<AuthenticationScheme> GetDefaultAuthenticateSchemeAsync()=> _options.DefaultAuthenticateScheme != null? GetSchemeAsync(_options.DefaultAuthenticateScheme): GetDefaultSchemeAsync();......public virtual Task<AuthenticationScheme> GetSchemeAsync(string name)=> Task.FromResult(_schemes.ContainsKey(name) ? _schemes[name] : null);public virtual void AddScheme(AuthenticationScheme scheme){if (_schemes.ContainsKey(scheme.Name)){throw new InvalidOperationException("Scheme already exists: " + scheme.Name);}lock (_lock){if (_schemes.ContainsKey(scheme.Name)){throw new InvalidOperationException("Scheme already exists: " + scheme.Name);}if (typeof(IAuthenticationRequestHandler).IsAssignableFrom(scheme.HandlerType)){_requestHandlers.Add(scheme);_requestHandlersCopy = _requestHandlers.ToArray();}_schemes[scheme.Name] = scheme;_schemesCopy = _schemes.Values.ToArray();}}}
}

可以看到,最终是从_schemes集合里取到的scheme(关键字就是我们配置的默认方案名称)。那么这些Scheme是怎么添加进来的呢?这要看AddCookie()的代码了:
在这里插入图片描述
那么这个AuthenticationBuilder从哪来的呢,它就是services.AddAuthentication()返回的,我们直接看AuthenticationBuilder的代码:

// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;namespace Microsoft.AspNetCore.Authentication
{public class AuthenticationBuilder{public AuthenticationBuilder(IServiceCollection services)=> Services = services;public virtual IServiceCollection Services { get; }private AuthenticationBuilder AddSchemeHelper<TOptions, THandler>(string authenticationScheme, string displayName, Action<TOptions> configureOptions)where TOptions : AuthenticationSchemeOptions, new()where THandler : class, IAuthenticationHandler{Services.Configure<AuthenticationOptions>(o =>{o.AddScheme(authenticationScheme, scheme => {scheme.HandlerType = typeof(THandler);scheme.DisplayName = displayName;});});if (configureOptions != null){Services.Configure(authenticationScheme, configureOptions);}Services.AddOptions<TOptions>(authenticationScheme).Validate(o => {o.Validate(authenticationScheme);return true;});Services.AddTransient<THandler>();return this;}public virtual AuthenticationBuilder AddScheme<TOptions, THandler>(string authenticationScheme, string displayName, Action<TOptions> configureOptions)where TOptions : AuthenticationSchemeOptions, new()where THandler : AuthenticationHandler<TOptions>=> AddSchemeHelper<TOptions, THandler>(authenticationScheme, displayName, configureOptions);......}
}

从上面的源码中我们可以看到关键代码在AddSchemeHelper()方法中:

private AuthenticationBuilder AddSchemeHelper<TOptions, THandler>(string authenticationScheme, string displayName, Action<TOptions> configureOptions)where TOptions : AuthenticationSchemeOptions, new()where THandler : class, IAuthenticationHandler
{Services.Configure<AuthenticationOptions>(o =>{o.AddScheme(authenticationScheme, scheme => {scheme.HandlerType = typeof(THandler);scheme.DisplayName = displayName;});});...
}

这里的o就是AuthenticationOptions,我们再看它的代码:

using System;
using System.Collections.Generic;
using System.Security.Claims;
using Microsoft.AspNetCore.Http;namespace Microsoft.AspNetCore.Authentication
{public class AuthenticationOptions{private readonly IList<AuthenticationSchemeBuilder> _schemes = new List<AuthenticationSchemeBuilder>();/// <summary>/// Returns the schemes in the order they were added (important for request handling priority)/// </summary>public IEnumerable<AuthenticationSchemeBuilder> Schemes => _schemes;/// <summary>/// Maps schemes by name./// </summary>public IDictionary<string, AuthenticationSchemeBuilder> SchemeMap { get; } = new Dictionary<string, AuthenticationSchemeBuilder>(StringComparer.Ordinal);public void AddScheme(string name, Action<AuthenticationSchemeBuilder> configureBuilder){if (name == null){throw new ArgumentNullException(nameof(name));}if (configureBuilder == null){throw new ArgumentNullException(nameof(configureBuilder));}if (SchemeMap.ContainsKey(name)){throw new InvalidOperationException("Scheme already exists: " + name);}var builder = new AuthenticationSchemeBuilder(name);configureBuilder(builder);_schemes.Add(builder);SchemeMap[name] = builder;}......}
}

可以看到最终把新创建的AuthenticationSchemeBuilder放进了SchemeMap(IDictionary<string, AuthenticationSchemeBuilder>)中。
这里有个就要问了“为什么不是创建AuthenticationScheme?AuthenticationSchemeProvider里又是怎么获取到的呢?”
带着问题让我们看下AuthenticationSchemeProvider里的构造函数都注入了什么,注入后又是怎么处理的:
在这里插入图片描述
现在就知道AddCookie()添加的Scheme怎么被AuthenticationSchemeProvider找到的了吧,那么AuthenticationMiddleware中间件也就找到了Cookie的AuthenticationScheme了。
接下来再看AuthenticationMiddleware的代码:
在这里插入图片描述
接下来就是将获取到的Cookie的Scheme的Name传给HttpContext.AuthenticateAsync()方法了:

namespace Microsoft.AspNetCore.Authentication
{public static class AuthenticationHttpContextExtensions{public static Task<AuthenticateResult> AuthenticateAsync(this HttpContext context) =>context.AuthenticateAsync(scheme: null);public static Task<AuthenticateResult> AuthenticateAsync(this HttpContext context, string scheme) =>context.RequestServices.GetRequiredService<IAuthenticationService>().AuthenticateAsync(context, scheme);......}
}

这里其实就是从容器里找到IAuthenticationService然后调用它的方法,这个服务在AddAuthentication()的时候就已经注册到容器中了(可以看上面的截图),这里直接看AuthenticationService.AuthenticateAsync()

namespace Microsoft.AspNetCore.Authentication
{public class AuthenticationService : IAuthenticationService{public AuthenticationService(IAuthenticationSchemeProvider schemes, IAuthenticationHandlerProvider handlers, IClaimsTransformation transform, IOptions<AuthenticationOptions> options){Schemes = schemes;Handlers = handlers;Transform = transform;Options = options.Value;}public IAuthenticationSchemeProvider Schemes { get; }public IAuthenticationHandlerProvider Handlers { get; }public IClaimsTransformation Transform { get; }public AuthenticationOptions Options { get; }public virtual async Task<AuthenticateResult> AuthenticateAsync(HttpContext context, string scheme){if (scheme == null){var defaultScheme = await Schemes.GetDefaultAuthenticateSchemeAsync();scheme = defaultScheme?.Name;if (scheme == null){throw new InvalidOperationException($"No authenticationScheme was specified, and there was no DefaultAuthenticateScheme found. The default schemes can be set using either AddAuthentication(string defaultScheme) or AddAuthentication(Action<AuthenticationOptions> configureOptions).");}}var handler = await Handlers.GetHandlerAsync(context, scheme);if (handler == null){throw await CreateMissingHandlerException(scheme);}var result = await handler.AuthenticateAsync();if (result != null && result.Succeeded){var transformed = await Transform.TransformAsync(result.Principal);return AuthenticateResult.Success(new AuthenticationTicket(transformed, result.Properties, result.Ticket.AuthenticationScheme));}return result;}......}
}

那么此时就去寻找Handlers,也就是容器中的IAuthenticationHandlerProvider,这个服务我们在AddAuthentication()里也已经注册过了,直接看它的代码:

namespace Microsoft.AspNetCore.Authentication
{public class AuthenticationHandlerProvider : IAuthenticationHandlerProvider{public AuthenticationHandlerProvider(IAuthenticationSchemeProvider schemes){Schemes = schemes;}public IAuthenticationSchemeProvider Schemes { get; }private Dictionary<string, IAuthenticationHandler> _handlerMap = new Dictionary<string, IAuthenticationHandler>(StringComparer.Ordinal);public async Task<IAuthenticationHandler> GetHandlerAsync(HttpContext context, string authenticationScheme){if (_handlerMap.ContainsKey(authenticationScheme)){return _handlerMap[authenticationScheme];}var scheme = await Schemes.GetSchemeAsync(authenticationScheme);if (scheme == null){return null;}var handler = (context.RequestServices.GetService(scheme.HandlerType) ??ActivatorUtilities.CreateInstance(context.RequestServices, scheme.HandlerType))as IAuthenticationHandler;if (handler != null){await handler.InitializeAsync(scheme, context);_handlerMap[authenticationScheme] = handler;}return handler;}}
}

其实这里Schemes.GetSchemeAsync(authenticationScheme)这个过程上面已经分析过了,在AddCookie的时候注册了Scheme和CookieAuthenticationHandler,所以紧接着下面获取到的handler就是CookieAuthenticationHandler,让我们往下进行,也就是分析var result = await handler.AuthenticateAsync();(AuthenticationService.cs里的)。
现在我们应该查看CookieAuthenticationHandler类的AuthenticateAsync方法,这个方法是在父类AuthenticationHandler定义的,那么一路看过去:

namespace Microsoft.AspNetCore.Authentication
{public abstract class AuthenticationHandler<TOptions> : IAuthenticationHandler where TOptions : AuthenticationSchemeOptions, new(){......public async Task<AuthenticateResult> AuthenticateAsync(){var target = ResolveTarget(Options.ForwardAuthenticate);if (target != null){return await Context.AuthenticateAsync(target);}// Calling Authenticate more than once should always return the original value.var result = await HandleAuthenticateOnceAsync();if (result?.Failure == null){var ticket = result?.Ticket;if (ticket?.Principal != null){Logger.AuthenticationSchemeAuthenticated(Scheme.Name);}else{Logger.AuthenticationSchemeNotAuthenticated(Scheme.Name);}}else{Logger.AuthenticationSchemeNotAuthenticatedWithFailure(Scheme.Name, result.Failure.Message);}return result;}protected Task<AuthenticateResult> HandleAuthenticateOnceAsync(){if (_authenticateTask == null){_authenticateTask = HandleAuthenticateAsync();}return _authenticateTask;}protected abstract Task<AuthenticateResult> HandleAuthenticateAsync();......}
}

这个时候我们要再回到CookieAuthenticationHandler查看HandleAuthenticateAsync方法:

namespace Microsoft.AspNetCore.Authentication.Cookies
{public class CookieAuthenticationHandler : SignInAuthenticationHandler<CookieAuthenticationOptions>{......protected override async Task<AuthenticateResult> HandleAuthenticateAsync(){var result = await EnsureCookieTicket();if (!result.Succeeded){return result;}var context = new CookieValidatePrincipalContext(Context, Scheme, Options, result.Ticket);await Events.ValidatePrincipal(context);if (context.Principal == null){return AuthenticateResult.Fail("No principal.");}if (context.ShouldRenew){RequestRefresh(result.Ticket, context.Principal);}return AuthenticateResult.Success(new AuthenticationTicket(context.Principal, context.Properties, Scheme.Name));}private Task<AuthenticateResult> EnsureCookieTicket(){// We only need to read the ticket onceif (_readCookieTask == null){_readCookieTask = ReadCookieTicket();}return _readCookieTask;}private async Task<AuthenticateResult> ReadCookieTicket(){var cookie = Options.CookieManager.GetRequestCookie(Context, Options.Cookie.Name);if (string.IsNullOrEmpty(cookie)){return AuthenticateResult.NoResult();}var ticket = Options.TicketDataFormat.Unprotect(cookie, GetTlsTokenBinding());if (ticket == null){return AuthenticateResult.Fail("Unprotect ticket failed");}if (Options.SessionStore != null){var claim = ticket.Principal.Claims.FirstOrDefault(c => c.Type.Equals(SessionIdClaim));if (claim == null){return AuthenticateResult.Fail("SessionId missing");}_sessionKey = claim.Value;ticket = await Options.SessionStore.RetrieveAsync(_sessionKey);if (ticket == null){return AuthenticateResult.Fail("Identity missing in session store");}}var currentUtc = Clock.UtcNow;var expiresUtc = ticket.Properties.ExpiresUtc;if (expiresUtc != null && expiresUtc.Value < currentUtc){if (Options.SessionStore != null){await Options.SessionStore.RemoveAsync(_sessionKey);}return AuthenticateResult.Fail("Ticket expired");}CheckForRefresh(ticket);// Finally we have a valid ticketreturn AuthenticateResult.Success(ticket);}......}
}

可以看到最终是从Request中读取到了Cookie并且去除了数据保护,得到ticket。
现在我们可以回到AuthenticationMiddleware中间件里了:
在这里插入图片描述
可以看到,验证完了之后把得到的ticket的Principal赋值给HttpContext.User,并且继续往下执行(无论验证通过与否)。

五. 授权服务

现在身份认证通过了,该授权了,当授权中间件app.UseAuthorization();发现未认证身份的请求访问[Authorize]的时候就会调用HttpContext.ChallengeAsync(),就像HttpContext.AuthenticateAsync()一样最终会执行CookieAuthenticationHandler.HandleChallengeAsync()
在这里插入图片描述
可以看到最终使用了配置的LoginPath进行了重定向。
这里的Events就是CookieAuthenticationEvents,看它的处理代码:

namespace Microsoft.AspNetCore.Authentication.Cookies
{public class CookieAuthenticationEvents{......public Func<RedirectContext<CookieAuthenticationOptions>, Task> OnRedirectToLogin { get; set; } = context =>{if (IsAjaxRequest(context.Request)){context.Response.Headers[HeaderNames.Location] = context.RedirectUri;context.Response.StatusCode = 401;}else{context.Response.Redirect(context.RedirectUri);}return Task.CompletedTask;};private static bool IsAjaxRequest(HttpRequest request){return string.Equals(request.Query["X-Requested-With"], "XMLHttpRequest", StringComparison.Ordinal) ||string.Equals(request.Headers["X-Requested-With"], "XMLHttpRequest", StringComparison.Ordinal);}public virtual Task RedirectToLogin(RedirectContext<CookieAuthenticationOptions> context) => OnRedirectToLogin(context);......}
}

可以看到这里判断是否是ajax,然后决定返回401还是重定向,其实我们在注册cookie方案的时候可以进行配置:

services.AddAuthentication().AddCookie(opt =>
{opt.LoginPath = "account/mylogin";opt.Events.OnRedirectToLogin = context =>{context.Response.Redirect(context.RedirectUri);return Task.CompletedTask;};
});

六、用户与Authentication的交互调用情况

从上面的分析中我们得知,身份认证主要有如下几个接口组成

  • AuthenticateAsync():当需要对request进行身份识别时调用(由UseAuthentication()中间件发起)
  • ChallengeAsync():当授权失败时调用(由UseAuthorization()中间件配合[Authorize]发起)
  • ForbidAsync():当禁止访问时调用(用户代码或其他发起)
  • SignInAsync():当需要将登陆凭证注册到应用中的时候调用(用户代码发起)
  • SignOutAsync():当需要将登陆凭证从应用中移除的时候调用(用户代码发起)

上面这些接口的调用入口都集中放在了HttpContext上,作为统一的调用方式对外提供,比如:

HttpContext.AuthenticateAsync();
HttpContext.ChallengeAsync();
HttpContext.ForbidAsync();
HttpContext.AuthenticateAsync();
HttpContext.SignInAsync();

下面就看一下HttpContext是如何处理这些调用请求的AuthenticationHttpContextExtensions.cs

using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;namespace Microsoft.AspNetCore.Authentication
{public static class AuthenticationHttpContextExtensions{public static Task<AuthenticateResult> AuthenticateAsync(this HttpContext context) =>context.AuthenticateAsync(scheme: null);public static Task<AuthenticateResult> AuthenticateAsync(this HttpContext context, string scheme) =>context.RequestServices.GetRequiredService<IAuthenticationService>().AuthenticateAsync(context, scheme);public static Task ChallengeAsync(this HttpContext context, string scheme) =>context.ChallengeAsync(scheme, properties: null);public static Task ChallengeAsync(this HttpContext context) =>context.ChallengeAsync(scheme: null, properties: null);public static Task ChallengeAsync(this HttpContext context, AuthenticationProperties properties) =>context.ChallengeAsync(scheme: null, properties: properties);public static Task ChallengeAsync(this HttpContext context, string scheme, AuthenticationProperties properties) =>context.RequestServices.GetRequiredService<IAuthenticationService>().ChallengeAsync(context, scheme, properties);public static Task ForbidAsync(this HttpContext context, string scheme) =>context.ForbidAsync(scheme, properties: null);public static Task ForbidAsync(this HttpContext context) =>context.ForbidAsync(scheme: null, properties: null);public static Task ForbidAsync(this HttpContext context, AuthenticationProperties properties) =>context.ForbidAsync(scheme: null, properties: properties);public static Task ForbidAsync(this HttpContext context, string scheme, AuthenticationProperties properties) =>context.RequestServices.GetRequiredService<IAuthenticationService>().ForbidAsync(context, scheme, properties);public static Task SignInAsync(this HttpContext context, string scheme, ClaimsPrincipal principal) =>context.SignInAsync(scheme, principal, properties: null);public static Task SignInAsync(this HttpContext context, ClaimsPrincipal principal) =>context.SignInAsync(scheme: null, principal: principal, properties: null);public static Task SignInAsync(this HttpContext context, ClaimsPrincipal principal, AuthenticationProperties properties) =>context.SignInAsync(scheme: null, principal: principal, properties: properties);public static Task SignInAsync(this HttpContext context, string scheme, ClaimsPrincipal principal, AuthenticationProperties properties) =>context.RequestServices.GetRequiredService<IAuthenticationService>().SignInAsync(context, scheme, principal, properties);public static Task SignOutAsync(this HttpContext context) => context.SignOutAsync(scheme: null, properties: null);public static Task SignOutAsync(this HttpContext context, AuthenticationProperties properties) => context.SignOutAsync(scheme: null, properties: properties);public static Task SignOutAsync(this HttpContext context, string scheme) => context.SignOutAsync(scheme, properties: null);public static Task SignOutAsync(this HttpContext context, string scheme, AuthenticationProperties properties) =>context.RequestServices.GetRequiredService<IAuthenticationService>().SignOutAsync(context, scheme, properties);public static Task<string> GetTokenAsync(this HttpContext context, string scheme, string tokenName) =>context.RequestServices.GetRequiredService<IAuthenticationService>().GetTokenAsync(context, scheme, tokenName);public static Task<string> GetTokenAsync(this HttpContext context, string tokenName) =>            context.RequestServices.GetRequiredService<IAuthenticationService>().GetTokenAsync(context, tokenName);}
}

从HttpContext的处理情况来看,它们都是在调用IAuthenticationService的方法,那么再看一下AuthenticationService的处理情况:

using System;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;namespace Microsoft.AspNetCore.Authentication
{public class AuthenticationService : IAuthenticationService{public AuthenticationService(IAuthenticationSchemeProvider schemes, IAuthenticationHandlerProvider handlers, IClaimsTransformation transform, IOptions<AuthenticationOptions> options){Schemes = schemes;Handlers = handlers;Transform = transform;Options = options.Value;}public IAuthenticationSchemeProvider Schemes { get; }public IAuthenticationHandlerProvider Handlers { get; }public IClaimsTransformation Transform { get; }public AuthenticationOptions Options { get; }public virtual async Task<AuthenticateResult> AuthenticateAsync(HttpContext context, string scheme){if (scheme == null){var defaultScheme = await Schemes.GetDefaultAuthenticateSchemeAsync();scheme = defaultScheme?.Name;if (scheme == null){throw new InvalidOperationException($"No authenticationScheme was specified, and there was no DefaultAuthenticateScheme found. The default schemes can be set using either AddAuthentication(string defaultScheme) or AddAuthentication(Action<AuthenticationOptions> configureOptions).");}}var handler = await Handlers.GetHandlerAsync(context, scheme);if (handler == null){throw await CreateMissingHandlerException(scheme);}var result = await handler.AuthenticateAsync();if (result != null && result.Succeeded){var transformed = await Transform.TransformAsync(result.Principal);return AuthenticateResult.Success(new AuthenticationTicket(transformed, result.Properties, result.Ticket.AuthenticationScheme));}return result;}public virtual async Task ChallengeAsync(HttpContext context, string scheme, AuthenticationProperties properties){if (scheme == null){var defaultChallengeScheme = await Schemes.GetDefaultChallengeSchemeAsync();scheme = defaultChallengeScheme?.Name;if (scheme == null){throw new InvalidOperationException($"No authenticationScheme was specified, and there was no DefaultChallengeScheme found. The default schemes can be set using either AddAuthentication(string defaultScheme) or AddAuthentication(Action<AuthenticationOptions> configureOptions).");}}var handler = await Handlers.GetHandlerAsync(context, scheme);if (handler == null){throw await CreateMissingHandlerException(scheme);}await handler.ChallengeAsync(properties);}public virtual async Task ForbidAsync(HttpContext context, string scheme, AuthenticationProperties properties){if (scheme == null){var defaultForbidScheme = await Schemes.GetDefaultForbidSchemeAsync();scheme = defaultForbidScheme?.Name;if (scheme == null){throw new InvalidOperationException($"No authenticationScheme was specified, and there was no DefaultForbidScheme found. The default schemes can be set using either AddAuthentication(string defaultScheme) or AddAuthentication(Action<AuthenticationOptions> configureOptions).");}}var handler = await Handlers.GetHandlerAsync(context, scheme);if (handler == null){throw await CreateMissingHandlerException(scheme);}await handler.ForbidAsync(properties);}public virtual async Task SignInAsync(HttpContext context, string scheme, ClaimsPrincipal principal, AuthenticationProperties properties){if (principal == null){throw new ArgumentNullException(nameof(principal));}if (Options.RequireAuthenticatedSignIn){if (principal.Identity == null){throw new InvalidOperationException("SignInAsync when principal.Identity == null is not allowed when AuthenticationOptions.RequireAuthenticatedSignIn is true.");}if (!principal.Identity.IsAuthenticated){throw new InvalidOperationException("SignInAsync when principal.Identity.IsAuthenticated is false is not allowed when AuthenticationOptions.RequireAuthenticatedSignIn is true.");}}if (scheme == null){var defaultScheme = await Schemes.GetDefaultSignInSchemeAsync();scheme = defaultScheme?.Name;if (scheme == null){throw new InvalidOperationException($"No authenticationScheme was specified, and there was no DefaultSignInScheme found. The default schemes can be set using either AddAuthentication(string defaultScheme) or AddAuthentication(Action<AuthenticationOptions> configureOptions).");}}var handler = await Handlers.GetHandlerAsync(context, scheme);if (handler == null){throw await CreateMissingSignInHandlerException(scheme);}var signInHandler = handler as IAuthenticationSignInHandler;if (signInHandler == null){throw await CreateMismatchedSignInHandlerException(scheme, handler);}await signInHandler.SignInAsync(principal, properties);}public virtual async Task SignOutAsync(HttpContext context, string scheme, AuthenticationProperties properties){if (scheme == null){var defaultScheme = await Schemes.GetDefaultSignOutSchemeAsync();scheme = defaultScheme?.Name;if (scheme == null){throw new InvalidOperationException($"No authenticationScheme was specified, and there was no DefaultSignOutScheme found. The default schemes can be set using either AddAuthentication(string defaultScheme) or AddAuthentication(Action<AuthenticationOptions> configureOptions).");}}var handler = await Handlers.GetHandlerAsync(context, scheme);if (handler == null){throw await CreateMissingSignOutHandlerException(scheme);}var signOutHandler = handler as IAuthenticationSignOutHandler;if (signOutHandler == null){throw await CreateMismatchedSignOutHandlerException(scheme, handler);}await signOutHandler.SignOutAsync(properties);}private async Task<Exception> CreateMissingHandlerException(string scheme){var schemes = string.Join(", ", (await Schemes.GetAllSchemesAsync()).Select(sch => sch.Name));var footer = $" Did you forget to call AddAuthentication().Add[SomeAuthHandler](\"{scheme}\",...)?";if (string.IsNullOrEmpty(schemes)){return new InvalidOperationException($"No authentication handlers are registered." + footer);}return new InvalidOperationException($"No authentication handler is registered for the scheme '{scheme}'. The registered schemes are: {schemes}." + footer);}private async Task<string> GetAllSignInSchemeNames(){return string.Join(", ", (await Schemes.GetAllSchemesAsync()).Where(sch => typeof(IAuthenticationSignInHandler).IsAssignableFrom(sch.HandlerType)).Select(sch => sch.Name));}private async Task<Exception> CreateMissingSignInHandlerException(string scheme){var schemes = await GetAllSignInSchemeNames();// CookieAuth is the only implementation of sign-in.var footer = $" Did you forget to call AddAuthentication().AddCookies(\"{scheme}\",...)?";if (string.IsNullOrEmpty(schemes)){return new InvalidOperationException($"No sign-in authentication handlers are registered." + footer);}return new InvalidOperationException($"No sign-in authentication handler is registered for the scheme '{scheme}'. The registered sign-in schemes are: {schemes}." + footer);}private async Task<Exception> CreateMismatchedSignInHandlerException(string scheme, IAuthenticationHandler handler){var schemes = await GetAllSignInSchemeNames();var mismatchError = $"The authentication handler registered for scheme '{scheme}' is '{handler.GetType().Name}' which cannot be used for SignInAsync. ";if (string.IsNullOrEmpty(schemes)){// CookieAuth is the only implementation of sign-in.return new InvalidOperationException(mismatchError+ $"Did you forget to call AddAuthentication().AddCookies(\"Cookies\") and SignInAsync(\"Cookies\",...)?");}return new InvalidOperationException(mismatchError + $"The registered sign-in schemes are: {schemes}.");}private async Task<string> GetAllSignOutSchemeNames(){return string.Join(", ", (await Schemes.GetAllSchemesAsync()).Where(sch => typeof(IAuthenticationSignOutHandler).IsAssignableFrom(sch.HandlerType)).Select(sch => sch.Name));}private async Task<Exception> CreateMissingSignOutHandlerException(string scheme){var schemes = await GetAllSignOutSchemeNames();var footer = $" Did you forget to call AddAuthentication().AddCookies(\"{scheme}\",...)?";if (string.IsNullOrEmpty(schemes)){// CookieAuth is the most common implementation of sign-out, but OpenIdConnect and WsFederation also support it.return new InvalidOperationException($"No sign-out authentication handlers are registered." + footer);}return new InvalidOperationException($"No sign-out authentication handler is registered for the scheme '{scheme}'. The registered sign-out schemes are: {schemes}." + footer);}private async Task<Exception> CreateMismatchedSignOutHandlerException(string scheme, IAuthenticationHandler handler){var schemes = await GetAllSignOutSchemeNames();var mismatchError = $"The authentication handler registered for scheme '{scheme}' is '{handler.GetType().Name}' which cannot be used for {nameof(SignOutAsync)}. ";if (string.IsNullOrEmpty(schemes)){// CookieAuth is the most common implementation of sign-out, but OpenIdConnect and WsFederation also support it.return new InvalidOperationException(mismatchError+ $"Did you forget to call AddAuthentication().AddCookies(\"Cookies\") and {nameof(SignOutAsync)}(\"Cookies\",...)?");}return new InvalidOperationException(mismatchError + $"The registered sign-out schemes are: {schemes}.");}}
}

观察到AuthenticationService就是先获取处理的方案(认证、登录、登出、禁止、询问都需要各自的方案),然后根据方案获取IAuthenticationHandler,然后调用它的同名方法,不过我们发现IAuthenticationHandler中只定义了认证、询问、禁止三个接口,而登录和登出并没有定义。经过探索发现登录和登出的方法分别在IAuthenticationSignInHandlerIAuthenticationSignOutHandler方法中定义了,它们的代码如下:

using System.Security.Claims;
using System.Threading.Tasks;namespace Microsoft.AspNetCore.Authentication
{public interface IAuthenticationSignInHandler : IAuthenticationSignOutHandler{Task SignInAsync(ClaimsPrincipal user, AuthenticationProperties properties);}public interface IAuthenticationSignOutHandler : IAuthenticationHandler{Task SignOutAsync(AuthenticationProperties properties);}public interface IAuthenticationHandler{Task InitializeAsync(AuthenticationScheme scheme, HttpContext context);Task<AuthenticateResult> AuthenticateAsync();Task ChallengeAsync(AuthenticationProperties properties);Task ForbidAsync(AuthenticationProperties properties);}
}

这些接口的定义都全了,那么它们实现的结构是怎么样的呢?
在这里插入图片描述
可以看到,CookieAuthenticationHandler最终全部实现了,所以在注册Cookie方案(也就是AddCookie())的时候只添加一个CookieAuthenticationHandler就可以了!

这篇关于.netcore入门10:分析aspnetcore自带的cookie认证原理的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/783769

相关文章

在C#中获取端口号与系统信息的高效实践

《在C#中获取端口号与系统信息的高效实践》在现代软件开发中,尤其是系统管理、运维、监控和性能优化等场景中,了解计算机硬件和网络的状态至关重要,C#作为一种广泛应用的编程语言,提供了丰富的API来帮助开... 目录引言1. 获取端口号信息1.1 获取活动的 TCP 和 UDP 连接说明:应用场景:2. 获取硬

C#使用HttpClient进行Post请求出现超时问题的解决及优化

《C#使用HttpClient进行Post请求出现超时问题的解决及优化》最近我的控制台程序发现有时候总是出现请求超时等问题,通常好几分钟最多只有3-4个请求,在使用apipost发现并发10个5分钟也... 目录优化结论单例HttpClient连接池耗尽和并发并发异步最终优化后优化结论我直接上优化结论吧,

C#使用yield关键字实现提升迭代性能与效率

《C#使用yield关键字实现提升迭代性能与效率》yield关键字在C#中简化了数据迭代的方式,实现了按需生成数据,自动维护迭代状态,本文主要来聊聊如何使用yield关键字实现提升迭代性能与效率,感兴... 目录前言传统迭代和yield迭代方式对比yield延迟加载按需获取数据yield break显式示迭

c# checked和unchecked关键字的使用

《c#checked和unchecked关键字的使用》C#中的checked关键字用于启用整数运算的溢出检查,可以捕获并抛出System.OverflowException异常,而unchecked... 目录在 C# 中,checked 关键字用于启用整数运算的溢出检查。默认情况下,C# 的整数运算不会自

C#实现获得某个枚举的所有名称

《C#实现获得某个枚举的所有名称》这篇文章主要为大家详细介绍了C#如何实现获得某个枚举的所有名称,文中的示例代码讲解详细,具有一定的借鉴价值,有需要的小伙伴可以参考一下... C#中获得某个枚举的所有名称using System;using System.Collections.Generic;usi

C# 读写ini文件操作实现

《C#读写ini文件操作实现》本文主要介绍了C#读写ini文件操作实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录一、INI文件结构二、读取INI文件中的数据在C#应用程序中,常将INI文件作为配置文件,用于存储应用程序的

C#实现获取电脑中的端口号和硬件信息

《C#实现获取电脑中的端口号和硬件信息》这篇文章主要为大家详细介绍了C#实现获取电脑中的端口号和硬件信息的相关方法,文中的示例代码讲解详细,有需要的小伙伴可以参考一下... 我们经常在使用一个串口软件的时候,发现软件中的端口号并不是普通的COM1,而是带有硬件信息的。那么如果我们使用C#编写软件时候,如

C#中图片如何自适应pictureBox大小

《C#中图片如何自适应pictureBox大小》文章描述了如何在C#中实现图片自适应pictureBox大小,并展示修改前后的效果,修改步骤包括两步,作者分享了个人经验,希望对大家有所帮助... 目录C#图片自适应pictureBox大小编程修改步骤总结C#图片自适应pictureBox大小上图中“z轴

使用C#代码计算数学表达式实例

《使用C#代码计算数学表达式实例》这段文字主要讲述了如何使用C#语言来计算数学表达式,该程序通过使用Dictionary保存变量,定义了运算符优先级,并实现了EvaluateExpression方法来... 目录C#代码计算数学表达式该方法很长,因此我将分段描述下面的代码片段显示了下一步以下代码显示该方法如

Redis主从/哨兵机制原理分析

《Redis主从/哨兵机制原理分析》本文介绍了Redis的主从复制和哨兵机制,主从复制实现了数据的热备份和负载均衡,而哨兵机制可以监控Redis集群,实现自动故障转移,哨兵机制通过监控、下线、选举和故... 目录一、主从复制1.1 什么是主从复制1.2 主从复制的作用1.3 主从复制原理1.3.1 全量复制