ASP.NET Core 安全概述






4.92/5 (6投票s)
本文概述了 ASP.NET Core 的安全功能。
引言
在企业应用程序场景中,保护应用程序免受各种安全威胁至关重要。首先,提供安全的授权和身份验证是应用程序的主要部分。在本文中,我将分享 ASP.NET Core 中使用的安全功能的基础知识。
背景
在本节中,我想简要介绍客户端-服务器应用程序的基本安全类型。在客户端-服务器应用程序中,有两种安全类型非常重要:数据传输安全和数据访问安全。
1. 传输安全
理解传输安全的一个简单方法是使用客户和银行的场景。例如,客户是客户端/浏览器,银行是 Web 服务器。在这种情况下,很容易理解客户如何与银行进行现金交易。

HTTPS:例如 - 客户必须使用相同的交通工具(如上所述),但钱被安全地锁在袋子里,客户会用(银行提供的)钥匙锁好袋子,只有银行才能用银行的专用钥匙打开这个袋子。即使客户丢失了袋子,也没有人能打开或偷走袋子里的钱(即银行拥有打开锁好的袋子的专用钥匙)。
HTTP 如何工作?
谁负责安全交易?
客户端(Web 浏览器)会向服务器(Web 服务器)请求执行特定操作,服务器会响应客户端的请求。在这种情况下,双方都必须确保通信安全。因此,客户端和服务器都有责任提供安全保障。
如何进行安全交易?
只有通过 HTTPS(超文本传输协议安全)才能确保客户端到服务器以及服务器到客户端之间的通信安全。如前所述,客户用聚乙烯袋子运送钱是不安全的,因为任何人都可以看到你的钱,存在被盗的风险。但在 HTTPS 中,你用的是一个安全箱来运送钱 - 即使有人偷了你的包,他也打不开箱子。HTTPS 使用 SSL 证书来确保交易被安全加密。
SSL 如何工作?
http://www.jordansphere.co.uk/how-does-ssl-work/
HTTPS 中什么被保护了?
HTTP 消息示例
HTTPS 消息示例
谁负责客户端 SSL 的加密和解密?
在客户端,浏览器负责 SSL 证书的加密和解密。
浏览器使用 SSL 证书加密和解密数据,浏览器确保数据通过网络通道发送时是加密的。一旦服务器(例如 IIS、Apache)收到数据,服务器就有责任确保数据是加密的(安全的),并负责发送安全响应。
2. 应用程序安全
应用程序安全是指应用程序中可以使用哪些资源(授权)由谁使用应用程序(身份验证)来决定,提供应用程序安全既需要授权也需要身份验证。
- 用户识别 - 身份验证
- 为用户提供资源访问权限 - 授权
示例:考虑一个公共图书馆,用户可以通过提供身份信息进入公共图书馆并使用图书馆资源(报纸、书籍、视频、电脑等),仅仅是为了了解用户信息。提供身份证明是身份验证,而允许访问图书馆资源是授权。
ASP.NET Core 身份验证
ASP.NET Core 身份验证主要涉及三个部分:
AuthenticationMiddleware
AuthenticationSchemeOptions
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
将由 AuthenticationResult
和 AuthenticationTicket
支持。
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 中,完整的身份验证过程非常简单,易于提供自定义身份验证功能。