我对 ASP.NET Core 身份验证中间件设计感到失望。作为一个框架,它应该简化并带来更高的生产力,而这里并非如此。
无论如何,一种简单而安全的方法是基于授权过滤器,例如IAsyncAuthorizationFilter。请注意,授权过滤器将在其他中间件之后执行,当 MVC 选择某个控制器操作并移动到过滤器处理时。但在过滤器中,首先执行授权过滤器 (details)。
我只是想评论 Clays 对 Hector 的回答的评论,但不喜欢 Hectors 抛出异常且没有任何挑战机制的示例,所以这是一个可行的示例。
记住:
- 在生产中没有 HTTPS 的基本身份验证非常糟糕。确保您的 HTTPS 设置得到强化(例如,禁用所有 SSL 和 TLS
- 如今,基本身份验证的大多数用途是公开受 API 密钥保护的 API(请参阅 Stripe.NET、Mailchimp 等)。使 curl 友好的 API 与服务器上的 HTTPS 设置一样安全。
考虑到这一点,不要相信任何关于基本身份验证的 FUD。跳过像基本身份验证这样基本的东西是高意见和低实质内容。您可以在 cmets here 中看到围绕此设计的挫败感。
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
namespace BasicAuthFilterDemo
{
public class BasicAuthenticationFilterAttribute : Attribute, IAsyncAuthorizationFilter
{
public string Realm { get; set; }
public const string AuthTypeName = "Basic ";
private const string _authHeaderName = "Authorization";
public BasicAuthenticationFilterAttribute(string realm = null)
{
Realm = realm;
}
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
try
{
var request = context?.HttpContext?.Request;
var authHeader = request.Headers.Keys.Contains(_authHeaderName) ? request.Headers[_authHeaderName].First() : null;
string encodedAuth = (authHeader != null && authHeader.StartsWith(AuthTypeName)) ? authHeader.Substring(AuthTypeName.Length).Trim() : null;
if (string.IsNullOrEmpty(encodedAuth))
{
context.Result = new BasicAuthChallengeResult(Realm);
return;
}
var (username, password) = DecodeUserIdAndPassword(encodedAuth);
// Authenticate credentials against database
var db = (ApplicationDbContext)context.HttpContext.RequestServices.GetService(typeof(ApplicationDbContext));
var userManager = (UserManager<User>)context.HttpContext.RequestServices.GetService(typeof(UserManager<User>));
var founduser = await db.Users.Where(u => u.Email == username).FirstOrDefaultAsync();
if (!await userManager.CheckPasswordAsync(founduser, password))
{
// writing to the Result property aborts rest of the pipeline
// see https://docs.microsoft.com/en-us/aspnet/core/mvc/controllers/filters?view=aspnetcore-3.0#cancellation-and-short-circuiting
context.Result = new StatusCodeOnlyResult(StatusCodes.Status401Unauthorized);
}
// Populate user: adjust claims as needed
var claims = new[] { new Claim(ClaimTypes.Name, username, ClaimValueTypes.String, AuthTypeName) };
var principal = new ClaimsPrincipal(new ClaimsIdentity(claims, AuthTypeName));
context.HttpContext.User = principal;
}
catch
{
// log and reject
context.Result = new StatusCodeOnlyResult(StatusCodes.Status401Unauthorized);
}
}
private static (string userid, string password) DecodeUserIdAndPassword(string encodedAuth)
{
var userpass = Encoding.UTF8.GetString(Convert.FromBase64String(encodedAuth));
var separator = userpass.IndexOf(':');
if (separator == -1)
return (null, null);
return (userpass.Substring(0, separator), userpass.Substring(separator + 1));
}
}
}
这些是支持类
public class StatusCodeOnlyResult : ActionResult
{
protected int StatusCode;
public StatusCodeOnlyResult(int statusCode)
{
StatusCode = statusCode;
}
public override Task ExecuteResultAsync(ActionContext context)
{
context.HttpContext.Response.StatusCode = StatusCode;
return base.ExecuteResultAsync(context);
}
}
public class BasicAuthChallengeResult : StatusCodeOnlyResult
{
private string _realm;
public BasicAuthChallengeResult(string realm = "") : base(StatusCodes.Status401Unauthorized)
{
_realm = realm;
}
public override Task ExecuteResultAsync(ActionContext context)
{
context.HttpContext.Response.StatusCode = StatusCode;
context.HttpContext.Response.Headers.Add("WWW-Authenticate", $"{BasicAuthenticationFilterAttribute.AuthTypeName} Realm=\"{_realm}\"");
return base.ExecuteResultAsync(context);
}
}