第一次涉足 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
但我有点迷失了我会如何使用它在我的代码中(好吧,不是我的代码,我发现其他人的代码让我的基本身份验证“有效”!)
找到答案了!
我需要将以下内容添加到我的中间件中:
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