装饰有AllowAnonymous的Endpoint仍然期待认证授权(net7.0)

问题描述 投票:0回答:1

第一次涉足 Web API 和 MVC 的世界。

这是我的

program.cs

namespace TelephoneInfoLookup;

using TelephoneInfoLookup.Middleware;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers()
    .AddJsonOptions(options =>
    {
        options.JsonSerializerOptions.DefaultIgnoreCondition =
            System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull;
    });

builder.Services.AddEndpointsApiExplorer()
    .AddSwaggerGen()
    .AddCors()
    .AddAuthentication("Basic");

var app = builder.Build();
if (app.Environment.IsDevelopment())
{
    app.UseSwagger()
        .UseDeveloperExceptionPage()
        .UseSwaggerUI();
}
else
    app.UseHsts();

app.UseHttpsRedirection()
    .UseMiddleware<BasicAuthMiddleware>(new BasicAuthenticationOptions
    {
        Name = "Bob",
        Password = "My Uncle"
    })
    .UseAuthorization();

app.MapControllers();

app.Run();

这是中间件:

namespace TelephoneInfoLookup.Middleware;

using System.Net;
using System.Security.Principal;

public class BasicAuthMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly BasicAuthenticationOptions _options;
        public BasicAuthMiddleware(RequestDelegate next, BasicAuthenticationOptions options)
        {
            _next = next;
            _options = options ?? throw new ArgumentException("User info can't be null");
        }
        public async Task Invoke(HttpContext context)
        {
            if (CheckIsValidRequest(context, out string username))
            {
                var identity = new GenericIdentity(username);
                var principle = new GenericPrincipal(identity, null);
                context.User = principle;
                await _next.Invoke(context);
            }
            else
            {
                context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
            }
        }
        private bool CheckIsValidRequest(HttpContext context, out string username)
        {
            var basicAuthHeader = GetBasicAuthenticationHeaderValue(context);
            username = basicAuthHeader.UserName;
            return basicAuthHeader.IsValidBasicAuthenticationHeaderValue &&
                   basicAuthHeader.UserName == _options.Name &&
                   basicAuthHeader.Password == _options.Password;
        }
        private BasicAuthenticationHeaderValue GetBasicAuthenticationHeaderValue(HttpContext context)
        {
            var basicAuthenticationHeader = context.Request.Headers["Authorization"]
                .FirstOrDefault(header => header.StartsWith("Basic", StringComparison.OrdinalIgnoreCase));
            var decodedHeader = new BasicAuthenticationHeaderValue(basicAuthenticationHeader);
            return decodedHeader;
        }
    }
    public class BasicAuthenticationOptions
    {
        public string Name { get; set; }
        public string Password { get; set; }
    }
    public class BasicAuthenticationHeaderValue
    {
        public BasicAuthenticationHeaderValue(string authenticationHeaderValue)
        {
            if (!string.IsNullOrWhiteSpace(authenticationHeaderValue))
            {
                _authenticationHeaderValue = authenticationHeaderValue;
                if (TryDecodeHeaderValue())
                {
                    ReadAuthenticationHeaderValue();
                }
            }
        }
        private readonly string _authenticationHeaderValue;
        private string[] _splitDecodedCredentials;
        public bool IsValidBasicAuthenticationHeaderValue { get; private set; }
        public string UserName { get; private set; }
        public string Password { get; private set; }
        private bool TryDecodeHeaderValue()
        {
            const int headerSchemeLength = 6;
            if (_authenticationHeaderValue.Length <= headerSchemeLength)
            {
                return false;
            }
            var encodedCredentials = _authenticationHeaderValue.Substring(headerSchemeLength);
            try
            {
                var decodedCredentials = Convert.FromBase64String(encodedCredentials);
                _splitDecodedCredentials = System.Text.Encoding.ASCII.GetString(decodedCredentials).Split(':');
                return true;
            }
            catch (FormatException)
            {
                return false;
            }
        }
        private void ReadAuthenticationHeaderValue()
        {
            IsValidBasicAuthenticationHeaderValue = _splitDecodedCredentials.Length == 2
                                                   && !string.IsNullOrWhiteSpace(_splitDecodedCredentials[0])
                                                   && !string.IsNullOrWhiteSpace(_splitDecodedCredentials[1]);
            if (IsValidBasicAuthenticationHeaderValue)
            {
                UserName = _splitDecodedCredentials[0];
                Password = _splitDecodedCredentials[1];
            }
        }
    }

这样我就可以确保只有密码为

Bob
的用户
My Uncle
可以使用这个端点:

namespace TelephoneInfoLookup.Controllers;

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("[Controller]")]
[Produces("application/json")]
[Authorize]
public class LookupController : ControllerBase
{

    private readonly ILogger<LookupController> _logger;

    public LookupController(ILogger<LookupController> logger)
    {
        _logger = logger;
    }

    [HttpGet]
    public async Task<Lookup> Get(string telephoneNumber, string fields)
    {
        var ret = await Lookup.LookupDetails(telephoneNumber, fields);
        return ret;
    }
}

然而,另一种我想保持开放的方法仍然要求我使用基本身份验证,每当我尝试调用它时都会收到 401:

namespace TelephoneInfoLookup.Controllers;

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

[AllowAnonymous]
[ApiController]
[Route("Simple")]
public class SimpleLookupController : ControllerBase
{

    private readonly ILogger<LookupController> _logger;

    public SimpleLookupController(ILogger<LookupController> logger)
    {
        _logger = logger;
    }

    [HttpGet]
    [AllowAnonymous]
    public async Task<string> GetExtension(string telephoneNumber)
    {
        var lookup = await Lookup.LookupDetails(telephoneNumber, null);
        var ret = lookup!.VIPExtension!;
        return string.IsNullOrEmpty(ret) ? "" : ret;
    }
}

我试过添加

app.UseHttpsRedirection()
    .UseMiddleware<BasicAuthMiddleware>(new BasicAuthenticationOptions
    {
        Name = "Bob",
        Password = "Your Uncle"
    })
    .UseAuthentication()
    .UseAuthorization();

但无济于事

我错过了什么傻事?

我怀疑是中间件中的某些东西需要更改,但是由于我对这一切完全不了解,所以我很困惑。

我确实花了一些时间来弄清楚AllowAnonymous 不使用 Custom AuthorizationAttribute 这会让我认为我可能需要使用一些东西

System.Web.Http.AuthorizeAttribute
但我有点迷失了我会如何使用它在我的代码中(好吧,不是我的代码,我发现其他人的代码让我的基本身份验证“有效”!)

c# asp.net-mvc
1个回答
0
投票

找到答案了!

我需要将以下内容添加到我的中间件中:

public async Task Invoke(HttpContext context)
{
    var endpoint = context.GetEndpoint();
    if (endpoint?.Metadata?.GetMetadata<IAllowAnonymous>() is object)
    {
        await _next(context);
        return;
    }

来源:https://www.stevejgordon.co.uk/anonymous-aware-middleware-with-endpoint-routing-and-healthchecks

© www.soinside.com 2019 - 2024. All rights reserved.