• 2.3CookieAuthenticationHandler【SignInAuthenticationHandler】


    using System;
    using System.Diagnostics;
    using System.Linq;
    using System.Security.Claims;
    using System.Text.Encodings.Web;
    using System.Threading.Tasks;
    using Microsoft.AspNetCore.Http;
    using Microsoft.AspNetCore.Http.Features;
    using Microsoft.Extensions.Logging;
    using Microsoft.Extensions.Options;
    using Microsoft.Net.Http.Headers;
    
    namespace Microsoft.AspNetCore.Authentication.Cookies
    {
        /// <summary>
        /// Implementation for the cookie-based authentication handler.
        /// </summary>
        public class CookieAuthenticationHandler : SignInAuthenticationHandler<CookieAuthenticationOptions>
        {
            private const string HeaderValueNoCache = "no-cache";
            private const string HeaderValueNoCacheNoStore = "no-cache,no-store";
            private const string HeaderValueEpocDate = "Thu, 01 Jan 1970 00:00:00 GMT";
            private const string SessionIdClaim = "Microsoft.AspNetCore.Authentication.Cookies-SessionId";
    
            private bool _shouldRefresh;
            private bool _signInCalled;
            private bool _signOutCalled;
    
            private DateTimeOffset? _refreshIssuedUtc;
            private DateTimeOffset? _refreshExpiresUtc;
            private string? _sessionKey;
            private Task<AuthenticateResult>? _readCookieTask;
            private AuthenticationTicket? _refreshTicket;
    
            /// <summary>
            /// Initializes a new instance of <see cref="CookieAuthenticationHandler"/>.
            /// </summary>
            /// <param name="options">Accessor to <see cref="CookieAuthenticationOptions"/>.</param>
            /// <param name="logger">The <see cref="ILoggerFactory"/>.</param>
            /// <param name="encoder">The <see cref="UrlEncoder"/>.</param>
            /// <param name="clock">The <see cref="ISystemClock"/>.</param>
            public CookieAuthenticationHandler(IOptionsMonitor<CookieAuthenticationOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
                : base(options, logger, encoder, clock)
            { }
    
            /// <summary>
            /// The handler calls methods on the events which give the application control at certain points where processing is occurring.
            /// If it is not provided a default instance is supplied which does nothing when the methods are called.
            /// </summary>
            protected new CookieAuthenticationEvents Events
            {
                get { return (CookieAuthenticationEvents)base.Events!; }
                set { base.Events = value; }
            }
    
            /// <inheritdoc />
            protected override Task InitializeHandlerAsync()
            {
                // Cookies needs to finish the response
                Context.Response.OnStarting(FinishResponseAsync);
                return Task.CompletedTask;
            }
    
            /// <summary>
            /// Creates a new instance of the events instance.
            /// </summary>
            /// <returns>A new instance of the events instance.</returns>
            protected override Task<object> CreateEventsAsync() => Task.FromResult<object>(new CookieAuthenticationEvents());
    
            private Task<AuthenticateResult> EnsureCookieTicket()
            {
                // We only need to read the ticket once
                if (_readCookieTask == null)
                {
                    _readCookieTask = ReadCookieTicket();
                }
                return _readCookieTask;
            }
    
            private async Task CheckForRefreshAsync(AuthenticationTicket ticket)
            {
                var currentUtc = Clock.UtcNow;
                var issuedUtc = ticket.Properties.IssuedUtc;
                var expiresUtc = ticket.Properties.ExpiresUtc;
                var allowRefresh = ticket.Properties.AllowRefresh ?? true;
                if (issuedUtc != null && expiresUtc != null && Options.SlidingExpiration && allowRefresh)
                {
                    var timeElapsed = currentUtc.Subtract(issuedUtc.Value);
                    var timeRemaining = expiresUtc.Value.Subtract(currentUtc);
    
                    var eventContext = new CookieSlidingExpirationContext(Context, Scheme, Options, ticket, timeElapsed, timeRemaining)
                    {
                        ShouldRenew = timeRemaining < timeElapsed,
                    };
                    await Options.Events.OnCheckSlidingExpiration(eventContext);
    
                    if (eventContext.ShouldRenew)
                    {
                        RequestRefresh(ticket);
                    }
                }
            }
    
            private void RequestRefresh(AuthenticationTicket ticket, ClaimsPrincipal? replacedPrincipal = null)
            {
                var issuedUtc = ticket.Properties.IssuedUtc;
                var expiresUtc = ticket.Properties.ExpiresUtc;
    
                if (issuedUtc != null && expiresUtc != null)
                {
                    _shouldRefresh = true;
                    var currentUtc = Clock.UtcNow;
                    _refreshIssuedUtc = currentUtc;
                    var timeSpan = expiresUtc.Value.Subtract(issuedUtc.Value);
                    _refreshExpiresUtc = currentUtc.Add(timeSpan);
                    _refreshTicket = CloneTicket(ticket, replacedPrincipal);
                }
            }
    
            private static AuthenticationTicket CloneTicket(AuthenticationTicket ticket, ClaimsPrincipal? replacedPrincipal)
            {
                var principal = replacedPrincipal ?? ticket.Principal;
                var newPrincipal = new ClaimsPrincipal();
                foreach (var identity in principal.Identities)
                {
                    newPrincipal.AddIdentity(identity.Clone());
                }
    
                var newProperties = new AuthenticationProperties();
                foreach (var item in ticket.Properties.Items)
                {
                    newProperties.Items[item.Key] = item.Value;
                }
    
                return new AuthenticationTicket(newPrincipal, newProperties, ticket.AuthenticationScheme);
            }
    
            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");
                    }
                    // Only store _sessionKey if it matches an existing session. Otherwise we'll create a new one.
                    ticket = await Options.SessionStore.RetrieveAsync(claim.Value, Context.RequestAborted);
                    if (ticket == null)
                    {
                        return AuthenticateResult.Fail("Identity missing in session store");
                    }
                    _sessionKey = claim.Value;
                }
    
                var currentUtc = Clock.UtcNow;
                var expiresUtc = ticket.Properties.ExpiresUtc;
    
                if (expiresUtc != null && expiresUtc.Value < currentUtc)
                {
                    if (Options.SessionStore != null)
                    {
                        await Options.SessionStore.RemoveAsync(_sessionKey!, Context.RequestAborted);
                    }
                    return AuthenticateResult.Fail("Ticket expired");
                }
    
                // Finally we have a valid ticket
                return AuthenticateResult.Success(ticket);
            }
    
            /// <inheritdoc />
            protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
            {
                var result = await EnsureCookieTicket();
                if (!result.Succeeded)
                {
                    return result;
                }
    
                // We check this before the ValidatePrincipal event because we want to make sure we capture a clean clone
                // without picking up any per-request modifications to the principal.
                await CheckForRefreshAsync(result.Ticket);
    
                Debug.Assert(result.Ticket != null);
                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 CookieOptions BuildCookieOptions()
            {
                var cookieOptions = Options.Cookie.Build(Context);
                // ignore the 'Expires' value as this will be computed elsewhere
                cookieOptions.Expires = null;
    
                return cookieOptions;
            }
    
            /// <inheritdoc />
            protected virtual async Task FinishResponseAsync()
            {
                // Only renew if requested, and neither sign in or sign out was called
                if (!_shouldRefresh || _signInCalled || _signOutCalled)
                {
                    return;
                }
    
                var ticket = _refreshTicket;
                if (ticket != null)
                {
                    var properties = ticket.Properties;
    
                    if (_refreshIssuedUtc.HasValue)
                    {
                        properties.IssuedUtc = _refreshIssuedUtc;
                    }
    
                    if (_refreshExpiresUtc.HasValue)
                    {
                        properties.ExpiresUtc = _refreshExpiresUtc;
                    }
    
                    if (Options.SessionStore != null && _sessionKey != null)
                    {
                        await Options.SessionStore.RenewAsync(_sessionKey, ticket, Context.RequestAborted);
                        var principal = new ClaimsPrincipal(
                            new ClaimsIdentity(
                                new[] { new Claim(SessionIdClaim, _sessionKey, ClaimValueTypes.String, Options.ClaimsIssuer) },
                                Scheme.Name));
                        ticket = new AuthenticationTicket(principal, null, Scheme.Name);
                    }
    
                    var cookieValue = Options.TicketDataFormat.Protect(ticket, GetTlsTokenBinding());
    
                    var cookieOptions = BuildCookieOptions();
                    if (properties.IsPersistent && _refreshExpiresUtc.HasValue)
                    {
                        cookieOptions.Expires = _refreshExpiresUtc.Value.ToUniversalTime();
                    }
    
                    Options.CookieManager.AppendResponseCookie(
                        Context,
                        Options.Cookie.Name!,
                        cookieValue,
                        cookieOptions);
    
                    await ApplyHeaders(shouldRedirectToReturnUrl: false, properties: properties);
                }
            }
    
            /// <inheritdoc />
            protected override async Task HandleSignInAsync(ClaimsPrincipal user, AuthenticationProperties? properties)
            {
                if (user == null)
                {
                    throw new ArgumentNullException(nameof(user));
                }
    
                properties = properties ?? new AuthenticationProperties();
    
                _signInCalled = true;
    
                // Process the request cookie to initialize members like _sessionKey.
                await EnsureCookieTicket();
                var cookieOptions = BuildCookieOptions();
    
                var signInContext = new CookieSigningInContext(
                    Context,
                    Scheme,
                    Options,
                    user,
                    properties,
                    cookieOptions);
    
                DateTimeOffset issuedUtc;
                if (signInContext.Properties.IssuedUtc.HasValue)
                {
                    issuedUtc = signInContext.Properties.IssuedUtc.Value;
                }
                else
                {
                    issuedUtc = Clock.UtcNow;
                    signInContext.Properties.IssuedUtc = issuedUtc;
                }
    
                if (!signInContext.Properties.ExpiresUtc.HasValue)
                {
                    signInContext.Properties.ExpiresUtc = issuedUtc.Add(Options.ExpireTimeSpan);
                }
    
                await Events.SigningIn(signInContext);
    
                if (signInContext.Properties.IsPersistent)
                {
                    var expiresUtc = signInContext.Properties.ExpiresUtc ?? issuedUtc.Add(Options.ExpireTimeSpan);
                    signInContext.CookieOptions.Expires = expiresUtc.ToUniversalTime();
                }
    
                var ticket = new AuthenticationTicket(signInContext.Principal!, signInContext.Properties, signInContext.Scheme.Name);
    
                if (Options.SessionStore != null)
                {
                    if (_sessionKey != null)
                    {
                        // Renew the ticket in cases of multiple requests see: https://github.com/dotnet/aspnetcore/issues/22135
                        await Options.SessionStore.RenewAsync(_sessionKey, ticket, Context.RequestAborted);
                    }
                    else
                    {
                        _sessionKey = await Options.SessionStore.StoreAsync(ticket, Context.RequestAborted);
                    }
    
                    var principal = new ClaimsPrincipal(
                        new ClaimsIdentity(
                            new[] { new Claim(SessionIdClaim, _sessionKey, ClaimValueTypes.String, Options.ClaimsIssuer) },
                            Options.ClaimsIssuer));
                    ticket = new AuthenticationTicket(principal, null, Scheme.Name);
                }
    
                var cookieValue = Options.TicketDataFormat.Protect(ticket, GetTlsTokenBinding());
    
                Options.CookieManager.AppendResponseCookie(
                    Context,
                    Options.Cookie.Name!,
                    cookieValue,
                    signInContext.CookieOptions);
    
                var signedInContext = new CookieSignedInContext(
                    Context,
                    Scheme,
                    signInContext.Principal!,
                    signInContext.Properties,
                    Options);
    
                await Events.SignedIn(signedInContext);
    
                // Only redirect on the login path
                var shouldRedirect = Options.LoginPath.HasValue && OriginalPath == Options.LoginPath;
                await ApplyHeaders(shouldRedirect, signedInContext.Properties);
    
                Logger.AuthenticationSchemeSignedIn(Scheme.Name);
            }
    
            /// <inheritdoc />
            protected override async Task HandleSignOutAsync(AuthenticationProperties? properties)
            {
                properties = properties ?? new AuthenticationProperties();
    
                _signOutCalled = true;
    
                // Process the request cookie to initialize members like _sessionKey.
                await EnsureCookieTicket();
                var cookieOptions = BuildCookieOptions();
                if (Options.SessionStore != null && _sessionKey != null)
                {
                    await Options.SessionStore.RemoveAsync(_sessionKey, Context.RequestAborted);
                }
    
                var context = new CookieSigningOutContext(
                    Context,
                    Scheme,
                    Options,
                    properties,
                    cookieOptions);
    
                await Events.SigningOut(context);
    
                Options.CookieManager.DeleteCookie(
                    Context,
                    Options.Cookie.Name!,
                    context.CookieOptions);
    
                // Only redirect on the logout path
                var shouldRedirect = Options.LogoutPath.HasValue && OriginalPath == Options.LogoutPath;
                await ApplyHeaders(shouldRedirect, context.Properties);
    
                Logger.AuthenticationSchemeSignedOut(Scheme.Name);
            }
    
            private async Task ApplyHeaders(bool shouldRedirectToReturnUrl, AuthenticationProperties properties)
            {
                Response.Headers.CacheControl = HeaderValueNoCacheNoStore;
                Response.Headers.Pragma = HeaderValueNoCache;
                Response.Headers.Expires = HeaderValueEpocDate;
    
                if (shouldRedirectToReturnUrl && Response.StatusCode == 200)
                {
                    // set redirect uri in order:
                    // 1. properties.RedirectUri
                    // 2. query parameter ReturnUrlParameter
                    //
                    // Absolute uri is not allowed if it is from query string as query string is not
                    // a trusted source.
                    var redirectUri = properties.RedirectUri;
                    if (string.IsNullOrEmpty(redirectUri))
                    {
                        redirectUri = Request.Query[Options.ReturnUrlParameter];
                        if (string.IsNullOrEmpty(redirectUri) || !IsHostRelative(redirectUri))
                        {
                            redirectUri = null;
                        }
                    }
    
                    if (redirectUri != null)
                    {
                        await Events.RedirectToReturnUrl(
                            new RedirectContext<CookieAuthenticationOptions>(Context, Scheme, Options, properties, redirectUri));
                    }
                }
            }
    
            private static bool IsHostRelative(string path)
            {
                if (string.IsNullOrEmpty(path))
                {
                    return false;
                }
                if (path.Length == 1)
                {
                    return path[0] == '/';
                }
                return path[0] == '/' && path[1] != '/' && path[1] != '\\';
            }
    
            /// <inheritdoc />
            protected override async Task HandleForbiddenAsync(AuthenticationProperties properties)
            {
                var returnUrl = properties.RedirectUri;
                if (string.IsNullOrEmpty(returnUrl))
                {
                    returnUrl = OriginalPathBase + OriginalPath + Request.QueryString;
                }
                var accessDeniedUri = Options.AccessDeniedPath + QueryString.Create(Options.ReturnUrlParameter, returnUrl);
                var redirectContext = new RedirectContext<CookieAuthenticationOptions>(Context, Scheme, Options, properties, BuildRedirectUri(accessDeniedUri));
                await Events.RedirectToAccessDenied(redirectContext);
            }
    
            /// <inheritdoc />
            protected override async Task HandleChallengeAsync(AuthenticationProperties properties)
            {
                var redirectUri = properties.RedirectUri;
                if (string.IsNullOrEmpty(redirectUri))
                {
                    redirectUri = OriginalPathBase + OriginalPath + Request.QueryString;
                }
    
                var loginUri = Options.LoginPath + QueryString.Create(Options.ReturnUrlParameter, redirectUri);
                var redirectContext = new RedirectContext<CookieAuthenticationOptions>(Context, Scheme, Options, properties, BuildRedirectUri(loginUri));
                await Events.RedirectToLogin(redirectContext);
            }
    
            private string? GetTlsTokenBinding()
            {
                var binding = Context.Features.Get<ITlsTokenBindingFeature>()?.GetProvidedTokenBindingId();
                return binding == null ? null : Convert.ToBase64String(binding);
            }
        }
    }
  • 相关阅读:
    form表单提交target属性使用
    window.showModalDialog
    mybaits中date类型显示时分秒(orcle数据库)
    mybatis中in查询
    偷懒的inline-block解决方法
    10. python单元测试(一)
    9. Request & 爬虫
    8. 类与对象
    7. python异常处理&异常基类学习
    6. IO及文件操作
  • 原文地址:https://www.cnblogs.com/htlp/p/15256497.html
Copyright © 2020-2023  润新知