认证(Authentication),指确定请求访问者真实身份的过程。 授权(Authrization)
有几个概念和定义需要清楚
简单理解是key-value的陈述,比如我想陈述我的邮件是123@qq.com,那就key就是email,value就是123@qq.com
namespace System.Security.Claims { public class Claim { /// <summary> /// 声明的类型,比如上面说的email /// <seealso cref="ClaimTypes"/>. /// </summary> public string Type { get; } /// <summary> /// Value,比如上面说的123@qq.com /// </summary> public string Value { get; } /// <summary> /// Value的类型,比如字符串或日期 /// <seealso cref="ClaimValueTypes"/> /// </summary> public string ValueType { get; } /// <summary> /// 颁发人 /// </summary> public string Issuer { get; } /// <summary> /// 最初颁发人 /// </summary> public string OriginalIssuer { get; } //... } }
namespace System.Security.Principal { public interface IIdentity { //名称 string? Name { get; } //认证类型 string? AuthenticationType { get; } //是否已经过认证 bool IsAuthenticated { get; } } }
有两个主要的实现,ClaimsIdentity
和GenericIdentity
。
认证的主体,可以是人,但也可以是一个应用或服务,所以不能叫用户,这里.net起名叫做主体(Principal)。
namespace System.Security.Principal { public interface IPrincipal { // 身份 IIdentity? Identity { get; } // Perform a check for a specific role bool IsInRole(string role); } }
也有两个主要实现,ClaimsPrincipal
和GenericPrincipal
。
目前为止,他们的关系
.net采用基于票据的认证方式,从请求中提取能够验证用户真实身份的数据,该数据被称为安全令牌(Security Token),在.net里被称为认证票据。
AuthenticationMiddleware中间件所作,就是3件事,都是针对票据的
namespace Microsoft.AspNetCore.Authentication { public class AuthenticationTicket { // 采取的认证方案 public string AuthenticationScheme { get; } // 主题人 public ClaimsPrincipal Principal { get; } // 其他属性值 public AuthenticationProperties Properties { get; } } }
namespace Microsoft.AspNetCore.Authentication { public class AuthenticationProperties { // 票据颁发时间 [JsonIgnore] public DateTimeOffset? IssuedUtc { get => GetDateTimeOffset(IssuedUtcKey); set => SetDateTimeOffset(IssuedUtcKey, value); } // 票据过期时间 [JsonIgnore] public DateTimeOffset? ExpiresUtc { get => GetDateTimeOffset(ExpiresUtcKey); set => SetDateTimeOffset(ExpiresUtcKey, value); } // 是否允许自动刷新票据(就是我们说的token滑动过期模式) [JsonIgnore] public bool? AllowRefresh { get => GetBool(RefreshKey); set => SetBool(RefreshKey, value); } // 是否希望被客户端持久化,比如客户端时浏览器,持久化后,重启浏览器还可以继续使用 [JsonIgnore] public bool IsPersistent { get => GetString(IsPersistentKey) != null; set => SetString(IsPersistentKey, value ? string.Empty : null); } // 重定向地址 [JsonIgnore] public string? RedirectUri { get => GetString(RedirectUriKey); set => SetString(RedirectUriKey, value); } public IDictionary<string, string?> Items { get; }
Challenge一般以401 Unauthorized或403 Forbidden的形式,向申请认证方质询,要求提供合法凭证进行认证。
public interface IAuthenticationHandler { Task InitializeAsync(AuthenticationScheme scheme, HttpContext context); Task<AuthenticateResult> AuthenticateAsync(); Task ChallengeAsync(AuthenticationProperties properties); Task ForbidAsync(AuthenticationProperties properties); }
另外3个派生出的接口:
public interface IAuthenticationSignInHandler : IAuthenticationSignOutHandler { Task SignInAsync(ClaimsPrincipal user, AuthenticationProperties properties); } public interface IAuthenticationSignOutHandler : IAuthenticationHandler { Task SignOutAsync(AuthenticationProperties properties); } public interface IAuthenticationRequestHandler : IAuthenticationHandler { Task<bool> HandleRequestAsync(); }
/// <summary> /// AuthenticationSchemes assign a name to a specific <see cref="IAuthenticationHandler"/> /// handlerType. /// </summary> public class AuthenticationScheme { public string Name { get; } public string DisplayName { get; } /// <summary> /// The <see cref="IAuthenticationHandler"/> type that handles this scheme. /// </summary> public Type HandlerType { get; } }
一个认证方案对应一种IAuthenticationHandler。
方法与上面的IAuthenticationHandler基本一致,不一样的是,如参是HttpContext,是针对的请求的。
public interface IAuthenticationService { /// <summary> /// Authenticate for the specified authentication scheme. /// </summary> /// <param name="context">The <see cref="HttpContext"/>.</param> /// <param name="scheme">The name of the authentication scheme.</param> /// <returns>The result.</returns> Task<AuthenticateResult> AuthenticateAsync(HttpContext context, string scheme); /// <summary> /// Challenge the specified authentication scheme. /// </summary> /// <param name="context">The <see cref="HttpContext"/>.</param> /// <param name="scheme">The name of the authentication scheme.</param> /// <param name="properties">The <see cref="AuthenticationProperties"/>.</param> /// <returns>A task.</returns> Task ChallengeAsync(HttpContext context, string scheme, AuthenticationProperties properties); /// <summary> /// Forbids the specified authentication scheme. /// </summary> /// <param name="context">The <see cref="HttpContext"/>.</param> /// <param name="scheme">The name of the authentication scheme.</param> /// <param name="properties">The <see cref="AuthenticationProperties"/>.</param> /// <returns>A task.</returns> Task ForbidAsync(HttpContext context, string scheme, AuthenticationProperties properties); /// <summary> /// Sign a principal in for the specified authentication scheme. /// </summary> /// <param name="context">The <see cref="HttpContext"/>.</param> /// <param name="scheme">The name of the authentication scheme.</param> /// <param name="principal">The <see cref="ClaimsPrincipal"/> to sign in.</param> /// <param name="properties">The <see cref="AuthenticationProperties"/>.</param> /// <returns>A task.</returns> Task SignInAsync(HttpContext context, string scheme, ClaimsPrincipal principal, AuthenticationProperties properties); /// <summary> /// Sign out the specified authentication scheme. /// </summary> /// <param name="context">The <see cref="HttpContext"/>.</param> /// <param name="scheme">The name of the authentication scheme.</param> /// <param name="properties">The <see cref="AuthenticationProperties"/>.</param> /// <returns>A task.</returns> Task SignOutAsync(HttpContext context, string scheme, AuthenticationProperties properties); }
默认实现:
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}."); } } }
本文作者:Ray
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
特别感谢:alicenetworks