it-swarm.dev

ASP.NET Core의 기본 인증

질문

ASP.NET Core 웹 응용 프로그램에서 사용자 지정 멤버 자격으로 기본 인증을 구현하려면 어떻게해야합니까?

노트

  • MVC 5에서는 WebConfig에 모듈을 추가해야하는이 article 의 지침을 사용하고있었습니다.

  • 여전히 새 MVC Coreapplication on IIS하지만이 방법은 작동하지 않는 것 같습니다.

  • 또한 Windows 인증을 사용하기 때문에 IIS 기본 인증 지원 내장)을 사용하고 싶지 않습니다.

40
A-Sharabiani

ASP.NET 보안에는 잠재적 인 불안정성 및 성능 문제로 인해 기본 인증 미들웨어가 포함되지 않습니다.

테스트 목적으로 기본 인증 미들웨어가 필요한 경우 https://github.com/blowdart/idunno.Authentication 을 참조하십시오.

24
blowdart

ASP.NET Core 2.0에는 인증 및 ID에 대한 주요 변경 사항이 도입되었습니다.

1.x 인증 제공자는 미들웨어를 통해 (허용 된 답변의 구현으로) 구성되었습니다. 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

12
Bruno Garcia

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

그런 다음 Digest 보안으로 액세스하려는 컨트롤러 또는 메소드에 주석을 달 수 있습니다.

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

}

기본 보안을 구현하려면 DigestAuthenticationFilterAttribute를 SHA1을 사용하지 않고 Authorization 헤더의 직접 Base64 디코딩으로 변경하십시오.

ASP.NET Core 인증 미들웨어 디자인에 실망했습니다. 프레임 워크로서 여기에는 해당되지 않는 단순화되고 생산성이 향상되어야합니다.

어쨌든 간단하지만 안전한 접근 방식은 인증 필터를 기반으로합니다. IAsyncAuthorizationFilter. MVC가 특정 컨트롤러 작업을 선택하고 필터 처리로 이동할 때 권한 부여 필터는 다른 미들웨어 후에 실행됩니다. 그러나 필터 내에서 권한 부여 필터가 먼저 실행됩니다 ( details ).

나는 단지 Hector의 대답에 대한 Clays의 의견에 대해 언급하려고했지만 Hectors의 예가 예외를 던지고 도전 메커니즘이없는 것을 좋아하지 않았으므로 여기에 실제적인 예가 있습니다.

명심하십시오 :

  1. 프로덕션 환경에서 HTTPS가없는 기본 인증은 매우 나쁩니다. HTTPS 설정이 강화되었는지 확인하십시오 (예 : 모든 SSL 및 TLS <1.2 등 비활성화)
  2. 오늘날 대부분의 기본 인증 사용은 API 키로 보호되는 API를 공개 할 때 사용됩니다 (Stripe.NET, Mailchimp 등 참조). 서버의 HTTPS 설정만큼 안전한 컬 친화적 API를 만듭니다.

이를 염두에두고 기본 인증과 관련된 FUD를 구매하지 마십시오. 기본 인증만큼 기본적인 것을 건너 뛰는 것은 의견이 많고 실질이 낮습니다. 주석 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);
        }
    }
0
DeepSpace101