ОБНОВЛЕНИЕ: если я изменяю return new OkObjectResult (token); вернуть Ok («тест»); тогда он не пустой.
Чтобы использовать API, необходимо вызвать AuthenticateAsyn c, который создает и возвращает токен.
[AllowAnonymous]
[HttpPost]
public async Task<IActionResult> AuthenticateAsync([FromBody]string apiKey)
{
return await StopwatchLogger.LogExecutionTime($"Authenticate user: {apiKey}", async () =>
{
EnsureValidLoginRequest(apiKey);
//Validate user credentials
AuthenticationInfo authenticationInfo = await _authenticationService.AuthenticateUser(apiKey);
if (!authenticationInfo.IsAuthenticated)
{
throw new RestApiExceptionNoLogging(ErrorCode.AuthenticationInvalidApiKey, "");
}
//Login current user and get the created token with claims identifying current user
Token token = _authenticationService.LoginApiUser(HttpContext, authenticationInfo.SystemUser);
return new OkObjectResult(token);
});
}
Токен создан правильно и из чего Я могу сказать, что все так же, как и раньше, за исключением того, что тело ответа становится пустым. Не выдается никаких ошибок.
РЕДАКТИРОВАТЬ: Добавление startup.cs
public class Startup
{
private const string LogText = "An error occurred during startup.";
private readonly List<Exception> _exceptions = new List<Exception>();
private IWebHostEnvironment _HostingEnvironment;
public IConfiguration Configuration { get; }
public Startup(IConfiguration configuration, IWebHostEnvironment env)
{
Configuration = configuration;
_HostingEnvironment = env;
}
/// <summary>
/// This method gets called by the runtime. Use this method to add services to the container.
/// </summary>
/// <param name="services"></param>
/// <remarks>Is called before Configure</remarks>
public void ConfigureServices(IServiceCollection services)
{
try
{
if (_exceptions.Any())
{
return;
}
// Add framework services. See configuration class (implementing IConfigureOptions<MvcOptions>) for specific implementation details.
services.AddApiVersioning(o =>
{
o.ReportApiVersions = true;
o.AssumeDefaultVersionWhenUnspecified = true;
o.DefaultApiVersion = new ApiVersion(1, 0);
});
// Adds services required for using options.
services.AddOptions();
services.AddLogging(logging =>
{
logging.AddConsole();
logging.AddDebug();
});
//Add scoped services (New service for every request)
services.AddScoped<IJwtBearerService, JwtBearerService>();
services.AddScoped<IApiAuthService, ApiAuthService>();
services.AddScoped<ILoggingService, LoggingService>();
services.AddScoped<IResponseDatabaseLogger, ResponseDatabaseLogger>();
services.AddScoped<IRequestDatabaseLogger, RequestDatabaseLogger>();
services.AddScoped<IAuthService, AuthService>();
services.AddScoped<IOrderService, OrderService>();
services.AddScoped<IApiOrderService, ApiOrderService>();
services.AddScoped<IHaikomService, HaikomService>();
services.AddDbContext<LoggingContext>(options => options.UseSqlServer(connection));
services.AddDbContext<OrderContext>(options => options.UseSqlServer(connection));
services.AddDbContext<AuthContext>(options => options.UseSqlServer(connection));
////Add application setting as options to enable dependency injection for settings
services.Configure<Mediacreator.RestApi.Models.AuthenticationModel.Options.AuthenticationOptions>(options =>
{
// Get the setting from environment variable or user secret.
// For more info: https://blogcarlosperez.com/2016/05/24/user-secrets/ eller http://asp.net-hacker.rocks/2016/07/11/user-secrets-in-aspnetcore.html
options.JwtEncryptionSigningSecret = Configuration["Tokens:Key"];
options.Audience = Configuration["Tokens:Issuer"];
options.Issuer = Configuration["Tokens:Issuer"];
});
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddSingleton<IResponseFileLogger, ResponseFileLogger>();
services.AddSingleton<IRequestFileLogger, RequestFileLogger>();
var sp = services.BuildServiceProvider();
// Resolve the services from the service provider
var jwtBearerService = sp.GetService<IJwtBearerService>();
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options =>
{
options.TokenValidationParameters = jwtBearerService.CreateTokenValidationParameters(Configuration["Tokens:Key"]);
});
services.Configure<IISOptions>(options =>
{
options.ForwardClientCertificate = false;
options.AutomaticAuthentication = false;
});
services.AddMvc(options =>
{
//The services should only support JSON as output format for avoiding supporting for example XML
//By specifying this property, the client will get a 406 error message if the accept header is for example "Accept: application/xml".
options.ReturnHttpNotAcceptable = true;
options.EnableEndpointRouting = false;
//Enable events executed before, and after an action has been executed.
options.Filters.Add(typeof(ActionFilter));
//Make all controllers be protected by authorization
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
});
}
catch (Exception ex)
{
_exceptions.Add(ex);
}
}
/// <summary>
/// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
/// </summary>
/// <param name="app"></param>
/// <param name="env"></param>
/// <param name="loggerFactory"></param>
/// <remarks>Is called after ConfigureServices</remarks>
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory)
{
ILogger logger = null;
try
{
NLog.LogManager.LoadConfiguration("NLog.config");
loggerFactory.EnsureApiLoggersExists();
logger = loggerFactory.CreateLogger(this.GetType());
logger.LogInformation($"Current environment: {env.EnvironmentName}");
//Check if any exceptions occurred during startup
if (_exceptions.Any())
{
//Terminate the pipeline and return a response to the client
app.Run(async httpContext =>
{
await ExceptionHandler.HandleExceptionWriteErrorResponse(logger, httpContext, _exceptions.First(), LogText);
});
}
//Important to add this middleware first as exceptions in other middleware are also to be caught
app.UseCustomExceptionHandler();
//Important to add this middleware before UseMvc is added as the middleware component otherwise may not be called for all requests.
app.UseRequestLogger();
app.UseResponseLogger();
app.UseAuthentication();
app.UseMvcWithDefaultRoute();
}
catch (Exception ex)
{
//Terminate the pipeline and return a response to the client
app.Run(async httpContext =>
{
await ExceptionHandler.HandleExceptionWriteErrorResponse(logger, httpContext, ex, LogText);
});
}
}
}
Мне пришлось изменить IHostingEnvironment на IWebHostEnvironment. В журналирование были внесены некоторые изменения, мне пришлось добавить options.EnableEndpointRouting = false для параметров MVC. Было еще несколько вещей, которые я не могу вспомнить из верхней части головы.
РЕДАКТИРОВАТЬ 2, добавив запись ответа:
public class ResponseLogger : MiddlewareBase
{
private readonly IResponseFileLogger _IResponseLogger;
public ResponseLogger(RequestDelegate nextRequestDelegate, ILoggerFactory loggerFactory, IResponseFileLogger respLogger) : base(nextRequestDelegate, loggerFactory)
{
_IResponseLogger = respLogger;
}
public async Task InvokeAsync(HttpContext context)
{
//Adds an identifier to the response headers. The identifier is used for connecting a response to log posts for corresponding requests and responses.
context.AddTraceIdentifierToResponseHeaders();
//The original response body needs to be stored locally as it can not be rewind after reading and logging it.
//http://stackoverflow.com/questions/37855384/log-httpresponse-body-for-asp-net-core-1-0-rest-api/38275942#38275942
using (var responseBodyBufferStream = new MemoryStream())
{
//Store the original body stream in a local variable
var responseBodyStream = context.Response.Body;
try
{
//Replace the context response with the newly created buffer as the original stream is not readable.
context.Response.Body = responseBodyBufferStream;
//Invoke the rest of the pipeline
await InvokeNextDelegate(context);
//Reset the buffer so the content can be read
responseBodyBufferStream.Seek(0, SeekOrigin.Begin);
//Create a stream reader to be able to read the response
using (var bufferStreamReader = new StreamReader(responseBodyBufferStream))
{
//Read the body from the stream
string responseBody = await bufferStreamReader.ReadToEndAsync();
//Reset the buffer
responseBodyBufferStream.Seek(0, SeekOrigin.Begin);
//Copy the content to the original stream and put it back
await responseBodyBufferStream.CopyToAsync(responseBodyStream);
//var responseFileLogger = new ResponseFileLogger(LoggerFactory, context, responseBody);
_IResponseLogger.Log(context, responseBody);
}
}
finally
{
//Ensure original body stream is is written back to the response body even if an exception occurs in another middleware.
context.Response.Body = responseBodyStream;
}
}
}
}
Я попытался закомментировать зашифрованный код ответа чтобы увидеть, не является ли это частью проблемы, но, кажется, не имеет значения из того, что я могу сказать.
РЕДАКТИРОВАТЬ 3, добавив вывод из запроса:
info: Microsoft.AspNetCore.Hosting.Diagnostics[1]
Request starting HTTP/1.1 POST http://localhost:5000/api/v1.0/authentication application/json; charset=utf-8 41
info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[3]
Route matched with {action = "Authenticate", controller = "Authentication"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] AuthenticateAsync(System.String) on controller Mediacreator.RestApi.Controllers.AuthenticationController (CoreRestApi).
info: Microsoft.EntityFrameworkCore.Infrastructure[10403]
Entity Framework Core 3.1.1 initialized 'LoggingContext' using provider 'Microsoft.EntityFrameworkCore.SqlServer' with options: None
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (70ms) [Parameters=[@p0='?' (Size = 4000), @p1='?' (Size = 4000), @p2='?' (Size = 4000), @p3='?' (DbType = DateTime2), @p4='?' (DbType = DateTime2), @p5='?' (DbType = Int32), @p6='?' (Size = 4000), @p7='?' (DbType = Int64), @p8='?' (Size = 4000), @p9='?' (Size = 4000)], CommandType='Text', CommandTimeout='30']
SET NOCOUNT ON;
INSERT INTO [ApiLogs] ([Action], [RemoteAddress], [RequestData], [RequestDateUtc], [ResponseDateUtc], [ResponseStatusCode], [Status], [SystemUserId], [TraceIdentifier], [Url])
VALUES (@p0, @p1, @p2, @p3, @p4, @p5, @p6, @p7, @p8, @p9);
SELECT [Id]
FROM [ApiLogs]
WHERE @@ROWCOUNT = 1 AND [Id] = scope_identity();
info: Microsoft.EntityFrameworkCore.Infrastructure[10403]
Entity Framework Core 3.1.1 initialized 'AuthContext' using provider 'Microsoft.EntityFrameworkCore.SqlServer' with options: None
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (22ms) [Parameters=[@__apiKey_0='?' (Size = 4000)], CommandType='Text', CommandTimeout='30']
SELECT [t].[Id], [t].[ApiKey], [t].[Deleted], [t].[Description], [t].[IsActive], [t].[Password], [t].[Permissions], [t].[Username], [t0].[SystemUserId], [t0].[UserRoleId], [t0].[Id], [t0].[Description], [t0].[Name]
FROM (
SELECT TOP(2) [s].[Id], [s].[ApiKey], [s].[Deleted], [s].[Description], [s].[IsActive], [s].[Password], [s].[Permissions], [s].[Username]
FROM [SystemUsers] AS [s]
WHERE [s].[ApiKey] = @__apiKey_0
) AS [t]
LEFT JOIN (
SELECT [s0].[SystemUserId], [s0].[UserRoleId], [u].[Id], [u].[Description], [u].[Name]
FROM [SystemUserRole] AS [s0]
INNER JOIN [UserRoles] AS [u] ON [s0].[UserRoleId] = [u].[Id]
) AS [t0] ON [t].[Id] = [t0].[SystemUserId]
ORDER BY [t].[Id], [t0].[SystemUserId], [t0].[UserRoleId], [t0].[Id]
info: Mediacreator.RestApi.Services.Authentication.ApiAuthService[0]
User with ApiKey <xxx> authenticated
info: Mediacreator.RestApi.Services.Authentication.ApiAuthService[0]
Token created for user '<yyy>' (5). Token is valid '3600' seconds
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (20ms) [Parameters=[@__logId_0='?' (DbType = Int64)], CommandType='Text', CommandTimeout='30']
SELECT TOP(2) [a].[Id], [a].[Action], [a].[RemoteAddress], [a].[RequestData], [a].[RequestDateUtc], [a].[ResponseDateUtc], [a].[ResponseStatusCode], [a].[Status], [a].[SystemUserId], [a].[TraceIdentifier], [a].[Url]
FROM [ApiLogs] AS [a]
WHERE [a].[Id] = @__logId_0
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (20ms) [Parameters=[@p3='?' (DbType = Int64), @p0='?' (DbType = DateTime2), @p1='?' (DbType = Int32), @p2='?' (Size = 4000)], CommandType='Text', CommandTimeout='30']
SET NOCOUNT ON;
UPDATE [ApiLogs] SET [ResponseDateUtc] = @p0, [ResponseStatusCode] = @p1, [Status] = @p2
WHERE [Id] = @p3;
SELECT @@ROWCOUNT;
info: Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor[1]
Executing ObjectResult, writing value of type 'Mediacreator.RestApi.Models.AuthenticationModel.Entities.Token'.
info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[2]
Executed action Mediacreator.RestApi.Controllers.AuthenticationController.AuthenticateAsync (CoreRestApi) in 8305.5034ms
info: Microsoft.AspNetCore.Hosting.Diagnostics[2]
Request finished in 11069.4992ms 200 application/json; charset=utf-8
РЕДАКТИРОВАТЬ 4: Добавление класса Token
public class Token
{
public readonly string access_token;
public readonly string token_type = Constants.TokenTypeBearer;
public readonly int expires_in;
public Token(string encodedJsonWebToken, int expiresInSeconds)
{
access_token = encodedJsonWebToken;
expires_in = expiresInSeconds;
}
}