无法使用 Serilog 从 HttpContext 访问用户声明以进行日志记录
Posted
技术标签:
【中文标题】无法使用 Serilog 从 HttpContext 访问用户声明以进行日志记录【英文标题】:Can't access User Claims for logging from HttpContext using Serilog 【发布时间】:2019-12-23 19:13:34 【问题描述】:我在一个 ASP Net Core API 项目中使用 Serilog 作为我的记录器,我想记录接收到的访问令牌的内容(声明)。声明应该在 HttpContext 对象的 User 属性中。
问题是当我尝试记录它们不可用的声明时。但是,当我稍后在应用程序中访问声明时,我可以看到它们。在另一个项目中,我遇到了类似的问题,并注意到当我将 Microsoft 日志记录事件从信息转为警告时,我可以看到声明,但当我切换回信息时,它们消失了。
如何记录用户声明?这部分日志记录是否应该在 Startup.cs 中间件而不是 Program.cs 中完成?微软是否可能试图阻止 PII 的记录?我在这里不知所措。任何帮助将不胜感激。
来自 Program.cs:
public static void Main(string[] args)
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
.Enrich.FromLogContext()
.WriteTo.File(new JsonFormatter(), @".\logs\log-bsw-startup-.txt",
rollingInterval: RollingInterval.Day,
retainedFileCountLimit: null,
rollOnFileSizeLimit: true)
.CreateLogger();
try
Log.Information("Building and running web host.");
CreateWebHostBuilder(args).Build().Run();
catch(Exception ex)
Log.Information("Could not start web host. @ex", ex);
finally
Log.Information("Closing web host.");
Log.CloseAndFlush();
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseSerilog((provider, context, config) =>
SerilogConfiguration.Configure(provider, context, config);
)
.UseStartup<Startup>();
来自 SerilogConfiguration.cs 文件:
public static void Configure(
IServiceProvider provider,
WebHostBuilderContext context,
LoggerConfiguration config)
var name = Assembly.GetExecutingAssembly().GetName();
var aspNetCoreEnvironment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
if (aspNetCoreEnvironment == null)
throw new Exception($"No ASPNETCORE_ENVIRONMENT found.");
var connectionString = context.Configuration.GetConnectionString("Default");
if (connectionString == null)
throw new Exception($"No connection string in appsettings.json for ASPNETCORE_ENVIRONMENT 'aspNetCoreEnvironment'.");
var batchPostingLimit = context.Configuration
.GetSection("Logging")
.GetValue("BatchPostingLimit", 1);
var sqlLogTable = context.Configuration
.GetSection("Logging")
.GetValue<string>("SqlLogTable", null);
if (sqlLogTable == null)
throw new Exception("No SQL log table in appsettings.json");
var options = new ColumnOptions();
options.Store.Remove(StandardColumn.Properties); // Removes XML config
options.Store.Add(StandardColumn.LogEvent); // Adds JSON config
options.Id.ColumnName = "id";
options.Message.ColumnName = "message";
options.MessageTemplate.ColumnName = "message_template";
options.TimeStamp.ColumnName = "timestamp";
options.Exception.ColumnName = "exception";
options.Level.ColumnName = "level";
options.LogEvent.ColumnName = "log_event";
options.LogEvent.ExcludeStandardColumns = true;
config.MinimumLevel.Debug()
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
.MinimumLevel.Override("System", LogEventLevel.Warning)
.MinimumLevel.Override("IdentityServer4", LogEventLevel.Warning)
.Enrich.FromLogContext()
.Enrich.WithMachineName()
.Enrich.WithThreadId()
.Enrich.WithThreadName()
.Enrich.WithProcessId()
.Enrich.WithProcessName()
.Enrich.WithEnvironmentUserName()
.Enrich.WithExceptionDetails()
.Enrich.WithProperty("LogId", $"Guid.NewGuid()")
.Enrich.WithProperty("Assembly", $"name.Name")
.Enrich.WithProperty("Version", $"name.Version")
.Enrich.WithProperty("AspNetCoreEnvironment", $"aspNetCoreEnvironment")
.Enrich.WithAspnetcoreHttpcontext(provider, CustomHttpContextEnricher);
config.WriteTo.MSSqlServer(connectionString, sqlLogTable, columnOptions: options,
batchPostingLimit: batchPostingLimit,
restrictedToMinimumLevel: LogEventLevel.Information);
config.WriteTo.File(new JsonFormatter(), @".\logs\log-.txt",
rollingInterval: RollingInterval.Day,
retainedFileCountLimit: null,
rollOnFileSizeLimit: true);
private static HttpContextProperties CustomHttpContextEnricher(
IHttpContextAccessor httpContextAccessor)
var context = httpContextAccessor.HttpContext;
if (context == null)
return null;
var contextIndex = "http-properties";
// Short circuit if there's already a context properties object available
var contextProperties = context.Items[contextIndex] as HttpContextProperties;
if (contextProperties != null)
return contextProperties;
var user = context.User;
var isAuthenticated = false;
string internalUserId = null;
string email = null;
var userClaims = new Dictionary<string, string>();
var i = 1;
// Here's where I'm trying to grab the user claims. When I do logger.LogInformation I would think the user claims would be available but they aren't.
if (user != null)
isAuthenticated = user.Identity.IsAuthenticated;
var userId = "user_id";
if (user.HasClaim(c => c.Type == userId))
internalUserId = user.Claims.FirstOrDefault(
c => c.Type == userId).Value.ToString();
if (user.HasClaim(c => c.Type == JwtRegisteredClaimNames.Email))
email = user.Claims.FirstOrDefault(
c => c.Type == JwtRegisteredClaimNames.Email).Value.ToString();
userClaims = user.Claims.ToDictionary(x => $"i++.ToString("D2")-x.Type", y => y.Value.ToString());
var properties = new HttpContextProperties
IpAddress = context.Connection.RemoteIpAddress.ToString(),
Host = context.Request.Host.ToString(),
Path = context.Request.Path.ToString(),
IsHttps = context.Request.IsHttps,
Scheme = context.Request.Scheme,
Method = context.Request.Method,
ContentType = context.Request.ContentType,
Protocol = context.Request.Protocol,
QueryString = context.Request.QueryString.ToString(),
Query = context.Request.Query.ToDictionary(x => x.Key, y => y.Value.ToString()),
Headers = context.Request.Headers
.Where(x => !(x.Key.ToString() == "Authorization")) // Remove from logs for security purposes
.ToDictionary(x => x.Key, y => y.Value.ToString()),
UserClaims = userClaims,
InternalUserId = internalUserId,
Email = email,
IsAuthenticated = isAuthenticated
;
context.Items[contextIndex] = properties;
return properties;
public class HttpContextProperties
public string Path get; set;
public string Host get; set;
public string Method get; set;
public string IpAddress get; set;
public bool IsHttps get; set;
public string Scheme get; set;
public string ContentType get; set;
public string Protocol get; set;
public string QueryString get; set;
public Dictionary<string, string> Query get; set;
public Dictionary<string, string> Headers get; set;
public string InternalUserId get; set;
public string Email get; set;
public bool IsAuthenticated get; set;
public Dictionary<string, string> UserClaims get; set;
【问题讨论】:
【参考方案1】:我不确定这是否是答案,但它可能会有所帮助 - .Net Core 的东西目前有点神秘,所以这可能会对你有所帮助(你可能也有这个无论如何你的startup.cs):
我试图在我的 startup.cs 中利用管道以在加载 SPA 服务之前触发到身份服务器登录的重定向(我不希望用户甚至尝试加载 SPA,除非他们经过身份验证。
我发现我的 Startup.cs 中的以下代码(在配置方法中)中的 IsAuthenticate 始终为 false:
app.Use(async (context, next) =>
if (!context.User.Identity.IsAuthenticated)
await context.ChallengeAsync("Identity.Application");
else
await next();
);
直到我将这段代码(在 ConfigureService 方法中)添加到我的启动中:
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddRoles<IdentityRole>()
//The following two may not be as relevant as the first two.
.AddDefaultUI()
.AddEntityFrameworkStores<ApplicationDbContext>();
【讨论】:
以上是关于无法使用 Serilog 从 HttpContext 访问用户声明以进行日志记录的主要内容,如果未能解决你的问题,请参考以下文章
Serilog:无法使用 MongoDb 接收器登录到 MongoDb
解决错误 - 无法解析“Serilog.ILogger”类型的服务