导航

11-005 Security 之 CookieAuthenticationHandler

Posted on 2015-04-22 17:45  DotNet1010  阅读(403)  评论(0)    收藏  举报

--

 internal class CookieAuthenticationHandler : AuthenticationHandler<CookieAuthenticationOptions>
    {
        private const string HeaderNameCacheControl = "Cache-Control";
		// 预处理 指示
        private const string HeaderNamePragma = "Pragma";
        private const string HeaderNameExpires = "Expires";
        private const string HeaderValueNoCache = "no-cache";
        private const string HeaderValueMinusOne = "-1";
        private const string SessionIdClaim = "Microsoft.AspNet.Security.Cookies-SessionId";

        private readonly ILogger _logger;

        private bool _shouldRenew;
        private DateTimeOffset _renewIssuedUtc;
        private DateTimeOffset _renewExpiresUtc;
        private string _sessionKey;

        public CookieAuthenticationHandler([NotNull] ILogger logger)
        {
            _logger = logger;
        }

        protected override AuthenticationTicket AuthenticateCore()
        {
            return AuthenticateCoreAsync().GetAwaiter().GetResult();
        }

		/// <summary>
		///  只有在ActiveMode 下才会执行 主要是修改 User 添加Claim
		///  ClaimsIdentity identity = new ClaimsIdentity(new[] { new Claim(SessionIdClaim, _sessionKey) }, Options.AuthenticationType);
		/// SessionIdClaim="Microsoft.AspNet.Security.Cookies-SessionId";
		/// _sessionKey="";AuthenticationType="Cookies";
		/// </summary>
		/// <returns></returns>
		protected override async Task<AuthenticationTicket> AuthenticateCoreAsync()
        {
            AuthenticationTicket ticket = null;
            try
            {
                string cookie = Options.CookieManager.GetRequestCookie(Context, Options.CookieName);
                if (string.IsNullOrWhiteSpace(cookie))
                {
                    return null;
                }

				// 从Cookie 获取
                ticket = Options.TicketDataFormat.Unprotect(cookie);

                if (ticket == null)
                {
                    _logger.WriteWarning(@"Unprotect ticket failed");
                    return null;
                }

				// 从Session获取
                if (Options.SessionStore != null)
                {
					#region ---------
					Claim claim = ticket.Identity.Claims.FirstOrDefault(c => c.Type.Equals(SessionIdClaim));
                    if (claim == null)
                    {
                        _logger.WriteWarning(@"SessionId missing");
                        return null;
                    }
                    _sessionKey = claim.Value;
                    ticket = await Options.SessionStore.RetrieveAsync(_sessionKey);
                    if (ticket == null)
                    {
                        _logger.WriteWarning(@"Identity missing in session store");
                        return null;
                    }
					#endregion
				}

                DateTimeOffset currentUtc = Options.SystemClock.UtcNow;
                DateTimeOffset? issuedUtc = ticket.Properties.IssuedUtc;
                DateTimeOffset? expiresUtc = ticket.Properties.ExpiresUtc;

				// 过期的话 直接退出
                if (expiresUtc != null && expiresUtc.Value < currentUtc)
                {
                    if (Options.SessionStore != null)
                    {
                        await Options.SessionStore.RemoveAsync(_sessionKey);
                    }
                    return null;
                }

                bool allowRefresh = ticket.Properties.AllowRefresh ?? true;
                if (issuedUtc != null && expiresUtc != null && Options.SlidingExpiration && allowRefresh)
                {
					#region Refresh-----
					TimeSpan timeElapsed = currentUtc.Subtract(issuedUtc.Value);
                    TimeSpan timeRemaining = expiresUtc.Value.Subtract(currentUtc);

					// 剩余时间 小于 已经用于的时间 
                    if (timeRemaining < timeElapsed)
                    {
                        _shouldRenew = true;
                        _renewIssuedUtc = currentUtc;
                        TimeSpan timeSpan = expiresUtc.Value.Subtract(issuedUtc.Value);
                        _renewExpiresUtc = currentUtc.Add(timeSpan);
                    }
					#endregion
				}

                var context = new CookieValidateIdentityContext(Context, ticket, Options);

                await Options.Notifications.ValidateIdentity(context);
				// 从Cookie 获取的信息
                return new AuthenticationTicket(context.Identity, context.Properties);
            }
            catch (Exception exception)
            {
                CookieExceptionContext exceptionContext = new CookieExceptionContext(Context, Options,
                    CookieExceptionContext.ExceptionLocation.Authenticate, exception, ticket);
                Options.Notifications.Exception(exceptionContext);
                if (exceptionContext.Rethrow)
                {
                    throw;
                }
                return exceptionContext.Ticket;
            }
        }

        protected override void ApplyResponseGrant()
        {
            ApplyResponseGrantAsync().GetAwaiter().GetResult();
        }

		/// <summary>
		/// Override this method to dela with sign-in/sign-out concerns, if an authentication scheme in question
		/// deals with grant/revoke as part of it's request flow. (like setting/deleting cookies)
		/// 授予 撤销  登入 登出 设置Cookie 删除Cookie
		/// </summary>
		/// <returns></returns>
		protected override async Task ApplyResponseGrantAsync()
        {
            var signin = SignInIdentityContext;
            bool shouldSignin = signin != null;    //执行过SignIn方法 
            var signout = SignOutContext;        
            bool shouldSignout = signout != null;  //执行过SignOut方法

			//全部是False 不登入 不登出  不ReNew 直接退出
            if (!(shouldSignin || shouldSignout || _shouldRenew))
            {
                return;
            }

			//从Cookie 获取的信息
            AuthenticationTicket model = await AuthenticateAsync();
            try
            {
                var cookieOptions = new CookieOptions
                {
                    Domain = Options.CookieDomain,
                    HttpOnly = Options.CookieHttpOnly,
                    Path = Options.CookiePath ?? (RequestPathBase.HasValue ? RequestPathBase.ToString() : "/"),
                };
                if (Options.CookieSecure == CookieSecureOption.SameAsRequest)
                {
                    cookieOptions.Secure = Request.IsSecure;
                }
                else
                {
                    cookieOptions.Secure = Options.CookieSecure == CookieSecureOption.Always;
                }

                if (shouldSignin)
                {
					#region ----shouldSignin----

					var signInContext = new CookieResponseSignInContext(
                        Context,
                        Options,
                        Options.AuthenticationType,
                        signin.Identity,
                        signin.Properties,
                        cookieOptions);

                    DateTimeOffset issuedUtc;
                    if (signin.Properties.IssuedUtc.HasValue)
                    {
                        issuedUtc = signin.Properties.IssuedUtc.Value;
                    }
                    else
                    {
                        issuedUtc = Options.SystemClock.UtcNow;
                        signin.Properties.IssuedUtc = issuedUtc;
                    }

                    if (!signin.Properties.ExpiresUtc.HasValue)
                    {
                        signin.Properties.ExpiresUtc = issuedUtc.Add(Options.ExpireTimeSpan);
                    }

                    Options.Notifications.ResponseSignIn(signInContext);

                    if (signInContext.Properties.IsPersistent)
                    {
                        DateTimeOffset expiresUtc = signInContext.Properties.ExpiresUtc ?? issuedUtc.Add(Options.ExpireTimeSpan);
                        signInContext.CookieOptions.Expires = expiresUtc.ToUniversalTime().DateTime;
                    }

                    model = new AuthenticationTicket(signInContext.Identity, signInContext.Properties);
                    if (Options.SessionStore != null)
                    {
                        if (_sessionKey != null)
                        {
                            await Options.SessionStore.RemoveAsync(_sessionKey);
                        }
						// 存储
                        _sessionKey = await Options.SessionStore.StoreAsync(model);
                        ClaimsIdentity identity = new ClaimsIdentity(
                            new[] { new Claim(SessionIdClaim, _sessionKey) },
                            Options.AuthenticationType);
                        model = new AuthenticationTicket(identity, null);
                    }
                    string cookieValue = Options.TicketDataFormat.Protect(model);

					// 设置新的Cookie 
                    Options.CookieManager.AppendResponseCookie(
                        Context,
                        Options.CookieName,
                        cookieValue,
                        signInContext.CookieOptions);

                    var signedInContext = new CookieResponseSignedInContext(
                        Context,
                        Options,
                        Options.AuthenticationType,
                        signInContext.Identity,
                        signInContext.Properties);

                    Options.Notifications.ResponseSignedIn(signedInContext);

					#endregion
				}
                else if (shouldSignout)
                {
					#region----shouldSignout----

					if (Options.SessionStore != null && _sessionKey != null)
                    {
                        await Options.SessionStore.RemoveAsync(_sessionKey);
                    }

                    var context = new CookieResponseSignOutContext(
                        Context,
                        Options,
                        cookieOptions);
                    
                    Options.Notifications.ResponseSignOut(context);

					//删除Cookie
                    Options.CookieManager.DeleteCookie(
                        Context,
                        Options.CookieName,
                        context.CookieOptions);

					#endregion
				}
                else if (_shouldRenew)
                {
					#region ---ShouldReNew----
					model.Properties.IssuedUtc = _renewIssuedUtc;
                    model.Properties.ExpiresUtc = _renewExpiresUtc;

                    if (Options.SessionStore != null && _sessionKey != null)
                    {
                        await Options.SessionStore.RenewAsync(_sessionKey, model);
                        ClaimsIdentity identity = new ClaimsIdentity(
                            new[] { new Claim(SessionIdClaim, _sessionKey) },
                            Options.AuthenticationType);
                        model = new AuthenticationTicket(identity, null);
                    }

                    string cookieValue = Options.TicketDataFormat.Protect(model);

                    if (model.Properties.IsPersistent)
                    {
                        cookieOptions.Expires = _renewExpiresUtc.ToUniversalTime().DateTime;
                    }

                    Options.CookieManager.AppendResponseCookie(
                        Context,
                        Options.CookieName,
                        cookieValue,
                        cookieOptions);

					#endregion 
				}

                Response.Headers.Set(  HeaderNameCacheControl,  HeaderValueNoCache);

                Response.Headers.Set(  HeaderNamePragma,  HeaderValueNoCache);

                Response.Headers.Set( HeaderNameExpires, HeaderValueMinusOne);

                bool shouldLoginRedirect = shouldSignin && Options.LoginPath.HasValue && Request.Path == Options.LoginPath;
                bool shouldLogoutRedirect = shouldSignout && Options.LogoutPath.HasValue && Request.Path == Options.LogoutPath;

                if ((shouldLoginRedirect || shouldLogoutRedirect) && Response.StatusCode == 200)
                {
                    IReadableStringCollection query = Request.Query;
                    string redirectUri = query.Get(Options.ReturnUrlParameter);
                    if (!string.IsNullOrWhiteSpace(redirectUri)
                        && IsHostRelative(redirectUri))
                    {
                        var redirectContext = new CookieApplyRedirectContext(Context, Options, redirectUri);
						// 重定向 ajax 或者 直接重定向
                        Options.Notifications.ApplyRedirect(redirectContext);
                    }
                }
            }
            catch (Exception exception)
            {
                CookieExceptionContext exceptionContext = new CookieExceptionContext(Context, Options,
                    CookieExceptionContext.ExceptionLocation.ApplyResponseGrant, exception, model);
                Options.Notifications.Exception(exceptionContext);
                if (exceptionContext.Rethrow)
                {
                    throw;
                }
            }
        }

        protected override void ApplyResponseChallenge()
        {

			 //不是 401 状态码 或者 没有LoginPath 直接退出
            if (Response.StatusCode != 401 || !Options.LoginPath.HasValue )
            {
                return;
            }

			// 是401 并且有LoginPath 继续执行

            // Active middleware should redirect on 401 even if there wasn't an explicit challenge.
            if (ChallengeContext == null && Options.AuthenticationMode == AuthenticationMode.Passive)
            {
                return;
            }

            string loginUri = string.Empty;
            if (ChallengeContext != null)
            {
				//  return Dictionary.TryGetValue(".redirect", out value) ? value : null;
				loginUri = new AuthenticationProperties(ChallengeContext.Properties).RedirectUri;
            }

            try
            {
                if (string.IsNullOrWhiteSpace(loginUri))
                {
					#region -- set currentUri ; loginUri
					string currentUri =
                        Request.PathBase +
                        Request.Path +
                        Request.QueryString;

                    loginUri =
                        Request.Scheme +
                        "://" +
                        Request.Host +
                        Request.PathBase +
                        Options.LoginPath +
                        new QueryString(Options.ReturnUrlParameter, currentUri);
					#endregion
				}

                var redirectContext = new CookieApplyRedirectContext(Context, Options, loginUri);
				// ajax请求 返回成功 和 信息   其它直接重定向
                Options.Notifications.ApplyRedirect(redirectContext);
            }
            catch (Exception exception)
            {
                CookieExceptionContext exceptionContext = new CookieExceptionContext(Context, Options,
                    CookieExceptionContext.ExceptionLocation.ApplyResponseChallenge, exception, ticket: null);
                Options.Notifications.Exception(exceptionContext);
                if (exceptionContext.Rethrow)
                {
                    throw;
                }
            }
        }

		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] != '\\';
		}
	}