导航

11-004 Security 之 AuthenticationHandler

Posted on 2015-04-22 16:26  DotNet1010  阅读(287)  评论(0)    收藏  举报

--

  /// <summary>
    /// Base class for the per-request work performed by most authentication middleware.
	/// 抽象基类:身份验证中间件每次请求要完成的工作
    /// </summary>
    public abstract class AuthenticationHandler : IAuthenticationHandler
    {
        private static readonly RandomNumberGenerator CryptoRandom = RandomNumberGenerator.Create();


		#region -----private public property

		private Task<AuthenticationTicket> _authenticate;
        private bool _authenticateInitialized;
        private object _authenticateSyncLock;

        private Task _applyResponse;
        private bool _applyResponseInitialized;
        private object _applyResponseSyncLock;

        private AuthenticationOptions _baseOptions;

		/// <summary>
		///  两个属性:AuthenticationTypes Properties
		/// 一个方法: void Accept(string authenticationType, IDictionary<string, object> description);
		/// </summary>
		protected IChallengeContext ChallengeContext { get; set; }
		/// <summary>
		/// 两个属性:ClaimsIdentity Identity { get; private set; }
		/// AuthenticationProperties Properties { get; private set; }
		/// </summary>
		protected SignInIdentityContext SignInIdentityContext { get; set; }
		/// <summary>
		/// 一个属性:IEnumerable<string> AuthenticationTypes { get; }
		/// 一个方法: void Accept(string authenticationType, IDictionary<string, object> description);
		/// </summary>
		protected ISignOutContext SignOutContext { get; set; }

        protected HttpContext Context { get; private set; }

        protected HttpRequest Request
        {
            get { return Context.Request; }
        }

        protected HttpResponse Response
        {
            get { return Context.Response; }
        }

        protected PathString RequestPathBase { get; private set; }
		/// <summary>
		/// 三个属性:string AuthenticationType
		/// AuthenticationMode AuthenticationMode { get; set; } = AuthenticationMode.Active;
		/// public AuthenticationDescription Description { get; set; } = new AuthenticationDescription();
		/// </summary>
		internal AuthenticationOptions BaseOptions
        {
            get { return _baseOptions; }
        }

		/// <summary>
		/// 之前的Handler 默认是NULL
		/// </summary>
        public IAuthenticationHandler PriorHandler { get; set; }

		/// <summary>
		/// 是否发生了异常
		/// </summary>
        public bool Faulted { get; set; }

		#endregion

		public virtual void GetDescriptions(IAuthTypeContext authTypeContext)
		{

			/*
			   接口实现类:  public class AuthTypeContext : IAuthTypeContext
			public IEnumerable<AuthenticationDescription> Results
			{
				get { return _results; }
			}
                
			public void Accept(IDictionary<string, object> description)
			{
				_results.Add(new AuthenticationDescription(description));
			}
			*/
			authTypeContext.Accept(BaseOptions.Description.Dictionary);

			if (PriorHandler != null)
			{
				PriorHandler.GetDescriptions(authTypeContext);
			}
		}

		// 谁来调用此方法: Response.SignIn
		public virtual void SignIn(ISignInContext context)
		{
			ClaimsIdentity identity;
			// 举例CookieAuthentication AuthenticationType = "Cookies";
			//OAuth 可以是:GitHub-AccessToken  Google-AccessToken ;Microsoft-AccessToken 等;
			// 判断是否存在这种验证类型  
			if (SecurityHelper.LookupSignIn(context.Identities, BaseOptions.AuthenticationType, out identity))
			{
				SignInIdentityContext = new SignInIdentityContext(identity, new AuthenticationProperties(context.Properties));
				SignOutContext = null;
				/*
				 接口实现类: public class SignInContext : ISignInContext{}
				  private List<string> _accepted=new List<string>();
				  _accepted.Add(authenticationType);

			   */
				context.Accept(BaseOptions.AuthenticationType, BaseOptions.Description.Dictionary);
			}

			if (PriorHandler != null)
			{
				PriorHandler.SignIn(context);
			}
		}

		// 谁来调用此方法: Response.SignOut
		public virtual void SignOut(ISignOutContext context)
		{
			// 主动的话可以没有Type
			if (SecurityHelper.LookupSignOut(context.AuthenticationTypes, BaseOptions.AuthenticationType, BaseOptions.AuthenticationMode))
			{
				SignInIdentityContext = null;
				SignOutContext = context;
				/*
				 private List<string> _accepted=new List<string>();
				 _accepted.Add(authenticationType);
				*/
				context.Accept(BaseOptions.AuthenticationType, BaseOptions.Description.Dictionary);
			}

			if (PriorHandler != null)
			{
				PriorHandler.SignOut(context);
			}
		}
		/// <summary>
		/// 谁来调用此方法: Response.Challenge
		/// </summary>
		/// <param name="context"></param>
		public virtual void Challenge(IChallengeContext context)
		{
			// 主动的话可以没有Type
			if (SecurityHelper.LookupChallenge(context.AuthenticationTypes, BaseOptions.AuthenticationType, BaseOptions.AuthenticationMode))
			{
				ChallengeContext = context;
				/*
				  private List<string> _accepted=new List<string>();
				   _accepted.Add(authenticationType);
				*/
				context.Accept(BaseOptions.AuthenticationType, BaseOptions.Description.Dictionary);
			}

			if (PriorHandler != null)
			{
				PriorHandler.Challenge(context);
			}
		}



		private void RegisterAuthenticationHandler()
		{
			var auth = Context.GetAuthentication();
			PriorHandler = auth.Handler;
			auth.Handler = this;
		}

		/// <summary>
		/// 这个方法什么时候调用 执行到中间件Invoke的时候会先初始化
		/// 如:  app.UseCookieAuthentication( options =>{} );
		/// </summary>
		/// <param name="options"></param>
		/// <param name="context"></param>
		/// <returns></returns>
		protected async Task BaseInitializeAsync(AuthenticationOptions options, HttpContext context)
        {
            _baseOptions = options;
            Context = context;
            RequestPathBase = Request.PathBase;

			/*
			var auth = Context.GetAuthentication();
            PriorHandler = auth.Handler; 原来的Handler 默认是NULL
            auth.Handler = this; -----------这里是关键
	        */
			RegisterAuthenticationHandler();
			/*
			    public override void OnSendingHeaders(Action<object> callback, object state)
               {
                   HttpResponseFeature.OnSendingHeaders(callback, state);
                }

			  register(callback, state);

	        */

			/*
			   //   调用 abstract 方法 需要子类实现    ApplyResponseGrant();
			  //    调用 abstract 方法 需要子类实现   ApplyResponseChallenge();
			      handler.ApplyResponse();
			*/

			Response.OnSendingHeaders(OnSendingHeaderCallback, this);

			/*
			   protected virtual Task InitializeCoreAsync()
			   {
				   return Task.FromResult(0);
			   }

		   */
			await InitializeCoreAsync();

			// 默认是 Active  会修改   context.User = newClaimsPrincipal;
			if (BaseOptions.AuthenticationMode == AuthenticationMode.Active)
            {
				//  protected abstract AuthenticationTicket AuthenticateCore();
				AuthenticationTicket ticket = await AuthenticateAsync();
                if (ticket != null)
                {
					if (ticket.Identity != null)
					{
				     /*
					 public static void AddUserIdentity([NotNull] HttpContext context, [NotNull] IIdentity identity)
					{
						var newClaimsPrincipal = new ClaimsPrincipal(identity);

						ClaimsPrincipal existingPrincipal = context.User;
						if (existingPrincipal != null)
						{
							foreach (var existingClaimsIdentity in existingPrincipal.Identities)
							{
								if (existingClaimsIdentity.IsAuthenticated)
								{
									newClaimsPrincipal.AddIdentity(existingClaimsIdentity);
								}
							}
						}
						context.User = newClaimsPrincipal;
					}
						*/
						SecurityHelper.AddUserIdentity(Context, ticket.Identity);
					}
					else if (ticket.Principal != null)
					{   // 方法同上
						SecurityHelper.AddUserIdentity(Context, ticket.Principal.Identity);
					}
                }
            }
        }

        private static void OnSendingHeaderCallback(object state)
        {
            AuthenticationHandler handler = (AuthenticationHandler)state;
			//   调用 Virtual 方法 需要子类实现    ApplyResponseGrant();
			//    调用 Virtual 方法 需要子类实现   ApplyResponseChallenge();
			handler.ApplyResponse();
        }

        protected virtual Task InitializeCoreAsync()
        {
            return Task.FromResult(0);
        }

        /// <summary>
        /// Called once per request after Initialize and Invoke.
		///  初始化和 Invoke 后被调用;
        /// </summary>
        /// <returns>async completion</returns>
        internal async Task TeardownAsync()
        {
            try
            {
				//abstract  await  ApplyResponseGrantAsync();
				//abstract  await  ApplyResponseChallengeAsync();

				await ApplyResponseAsync();
            }
            catch (Exception)
            {
                try
                {
					//// virtual 空方法
					await TeardownCoreAsync();
                }
                catch (Exception)
                {
                    // Don't mask the original exception
                }
                UnregisterAuthenticationHandler();
                throw;
            }

			// virtual 空方法
            await TeardownCoreAsync();
			/*
			var auth = Context.GetAuthentication();
			auth.Handler = PriorHandler;
			*/
			UnregisterAuthenticationHandler();
        }

        protected virtual Task TeardownCoreAsync()
        {
            return Task.FromResult(0);
        }

        /// <summary>
        /// Called once by common code after initialization. If an authentication middleware responds directly to
        /// specifically known paths it must override this virtual, compare the request path to it's known paths, 
        /// provide any response information as appropriate, and true to stop further processing.
		///  初始化后被调用.
        /// </summary>
        /// <returns>Returning false will cause the common code to call the next middleware in line. Returning true will
        /// cause the common code to begin the async completion journey without calling the rest of the middleware
        /// pipeline.
		///  返回 false 会调用后续的中间件。 返回 true 会导致公共代码 异步完成 而不再调用后面的中间件。
		/// </returns>
        public virtual Task<bool> InvokeAsync()
        {
            return Task.FromResult<bool>(false);
        }

     

        public virtual void Authenticate(IAuthenticateContext context)
        {
            if (context.AuthenticationTypes.Contains(BaseOptions.AuthenticationType, StringComparer.Ordinal))
            {
				// 调用需要子类实现的方法
				//    protected abstract AuthenticationTicket AuthenticateCore();
				AuthenticationTicket ticket = Authenticate();
                if (ticket != null && ticket.Identity != null)
                {

					/*
			  var descrip = new AuthenticationDescription(description);
             _accepted.Add(descrip.AuthenticationType); // may not match identity.AuthType
             _results.Add(new AuthenticationResult(identity, new AuthenticationProperties(properties), descrip));
	                */
					context.Authenticated(ticket.Identity, ticket.Properties.Dictionary, BaseOptions.Description.Dictionary);
                }
                else
                {
					//  _accepted.Add(authenticationType);
					context.NotAuthenticated(BaseOptions.AuthenticationType, properties: null, description: BaseOptions.Description.Dictionary);
                }
            }

            if (PriorHandler != null)
            {
                PriorHandler.Authenticate(context);
            }
        }

        public virtual async Task AuthenticateAsync(IAuthenticateContext context)
        {
            if (context.AuthenticationTypes.Contains(BaseOptions.AuthenticationType, StringComparer.Ordinal))
            {
                AuthenticationTicket ticket = await AuthenticateAsync();
                if (ticket != null && ticket.Identity != null)
                {
                    context.Authenticated(ticket.Identity, ticket.Properties.Dictionary, BaseOptions.Description.Dictionary);
                }
                else
                {
                    context.NotAuthenticated(BaseOptions.AuthenticationType, properties: null, description: BaseOptions.Description.Dictionary);
                }
            }

            if (PriorHandler != null)
            {
                await PriorHandler.AuthenticateAsync(context);
            }
        }

        public AuthenticationTicket Authenticate()
        {
			// 确保只执行一次
            return LazyInitializer.EnsureInitialized(
                ref _authenticate,
                ref _authenticateInitialized,
                ref _authenticateSyncLock,
                () =>
                {
                    return Task.FromResult(AuthenticateCore());
                }).GetAwaiter().GetResult();
        }

        protected abstract AuthenticationTicket AuthenticateCore();

        /// <summary>
        /// Causes the authentication logic in AuthenticateCore to be performed for the current request 
        /// at most once and returns the results. Calling Authenticate more than once will always return 
        /// the original value. 
        /// 
        /// This method should always be called instead of calling AuthenticateCore directly.
        /// </summary>
        /// <returns>The ticket data provided by the authentication logic</returns>
        public Task<AuthenticationTicket> AuthenticateAsync()
        {
            return LazyInitializer.EnsureInitialized(
                ref _authenticate,
                ref _authenticateInitialized,
                ref _authenticateSyncLock,
                AuthenticateCoreAsync);
        }

        /// <summary>
        /// The core authentication logic which must be provided by the handler. Will be invoked at most
        /// once per request. Do not call directly, call the wrapping Authenticate method instead.
        /// </summary>
        /// <returns>The ticket data provided by the authentication logic</returns>
        protected virtual Task<AuthenticationTicket> AuthenticateCoreAsync()
        {
            return Task.FromResult(AuthenticateCore());
        }

        private void ApplyResponse()
        {
            // If ApplyResponse already failed in the OnSendingHeaderCallback or TeardownAsync code path then a
            // failed task is cached. If called again the same error will be re-thrown. This breaks error handling
            // scenarios like the ability to display the error page or re-execute the request.
            try
            {
                if (!Faulted)
                {
                    LazyInitializer.EnsureInitialized(
                        ref _applyResponse,
                        ref _applyResponseInitialized,
                        ref _applyResponseSyncLock,
                        () =>
                        {
                            ApplyResponseCore();
                            return Task.FromResult(0);
                        }).GetAwaiter().GetResult(); // Block if the async version is in progress.
                }
            }
            catch (Exception)
            {
                Faulted = true;
                throw;
            }
        }

        protected virtual void ApplyResponseCore()
        {
            ApplyResponseGrant();
            ApplyResponseChallenge();
        }

        /// <summary>
        /// Causes the ApplyResponseCore to be invoked at most once per request. This method will be
        /// invoked either earlier, when the response headers are sent as a result of a response write or flush,
        /// or later, as the last step when the original async call to the middleware is returning.
        /// </summary>
        /// <returns></returns>
        private async Task ApplyResponseAsync()
        {
            // If ApplyResponse already failed in the OnSendingHeaderCallback or TeardownAsync code path then a
            // failed task is cached. If called again the same error will be re-thrown. This breaks error handling
            // scenarios like the ability to display the error page or re-execute the request.
            try
            {
                if (!Faulted)
                {
                    await LazyInitializer.EnsureInitialized(
                        ref _applyResponse,
                        ref _applyResponseInitialized,
                        ref _applyResponseSyncLock,
                        ApplyResponseCoreAsync);
                }
            }
            catch (Exception)
            {
                Faulted = true;
                throw;
            }
        }

		/// <summary>
		/// Core method that may be overridden by handler. The default behavior is to call two common response 
		/// activities, one that deals with sign-in/sign-out concerns, and a second to deal with 401 challenges.
		/// 默认的行为时调用连个响应活动  一个 处理 登入 登出 如 设置 删除Cookie
		/// 另一个处理 401 未授权 质询
		/// 两个地方调用  TrearDown ON 另一个 是 OnSendingHeaderCallback
		/// </summary>
		/// <returns></returns>
		protected virtual async Task ApplyResponseCoreAsync()
        {
            await ApplyResponseGrantAsync();
            await ApplyResponseChallengeAsync();
        }

        protected abstract void ApplyResponseGrant();

        /// <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)
        /// </summary>
        /// <returns></returns>
        protected virtual Task ApplyResponseGrantAsync()
        {
            ApplyResponseGrant();
            return Task.FromResult(0);
        }

	 

        protected abstract void ApplyResponseChallenge();

        /// <summary>
        /// Override this method to deal with 401 challenge concerns, if an authentication scheme in question
        /// deals an authentication interaction as part of it's request flow. (like adding a response header, or
        /// changing the 401 result to 302 of a login page or external sign-in location.)
		/// 401 是 未授权 302 是重定向
        /// </summary>
        /// <returns></returns>
        protected virtual Task ApplyResponseChallengeAsync()
        {
            ApplyResponseChallenge();
            return Task.FromResult(0);
        }

		/// <summary>
		/// OAuthAuthenticationHandler 里会调用此方法
		/// </summary>
		/// <param name="properties"></param>
		protected void GenerateCorrelationId([NotNull] AuthenticationProperties properties)
        {
            string correlationKey = Constants.CorrelationPrefix + BaseOptions.AuthenticationType;

            var nonceBytes = new byte[32];
            CryptoRandom.GetBytes(nonceBytes);
            string correlationId = TextEncodings.Base64Url.Encode(nonceBytes);

            var cookieOptions = new CookieOptions
            {
                HttpOnly = true,
                Secure = Request.IsSecure
            };
			// 比如: .AspNet.Correlation.Cookies 
			properties.Dictionary[correlationKey] = correlationId;

            Response.Cookies.Append(correlationKey, correlationId, cookieOptions);
        }

        protected bool ValidateCorrelationId([NotNull] AuthenticationProperties properties, [NotNull] ILogger logger)
        {
            string correlationKey = Constants.CorrelationPrefix + BaseOptions.AuthenticationType;

            string correlationCookie = Request.Cookies[correlationKey];
            if (string.IsNullOrWhiteSpace(correlationCookie))
            {
                logger.WriteWarning("{0} cookie not found.", correlationKey);
                return false;
            }

            var cookieOptions = new CookieOptions
            {
                HttpOnly = true,
                Secure = Request.IsSecure
            };
            Response.Cookies.Delete(correlationKey, cookieOptions);

            string correlationExtra;
            if (!properties.Dictionary.TryGetValue(
                correlationKey,
                out correlationExtra))
            {
                logger.WriteWarning("{0} state property not found.", correlationKey);
                return false;
            }

            properties.Dictionary.Remove(correlationKey);

            if (!string.Equals(correlationCookie, correlationExtra, StringComparison.Ordinal))
            {
                logger.WriteWarning("{0} correlation cookie and state property mismatch.", correlationKey);
                return false;
            }

            return true;
        }

     

        private void UnregisterAuthenticationHandler()
        {
            var auth = Context.GetAuthentication();
            auth.Handler = PriorHandler;
        }
    }