ASP.NET Web API 中的基本身份验证
作者:Mike Wasson
基本身份验证在 RFC 2617、HTTP 身份验证:基本和摘要访问身份验证中定义。
缺点
- 用户凭据在请求中发送。
- 凭据以纯文本形式发送。
- 凭据随每个请求一起发送。
- 除非结束浏览器会话,否则无法注销。
- 易受到跨站点请求伪造 (CSRF) ;需要反 CSRF 措施。
优势
- Internet 标准。
- 受所有主要浏览器支持。
- 相对简单的协议。
基本身份验证的工作原理如下:
- 如果请求需要身份验证,服务器将返回 401 (未授权) 。 响应包括WWW-Authenticate标头,指示服务器支持基本身份验证。
- 客户端发送另一个请求,并在 Authorization 标头中包含客户端凭据。 凭据的格式设置为字符串“name:password”,base64 编码。 凭据未加密。
基本身份验证在“领域”的上下文中执行。服务器在 WWW-Authenticate 标头中包含领域的名称。 用户的凭据在该领域有效。 领域的确切范围由服务器定义。 例如,可以定义多个领域来对资源进行分区。
由于凭据未加密发送,因此基本身份验证仅通过 HTTPS 进行安全保护。 请参阅 在 Web API 中使用 SSL。
基本身份验证也容易受到 CSRF 攻击。 用户输入凭据后,浏览器会在会话期间,根据后续请求自动将凭据发送到同一域。 这包括 AJAX 请求。 请参阅 防止跨站点请求伪造 (CSRF) 攻击。
使用 IIS 进行基本身份验证
IIS 支持基本身份验证,但有一个警告:用户根据其 Windows 凭据进行身份验证。 这意味着用户必须在服务器的域中拥有帐户。 对于面向公众的网站,通常需要针对 ASP.NET 成员资格提供程序进行身份验证。
若要使用 IIS 启用基本身份验证,请在 ASP.NET 项目的Web.config中将身份验证模式设置为“Windows”:
<system.web>
<authentication mode="Windows" />
</system.web>
在此模式下,IIS 使用 Windows 凭据进行身份验证。 此外,必须在 IIS 中启用基本身份验证。 在 IIS 管理器中,转到“功能视图”,选择“身份验证”,然后启用“基本身份验证”。
在 Web API 项目中,为需要身份验证的任何控制器操作添加 [Authorize]
属性。
客户端通过在请求中设置 Authorization 标头对自身进行身份验证。 浏览器客户端自动执行此步骤。 非浏览器客户端将需要设置 标头。
使用自定义成员身份进行基本身份验证
如前所述,内置于 IIS 的基本身份验证使用 Windows 凭据。 这意味着需要在托管服务器上为用户创建帐户。 但对于 Internet 应用程序,用户帐户通常存储在外部数据库中。
以下代码如何执行基本身份验证的 HTTP 模块。 可以通过替换 CheckPassword
方法轻松插入 ASP.NET 成员资格提供程序,该方法在本示例中是一个虚拟方法。
在 Web API 2 中,应考虑编写 身份验证筛选器 或 OWIN 中间件,而不是 HTTP 模块。
namespace WebHostBasicAuth.Modules
{
public class BasicAuthHttpModule : IHttpModule
{
private const string Realm = "My Realm";
public void Init(HttpApplication context)
{
// Register event handlers
context.AuthenticateRequest += OnApplicationAuthenticateRequest;
context.EndRequest += OnApplicationEndRequest;
}
private static void SetPrincipal(IPrincipal principal)
{
Thread.CurrentPrincipal = principal;
if (HttpContext.Current != null)
{
HttpContext.Current.User = principal;
}
}
// TODO: Here is where you would validate the username and password.
private static bool CheckPassword(string username, string password)
{
return username == "user" && password == "password";
}
private static void AuthenticateUser(string credentials)
{
try
{
var encoding = Encoding.GetEncoding("iso-8859-1");
credentials = encoding.GetString(Convert.FromBase64String(credentials));
int separator = credentials.IndexOf(':');
string name = credentials.Substring(0, separator);
string password = credentials.Substring(separator + 1);
if (CheckPassword(name, password))
{
var identity = new GenericIdentity(name);
SetPrincipal(new GenericPrincipal(identity, null));
}
else
{
// Invalid username or password.
HttpContext.Current.Response.StatusCode = 401;
}
}
catch (FormatException)
{
// Credentials were not formatted correctly.
HttpContext.Current.Response.StatusCode = 401;
}
}
private static void OnApplicationAuthenticateRequest(object sender, EventArgs e)
{
var request = HttpContext.Current.Request;
var authHeader = request.Headers["Authorization"];
if (authHeader != null)
{
var authHeaderVal = AuthenticationHeaderValue.Parse(authHeader);
// RFC 2617 sec 1.2, "scheme" name is case-insensitive
if (authHeaderVal.Scheme.Equals("basic",
StringComparison.OrdinalIgnoreCase) &&
authHeaderVal.Parameter != null)
{
AuthenticateUser(authHeaderVal.Parameter);
}
}
}
// If the request was unauthorized, add the WWW-Authenticate header
// to the response.
private static void OnApplicationEndRequest(object sender, EventArgs e)
{
var response = HttpContext.Current.Response;
if (response.StatusCode == 401)
{
response.Headers.Add("WWW-Authenticate",
string.Format("Basic realm=\"{0}\"", Realm));
}
}
public void Dispose()
{
}
}
}
若要启用 HTTP 模块,请将以下内容添加到 system.webServer 节中的 web.config 文件:
<system.webServer>
<modules>
<add name="BasicAuthHttpModule"
type="WebHostBasicAuth.Modules.BasicAuthHttpModule, YourAssemblyName"/>
</modules>
将“YourAssemblyName”替换为程序集的名称, (不包括“dll”扩展) 。
应禁用其他身份验证方案,例如窗体或 Windows 身份验证。