ASP.NET核心中的基本身份验证

A-S*_*ani 43 basic-authentication asp.net-core-mvc asp.net-core asp.net-core-1.0

如何在ASP.NET Core Web应用程序中实现具有自定义成员身份的基本身份验证?

笔记

  • 在MVC 5中,我使用了本文中的说明,该说明需要在中添加模块WebConfig.

  • 我仍在部署我的新MVC Core应用程序,IIS但这种方法似乎无法正常工作.

  • 我也不想使用内置的IIS支持基本身份验证,因为它使用Windows凭据.

blo*_*art 25

ASP.NET Security不会包含基本身份验证中间件,因为它存在潜在的不安全性和性能问题.

如果您需要基本身份验证中间件进行测试,请查看https://github.com/blowdart/idunno.Authentication

  • @blowdart 愿意详细说明“潜在的不安全和性能问题”吗?基于 https 的基本身份验证非常安全,并在生产中广泛使用(例如 stripe、mailchimp、aws 等)。“测试目的”只是错误的指导和不充分的背景。除非我们遗漏了 ASP.NET 安全本身的某些实施不善的地方。 (8认同)
  • 我们该怎么办的情况一样,如果我们试图写一个[用于Visual Studio团队服务Web挂机](https://www.visualstudio.com/en-us/docs/marketplace/integrate/service-hooks/services/ webhooks)只支持基本身份验证? (5认同)
  • 为什么仅“出于测试目的”?您为什么不能在生产中使用这种方法? (4认同)
  • 我们在[kukkimonsuta](https://github.com/Kukkimonsuta/)的一个项目中使用了[Odachi](https://github.com/Kukkimonsuta/Odachi)库,因为它有一些兼容性"问题" .它适用于我们需要的东西. (2认同)
  • 商定的基本身份验证很糟糕,但 Azure DevOps 服务挂钩似乎不支持其他任何内容。 (2认同)

Bru*_*cia 12

ASP.NET Core 2.0引入了对身份验证和身份的重大更改.

在1.x auth提供程序是通过中间件配置的(作为接受的答案的实现).在2.0它基于服务.

有关MS doc的详细信息:https: //docs.microsoft.com/en-us/aspnet/core/migration/1x-to-2x/identity-2x

我为ASP.NET Core 2.0编写了一个基本身份验证实现并发布到NuGet:https: //github.com/bruno-garcia/Bazinga.AspNetCore.Authentication.Basic


Dee*_*101 10

我对 ASP.NET Core 身份验证中间件设计感到失望。作为一个框架,它应该简化并导致更高的生产力,而这里的情况并非如此。

无论如何,一种简单而安全的方法是基于授权过滤器,例如IAsyncAuthorizationFilter。请注意,当 MVC 选择某个控制器操作并移动到过滤处理时,将在其他中间件之后执行授权过滤器。但是在过滤器中,首先执行授权过滤器(details)。

我只是想评论克莱斯对赫克托尔答案的评论,但不喜欢赫克托斯的例子抛出异常并且没有任何挑战机制,所以这里是一个有效的例子。

记住:

  1. 在生产中没有 HTTPS 的基本身份验证非常糟糕。确保您的 HTTPS 设置得到强化(例如禁用所有 SSL 和 TLS < 1.2 等)
  2. 今天,基本身份验证的大多数用途是公开受 API 密钥保护的 API(请参阅 Stripe.NET、Mailchimp 等)。使 curl 友好的 API 与服务器上的 HTTPS 设置一样安全。

考虑到这一点,不要购买任何围绕基本身份验证的 FUD。跳过像基本身份验证这样的基本内容是高意见而低实质的。您可以在此处的评论中看到围绕此设计的挫败感。

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));
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这些是支持类

    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);
        }
    }
Run Code Online (Sandbox Code Playgroud)


Jos*_*hee 8

.NET Core 中的超级简单基本身份验证:

1. 添加此实用方法:

static System.Text.Encoding ISO_8859_1_ENCODING = System.Text.Encoding.GetEncoding("ISO-8859-1");
public static (string, string) GetUsernameAndPasswordFromAuthorizeHeader(string authorizeHeader)
{
    if (authorizeHeader == null || !authorizeHeader.Contains("Basic ")) 
        return (null, null);
    
    string encodedUsernamePassword = authorizeHeader.Substring("Basic ".Length).Trim();
    string usernamePassword = ISO_8859_1_ENCODING.GetString(Convert.FromBase64String(encodedUsernamePassword));

    string username = usernamePassword.Split(':')[0];
    string password = usernamePassword.Split(':')[1];

    return (username, password);
}
Run Code Online (Sandbox Code Playgroud)

2. 更新控制器操作以从 Authorization 标头获取用户名和密码:

public async Task<IActionResult> Index([FromHeader]string Authorization)
{
    (string username, string password) = GetUsernameAndPasswordFromAuthorizeHeader(Authorization);

    // Now use username and password with whatever authentication process you want 

    return View();
}
Run Code Online (Sandbox Code Playgroud)

例子

此示例演示了如何将其与 ASP.NET Core Identity 结合使用。

public class HomeController : Controller
{
    private readonly UserManager<IdentityUser> _userManager;

    public HomeController(UserManager<IdentityUser> userManager)
    {
        _userManager = userManager;
    }

    [AllowAnonymous]
    public async Task<IActionResult> MyApiEndpoint([FromHeader]string Authorization)
    {
        (string username, string password) = GetUsernameAndPasswordFromAuthorizeHeader(Authorization);

        IdentityUser user = await _userManager.FindByNameAsync(username);
        bool successfulAuthentication = await _userManager.CheckPasswordAsync(user, password);

        if (successfulAuthentication)
            return Ok();
        else
            return Unauthorized();
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 将 `Constants.ISO_8859_1_ENCODING` 替换为 `static Encoding ISO_8859_1_ENCODING = System.Text.Encoding.GetEncoding("ISO-8859-1")` 以使其正常工作。 (2认同)

Héc*_*dez 6

我们使用ActionFilter为内部服务实现了摘要安全性:

public class DigestAuthenticationFilterAttribute : ActionFilterAttribute
{
    private const string AUTH_HEADER_NAME = "Authorization";
    private const string AUTH_METHOD_NAME = "Digest ";
    private AuthenticationSettings _settings;

    public DigestAuthenticationFilterAttribute(IOptions<AuthenticationSettings> settings)
    {
        _settings = settings.Value;
    }

    public override void OnActionExecuting(ActionExecutingContext context)
    {
        ValidateSecureChannel(context?.HttpContext?.Request);
        ValidateAuthenticationHeaders(context?.HttpContext?.Request);
        base.OnActionExecuting(context);
    }

    private void ValidateSecureChannel(HttpRequest request)
    {
        if (_settings.RequireSSL && !request.IsHttps)
        {
            throw new AuthenticationException("This service must be called using HTTPS");
        }
    }

    private void ValidateAuthenticationHeaders(HttpRequest request)
    {
        string authHeader = GetRequestAuthorizationHeaderValue(request);
        string digest = (authHeader != null && authHeader.StartsWith(AUTH_METHOD_NAME)) ? authHeader.Substring(AUTH_METHOD_NAME.Length) : null;
        if (string.IsNullOrEmpty(digest))
        {
            throw new AuthenticationException("You must send your credentials using Authorization header");
        }
        if (digest != CalculateSHA1($"{_settings.UserName}:{_settings.Password}"))
        {
            throw new AuthenticationException("Invalid credentials");
        }

    }

    private string GetRequestAuthorizationHeaderValue(HttpRequest request)
    {
        return request.Headers.Keys.Contains(AUTH_HEADER_NAME) ? request.Headers[AUTH_HEADER_NAME].First() : null;
    }

    public static string CalculateSHA1(string text)
    {
        var sha1 = System.Security.Cryptography.SHA1.Create();
        var hash = sha1.ComputeHash(Encoding.UTF8.GetBytes(text));
        return Convert.ToBase64String(hash);
    }
}
Run Code Online (Sandbox Code Playgroud)

之后,您可以使用Digest安全性注释要访问的控制器或方法:

[Route("api/xxxx")]
[ServiceFilter(typeof(DigestAuthenticationFilterAttribute))]
public class MyController : Controller
{
    [HttpGet]
    public string Get()
    {
        return "HELLO";
    }

}
Run Code Online (Sandbox Code Playgroud)

要实现Basic安全性,只需将DigestAuthenticationFilterAttribute更改为不使用SHA1,但直接对Authorization标头进行Base64解码.

  • 仅供参考.这种方法的缺点是这个过滤器(身份验证)发生得很晚 - 它在授权过滤器运行后运行,使授权过滤器无效. (4认同)

归档时间:

查看次数:

31196 次

最近记录:

6 年,7 月 前