基本認証で動作していたASP.NET Core 2.2 Web APIがあります。これまでのところ問題なく動作し、問題はありません。コントローラーの1つで、1つのアクションメソッドが[AllowAnonymous]
を使用して、通常どおりユーザーログインを行います。
[Produces("application/json")]
[Route("user")]
[AllowAnonymous]
[ApiController]
public class LoginController : ControllerBase
{
private readonly IConfiguration _configuration;
private readonly IMessagingService _messageService;
private readonly IBasicAuthenticationService _basicAuthenticationService;
private readonly string PWAPIBaseUrl;
public LoginController(IConfiguration configuration, ILogger<LoginController> logger, IMessagingService messagingService, IBasicAuthenticationService authenticationService)
{
_configuration = configuration;
_logger = logger;
_messageService = messagingService;
_basicAuthenticationService = authenticationService;
}
[HttpGet]
[AllowAnonymous]
[Route("login/{username}/{clientID}")]
public async Task<IActionResult> UserLogin(string username, string clientID)
{
// Check the Credentials Manually
string failReason = "";
if (!CheckCredentials(out failReason))
{
return StatusCode(StatusCodes.Status403Forbidden, userInfo);
}
// Load the Roles and UI Preferences ...
}
}
.NET Core 2.2の終わりが近づいているので、.NET Core 3.1へのアップグレードを試みて 公式の移行ガイド に従い、必要な 変更 を作成しました。アプリケーションはスムーズに起動しましたが、アップグレードを禁止するバグの問題が1つあります。
上記のコントローラーでは、[AllowAnonymous]は無視されず、認証が評価されてエラーでスローされます。しかし、後にLoginメソッドが実行されます。これにより、すべての依存アプリケーションでログインが中断します。 this 、 this および this のようなStackoverflowからの提案をすべて試しました。
基本認証ハンドラー:
public class BasicAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
private readonly ILogger<BasicAuthenticationHandler> _logger = null;
private readonly IBasicAuthenticationService _basicAuthenticationService;
public BasicAuthenticationHandler(
IOptionsMonitor<AuthenticationSchemeOptions> options,
UrlEncoder encoder,
ILoggerFactory loggerFactory,
ISystemClock clock,
IBasicAuthenticationService authenticationService)
: base(options, loggerFactory, encoder, clock)
{
_logger = loggerFactory.CreateLogger<BasicAuthenticationHandler>();
_basicAuthenticationService = authenticationService;
}
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
var config = Util.GetConfig();
if (!Request.Headers.ContainsKey("Authorization"))
{
_logger.LogError("No authorization credentials");
return AuthenticateResult.NoResult();
}
if (!Request.Headers.ContainsKey("ClientID"))
{
_logger.LogError("Missing header client token");
return AuthenticateResult.Fail("Missing header client token");
}
var authHeader = AuthenticationHeaderValue.Parse(Request.Headers["Authorization"]);
if (authHeader.Scheme != "Basic")
{
_logger.LogError("Authentication scheme not recognized");
return AuthenticateResult.Fail("Authentication scheme not recognized");
}
var credentialBytes = Convert.FromBase64String(authHeader.Parameter);
var credentials = Encoding.UTF8.GetString(credentialBytes).Split(':');
var username = credentials[0];
var password = credentials[1];
string fullname = "";
string failReason = "";
bool t = false;
IPrincipal principal = null;
// Do Business Validation against the DB
if (!t) // login failed
{
byte[] bEncodedResponse = Encoding.UTF8.GetBytes(failReason);
await Context.Response.Body.WriteAsync(bEncodedResponse, 0, bEncodedResponse.Length);
return AuthenticateResult.Fail(failReason);
}
else
{
var claims = new[]
{
new Claim(ClaimTypes.NameIdentifier, username),
new Claim(ClaimTypes.Name, fullname),
};
var identity = new ClaimsIdentity(claims, Scheme.Name);
principal = principal==null?new ClaimsPrincipal(identity): principal;
var ticket = new AuthenticationTicket(principal as ClaimsPrincipal, Scheme.Name);
return AuthenticateResult.Success(ticket);
}
}
}
Startup.cs
public class Startup
{
public Startup(IWebHostEnvironment environment, IConfiguration configuration, ILoggerFactory loggerFactory)
{
Environment = environment;
Configuration = configuration;
LoggerFactory = loggerFactory;
}
public IConfiguration Configuration { get; }
public ILoggerFactory LoggerFactory { get; }
public IWebHostEnvironment Environment { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication("BasicAuthentication")
.AddScheme<AuthenticationSchemeOptions, BasicAuthenticationHandler>("BasicAuthentication", null);
// Adding the Configuration Options -- Extension Methods to Inject Configuration as IOption POCOs
services.ConfigureAPIOptions(Configuration);
// configure DI for application services -- Other DI Objects
services.ConfigureDependencies(Configuration, LoggerFactory);
Common.APIConfiguration.Current = Configuration;
services.AddControllers();
services.AddAuthorization();
if (Environment.IsDevelopment())
{
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Microsoft.OpenApi.Models.OpenApiInfo { Title = "My Materials API", Version = "v1" });
});
}
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseRouting();
if (env.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My Materials API v1");
c.RoutePrefix = string.Empty;
});
}
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
私はまだ私が間違ったことに無知であり、ASP.NET Core 3.1で何かが欠けている可能性があります。これを機能させるために私を助けてください。前もって感謝します。
EDIT 1:
ServiceExtensions.cs
public static class ServiceExtensions
{
public static void ConfigureAPIOptions(this IServiceCollection services, IConfiguration configuration)
{
services.AddOptions();
services.Configure<DataSetting>(configuration.GetSection("DataSettings"));
services.Configure<UrlSetting>(configuration.GetSection("UrlSettings"));
services.Configure<SiteSettings>(configuration.GetSection("SiteSettings"));
}
public static void ConfigureDependencies(this IServiceCollection services, IConfiguration configuration, ILoggerFactory loggerFactory)
{
services.AddSingleton<IConfiguration>(configuration);
services.AddScoped<IBasicAuthenticationService, BasicAuthenticationService>();
services.AddScoped<IMessagingService>(s => new MessagingServices(configuration, loggerFactory.CreateLogger<MessagingServices>()));
services.AddHostedService<TimedHostedService>();
}
}
DIが不可能な構成にアクセスするための小さなクラッジ。
public static class APIConfiguration
{
public static IConfiguration Current { get; set; }
}
私はこれを試しました、そしてそれは本当に私を助けます。
private static bool HasAllowAnonymous(AuthorizationFilterContext context)
{
var filters = context.Filters;
for (var i = 0; i < filters.Count; i++)
{
if (filters[i] is IAllowAnonymousFilter)
{
return true;
}
}
// When doing endpoint routing, MVC does not add AllowAnonymousFilters for AllowAnonymousAttributes that
// were discovered on controllers and actions. To maintain compat with 2.x,
// we'll check for the presence of IAllowAnonymous in endpoint metadata.
var endpoint = context.HttpContext.GetEndpoint();
if (endpoint?.Metadata?.GetMetadata<IAllowAnonymous>() != null)
{
return true;
}
return false;
}