65.9K
CodeProject 正在变化。 阅读更多。
Home

ASP.NET Core 安全概述

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.92/5 (6投票s)

2018年2月7日

CPOL

5分钟阅读

viewsIcon

23099

本文概述了 ASP.NET Core 的安全功能。

引言

在企业应用程序场景中,保护应用程序免受各种安全威胁至关重要。首先,提供安全的授权和身份验证是应用程序的主要部分。在本文中,我将分享 ASP.NET Core 中使用的安全功能的基础知识。

背景

在本节中,我想简要介绍客户端-服务器应用程序的基本安全类型。在客户端-服务器应用程序中,有两种安全类型非常重要:数据传输安全和数据访问安全。

1. 传输安全

理解传输安全的一个简单方法是使用客户和银行的场景。例如,客户是客户端/浏览器,银行是 Web 服务器。在这种情况下,很容易理解客户如何与银行进行现金交易。

HTTP:例如 - 假设客户带着钱去银行,客户会使用不同的交通工具,例如公共汽车、火车和汽车前往银行,并把钱放在透明的聚乙烯袋里。客户会使用许多交通工具,但把钱放在聚乙烯袋里是有风险的(任何人都可以看到并从你的包里偷钱,客户的包没有提供任何安全保障)。

HTTPS:例如 - 客户必须使用相同的交通工具(如上所述),但钱被安全地锁在袋子里,客户会用(银行提供的)钥匙锁好袋子,只有银行才能用银行的专用钥匙打开这个袋子。即使客户丢失了袋子,也没有人能打开或偷走袋子里的钱(即银行拥有打开锁好的袋子的专用钥匙)。

HTTP 如何工作?

谁负责安全交易?

客户端(Web 浏览器)会向服务器(Web 服务器)请求执行特定操作,服务器会响应客户端的请求。在这种情况下,双方都必须确保通信安全。因此,客户端和服务器都有责任提供安全保障。

如何进行安全交易?

只有通过 HTTPS(超文本传输协议安全)才能确保客户端到服务器以及服务器到客户端之间的通信安全。如前所述,客户用聚乙烯袋子运送钱是不安全的,因为任何人都可以看到你的钱,存在被盗的风险。但在 HTTPS 中,你用的是一个安全箱来运送钱 - 即使有人偷了你的包,他也打不开箱子。HTTPS 使用 SSL 证书来确保交易被安全加密。

SSL 如何工作?

Related image

http://www.jordansphere.co.uk/how-does-ssl-work/

HTTPS 中什么被保护了?

HTTP 消息示例

HTTPS 消息示例

谁负责客户端 SSL 的加密和解密?

在客户端,浏览器负责 SSL 证书的加密和解密。

浏览器使用 SSL 证书加密和解密数据,浏览器确保数据通过网络通道发送时是加密的。一旦服务器(例如 IIS、Apache)收到数据,服务器就有责任确保数据是加密的(安全的),并负责发送安全响应。

2. 应用程序安全

应用程序安全是指应用程序中可以使用哪些资源(授权)由谁使用应用程序(身份验证)来决定,提供应用程序安全既需要授权也需要身份验证。

  1. 用户识别 - 身份验证
  2. 为用户提供资源访问权限 - 授权

示例:考虑一个公共图书馆,用户可以通过提供身份信息进入公共图书馆并使用图书馆资源(报纸、书籍、视频、电脑等),仅仅是为了了解用户信息。提供身份证明是身份验证,而允许访问图书馆资源是授权。

ASP.NET Core 身份验证

ASP.NET Core 身份验证主要涉及三个部分:

  1. AuthenticationMiddleware
  2. AuthenticationSchemeOptions
  3. AuthenticationHandler

AuthenticationMiddleware 会拦截管道并对请求进行身份验证。要启用 AuthenticationMiddleware,请在 Startup 类中使用 UseAuthentication() 函数。

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.UseAuthentication();
}

这将把 AuthenticationMiddleware 添加到指定的 ApplicationBuilder 中。默认生成器在应用程序初始化时创建。

 public class Program
    {
        public static void Main(string[] args)
        {
            BuildWebHost(args).Run();
        }

        public static IWebHost BuildWebHost(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .UseStartup<Startup>()
                .Build();
    }

app.UseAuthentication(); 将创建新的 AuthenticationMiddleware 中间件。AuthenticationMiddleware 在管道的早期处理请求并验证身份验证。

关于此功能要记住的一点是,除非提供授权,否则此中间件身份验证拦截器不会限制对任何资源的访问。

AuthenticationSchemeOptions 将验证方案提供的不同选项,它将由 AuthenticationHandler 使用。在自定义身份验证中,可以派生此类以根据要求提供更多选项来验证不同的参数。

 /// <summary>
    /// Contains the options used by the <see cref="AuthenticationHandler{T}"/>.
    /// </summary>
    public class AuthenticationSchemeOptions
    {
        /// <summary>
        /// Check that the options are valid. Should throw an exception if things are not ok.
        /// </summary>
        public virtual void Validate() { }

        /// <summary>
        /// Checks that the options are valid for a specific scheme
        /// </summary>
        /// <param name="scheme">The scheme being validated.</param>
        public virtual void Validate(string scheme)
            => Validate();

        /// <summary>
        /// Gets or sets the issuer that should be used for any claims that are created
        /// </summary>
        public string ClaimsIssuer { get; set; }

        /// <summary>
        /// Instance used for events
        /// </summary>
        public object Events { get; set; }

        /// <summary>
        /// If set, will be used as the service type to get the Events instance instead of the property.
        /// </summary>
        public Type EventsType { get; set; }
    }

下面的类 CookieAuthenticationOptions 派生自 AuthenticationSchemeOptions,并提供了许多属性和事件。

    /// <summary>
    /// Configuration options for <see cref="CookieAuthenticationOptions"/>.
    /// </summary>
    public class CookieAuthenticationOptions : AuthenticationSchemeOptions
    {
        public CookieAuthenticationOptions()
        {
            ExpireTimeSpan = TimeSpan.FromDays(14);
            ReturnUrlParameter = CookieAuthenticationDefaults.ReturnUrlParameter;
            SlidingExpiration = true;
            Events = new CookieAuthenticationEvents();
        }
        public CookieBuilder Cookie
        {
            get => _cookieBuilder;
            set => _cookieBuilder = value ?? throw new ArgumentNullException(nameof(value));
        }
        public IDataProtectionProvider DataProtectionProvider { get; set; }
        public bool SlidingExpiration { get; set; }
        public PathString LoginPath { get; set; }
        public PathString LogoutPath { get; set; }
        
        ...
    }

ConfigureServices 中调用 AddCookie 方法以提供所需的选项进行验证。

public void ConfigureServices(IServiceCollection services)
{

services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) 
    .AddCookie(options => 
        { 
            options.LoginPath = "/Account/LogIn"; 
            options.LogoutPath = "/Account/LogOff"; 
        });
}

AuthenticationHandler 中根据指定参数进行验证或采取相应操作。

AuthenticationHandler 将执行核心工作,它将执行实际的身份验证。AuthenticationHandler 根据提供的 AuthenticationSchemeOptions 帮助执行我们想要做的任何事情。'HandleAuthenticateAsync' 方法将完成身份验证请求的所有工作。

protected override async Task<AuthenticateResult> HandleAuthenticateAsync()

AuthenticationHandler 将由 AuthenticationResultAuthenticationTicket 支持。

AuthenticationResult - 这是一个简单的类,包含 Authenticate 调用的结果。

    /// <summary>
    /// Contains the result of an Authenticate call
    /// </summary>
    public class AuthenticateResult
    {
        ...

        /// <summary>
        /// The authentication ticket.
        /// </summary>
        public AuthenticationTicket Ticket { get; protected set; }
        /// <summary>
        /// Indicates that authentication was successful.
        /// </summary>
        /// <param name="ticket">The ticket representing the authentication result.</param>
        /// <returns>The result.</returns>
        public static AuthenticateResult Success(AuthenticationTicket ticket)
        {
            if (ticket == null)
            {
                throw new ArgumentNullException(nameof(ticket));
            }
            return new AuthenticateResult() { Ticket = ticket, Properties = ticket.Properties };
        }
        /// <summary>
        /// Indicates that there was a failure during authentication.
        /// </summary>
        /// <param name="failure">The failure exception.</param>
        /// <returns>The result.</returns>
        public static AuthenticateResult Fail(Exception failure)
        {
            return new AuthenticateResult() { Failure = failure };
        }
        /// <summary>
        /// Indicates that there was no information returned for this authentication scheme.
        /// </summary>
        /// <returns>The result.</returns>
        public static AuthenticateResult NoResult()
        {
            return new AuthenticateResult() { None = true };
        }
        ...
    }

HandleAuthenticateAsync 将返回 AuthenticationResult 对象 - 此结果将指示身份验证结果。此类由 AuthenticationTicket 支持。

AuthenticationTicket AuthenticationResult 对象的成功结果。没有 AuthenticationTicket 身份验证就无法成功。

    /// <summary>
    /// Contains user identity information as well as additional authentication state.
    /// </summary>
    public class AuthenticationTicket
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="AuthenticationTicket"/> class
        /// </summary>
        /// <param name="principal">the <see cref="ClaimsPrincipal"/> 
        /// that represents the authenticated user.</param>
        /// <param name="properties">additional properties that can be consumed 
        /// by the user or runtime.</param>
        /// <param name="authenticationScheme">the authentication middleware 
        /// that was responsible for this ticket.</param>
        public AuthenticationTicket(ClaimsPrincipal principal, 
               AuthenticationProperties properties, string authenticationScheme)
        {
            if (principal == null)
            {
                throw new ArgumentNullException(nameof(principal));
            }

            AuthenticationScheme = authenticationScheme;
            Principal = principal;
            Properties = properties ?? new AuthenticationProperties();
        }
        ...
    }

可以派生 AuthenticationHandler 并根据不同的要求验证不同的参数,我们以 CookieAuthenticationHandler 为例。

        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);
            }

            return AuthenticateResult.Success(new AuthenticationTicket
                           (context.Principal, context.Properties, Scheme.Name));
        }

此处理程序将调用 EnsureCookieTicket() 并根据条件检查返回 AuthenticateResult

        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);
        }

在 Cookie 身份验证中,每个请求都必须经过 CookieAuthenticationHandler 并验证 Cookie 数据,AuthenticateResult 将指示 Cookie 验证结果。

在 ASP.NET Core 中,完整的身份验证过程非常简单,易于提供自定义身份验证功能。

© . All rights reserved.