环境
1.1 Scheme、Claim、ClaimsIdentity、ClaimsPrincipal介绍
AddCookie(options=>{})
表示cookie方案,AddJwtBear(options=>{})
表示token方案)总结:一个
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
授权依赖于身份认证,尽管它们之间有联系,但是在aspnetcore中这是两块内容。
startup.cs
public void ConfigureServices(IServiceCollection services)
{
//mvc应用
services.AddControllersWithViews();
services.AddAuthentication(options =>
{
options.DefaultScheme= CookieAuthenticationDefaults.AuthenticationScheme;
}).AddCookie(options =>
{
//这里设置的默认的地址(也可以不写)
options.LoginPath = CookieAuthenticationDefaults.LoginPath;
options.LogoutPath = CookieAuthenticationDefaults.LogoutPath;
});
}
startup.cs
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
//开启身份认证
app.UseAuthentication();
//开启授权验证([Authorize])
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
https://localhost:5001/Account/Login?ReturnUrl=%2F
参照:
首先在http的请求管道里注册身份认证和授权中间件(
app.UseAuthentication();app.UseAuthorization();
),中间件拦截之后就根据你注入的认证服务(services.AddAuthentication()
)和认证方案(AddCookie()
)进行身份认证,如果获取到了身份的话就包装成ClaimsPrincipal
放在HttpContext.User
上,没获取到的话也要继续向下走。接着授权中间件找到了Controller.Action
观察是否有[Authorize]
标记,如果有的话就检查是否认证了身份,没有认证身份的就按照LoginPath
加上ReturnUrl
重定向(比如:https://localhost:5001/Account/Login?ReturnUrl=%2F)
这里将Authentication和Authorization作为框架对待,而Cookie认证方式作为自定义的扩展对待。代码在它们之间的执行流程和关键点如下所示:
app.UseAuthentication()
说起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 request
var 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>();
///
/// Returns the schemes in the order they were added (important for request handling priority)
///
public IEnumerable<AuthenticationSchemeBuilder> Schemes => _schemes;
///
/// Maps schemes by name.
///
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
中。
这里有个就要问了“为什么不是创建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 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 once
if (_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 ticket
return 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;
};
});
从上面的分析中我们得知,身份认证主要有如下几个接口组成
UseAuthentication()
中间件发起)UseAuthorization()
中间件配合[Authorize]
发起)上面这些接口的调用入口都集中放在了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 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 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 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 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 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
中只定义了认证、询问、禁止三个接口,而登录和登出并没有定义。经过探索发现登录和登出的方法分别在IAuthenticationSignInHandler
和IAuthenticationSignOutHandler
方法中定义了,它们的代码如下:
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就可以了!