我已经将我的应用程序迁移到aspnetcore,现在我确实遇到了验证令牌的随机问题。1.问题是随机用户接收
在处理远程登录时遇到错误。相关性失败。
问题是,如果我自己去测试它,它就能工作。
第二个问题是,当用户收到电子邮件确认令牌并点击电子邮件中的链接时,他们将获得
无效令牌
因此他们无法确认电子邮件。
首先我认为问题在于UseCookiePolicy,但我已经禁用了它。
启动.cs
namespace Flymark.Online.Web
{
public class Startup
{
private readonly IHostingEnvironment _env;
public Startup(IHostingEnvironment env)
{
_env = env;
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", true, true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Configure SnapshotCollector from application settings
services.Configure<SnapshotCollectorConfiguration>(
Configuration.GetSection(nameof(SnapshotCollectorConfiguration)));
// Add SnapshotCollector telemetry processor.
services.AddSingleton<ITelemetryProcessorFactory>(sp => new SnapshotCollectorTelemetryProcessorFactory(sp));
services.AddApplicationInsightsTelemetryProcessor<TelemetryFilter>();
services.AddSingleton<ITelemetryInitializer, AppInsightsInitializer>();
services.AddCors();
var decompressionOptions = new RequestDecompressionOptions();
decompressionOptions.UseDefaults();
services.AddRequestDecompression(decompressionOptions);
FlymarkAppSettings.Init(Configuration, _env.EnvironmentName);
var storageUri = new Uri(Configuration.GetValue<string>("Flymark:DataProtectionStorageUrl"));
//Get a reference to a container to use for the sample code, and create it if it does not exist.
var container = new CloudBlobClient(storageUri).GetContainerReference("data-protection");
services.AddDataProtection()
.SetApplicationName("Flymark.Online")
.PersistKeysToAzureBlobStorage(container, "data-protection.xml");
services.AddDetection();
services.AddAutoMapper();
services.AddWebMarkupMin(
options =>
{
options.AllowMinificationInDevelopmentEnvironment = true;
options.AllowCompressionInDevelopmentEnvironment = true;
})
.AddHtmlMinification(o =>
{
o.ExcludedPages = new List<IUrlMatcher>
{
new WildcardUrlMatcher("/scripts/*")
};
o.MinificationSettings.AttributeQuotesRemovalMode = HtmlAttributeQuotesRemovalMode.KeepQuotes;
o.MinificationSettings.EmptyTagRenderMode = HtmlEmptyTagRenderMode.NoSlash;
o.MinificationSettings.RemoveOptionalEndTags = false;
})
.AddXmlMinification()
.AddHttpCompression();
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.Lax;
});
services
.AddScoped<UserStore<ApplicationUser, IdentityRole<int>, FlymarkContext, int, IdentityUserClaim<int>,
IdentityUserRole<int>, IdentityUserLogin<int>, IdentityUserToken<int>, IdentityRoleClaim<int>>,
ApplicationUserStore>();
services.AddScoped<UserManager<ApplicationUser>, FlymarkUserManager>();
services.AddScoped<RoleManager<IdentityRole<int>>, ApplicationRoleManager>();
services.AddScoped<SignInManager<ApplicationUser>, ApplicationSignInManager>();
services
.AddScoped<RoleStore<IdentityRole<int>, FlymarkContext, int, IdentityUserRole<int>,
IdentityRoleClaim<int>>, ApplicationRoleStore>();
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddIdentity<ApplicationUser, IdentityRole<int>>(
o =>
{
o.User.RequireUniqueEmail = true;
})
.AddUserStore<ApplicationUserStore>()
.AddUserManager<FlymarkUserManager>()
.AddRoleStore<ApplicationRoleStore>()
.AddRoleManager<ApplicationRoleManager>()
.AddSignInManager<ApplicationSignInManager>()
.AddClaimsPrincipalFactory<FlymarkClaimsPrincipalFactory>()
.AddDefaultTokenProviders();
services.AddSingleton<ILoggerFactory, LoggerFactory>(sp =>
new LoggerFactory(
sp.GetRequiredService<IEnumerable<ILoggerProvider>>(),
sp.GetRequiredService<IOptionsMonitor<LoggerFilterOptions>>()
)
);
services.Configure<ApiBehaviorOptions>(options => { options.SuppressModelStateInvalidFilter = true; });
services.AddMemoryCache();
services.AddSingleton<IEmailSender, FlymarkEmailSender>();
services.AddMvc(o =>
{
o.Conventions.Add(new FlymarkAsyncConvention());
o.AllowValidatingTopLevelNodes = false;
o.AllowEmptyInputInBodyModelBinding = true;
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
.AddJsonOptions(opt =>
{
opt.SerializerSettings.DateFormatString = "dd/MM/yyyy";
opt.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
var resolver = opt.SerializerSettings.ContractResolver;
if (resolver == null) return;
if (resolver is DefaultContractResolver res) res.NamingStrategy = null;
});
services.Configure<IdentityOptions>(options =>
{
// Default Password settings.
options.Password.RequireDigit = false;
options.Password.RequireLowercase = false;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = false;
options.Password.RequiredLength = 6;
options.Password.RequiredUniqueChars = 1;
options.Lockout.MaxFailedAccessAttempts = 20;
});
services
.AddAuthorization(options =>
{
options.DefaultPolicy = new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes(OAuthValidationDefaults.AuthenticationScheme,
IdentityConstants.ApplicationScheme)
.RequireAuthenticatedUser()
.Build();
});
services.AddAuthentication()
.AddExternalAuthProviders(Configuration)
.AddFlymarkOpenIdConnectServer()
.AddOAuthValidation(OAuthValidationDefaults.AuthenticationScheme);
services.Configure<SecurityStampValidatorOptions>(options =>
{
// This is the key to control how often validation takes place
options.ValidationInterval = TimeSpan.FromMinutes(15);
});
services.ConfigureApplicationCookie(config =>
{
config.LoginPath = "/Identity/Account/LogIn";
config.AccessDeniedPath = "/Identity/Account/LogIn";
config.SlidingExpiration = true;
config.Events.OnRedirectToLogin = OnRedirectToLoginAsync;
});
}
private Task OnRedirectToLoginAsync(RedirectContext<CookieAuthenticationOptions> context)
{
if (context.HttpContext.Request.Path.Value.Contains("/api"))
context.Response.StatusCode = 401;
else
context.Response.Redirect(context.RedirectUri);
return Task.CompletedTask;
}
public void ConfigureContainer(ContainerBuilder builder)
{
builder.RegisterSource(new AnyConcreteTypeNotAlreadyRegisteredSource());
//builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
builder.RegisterModule(new FlymarkDalDiModule
{
Configuration = Configuration
});
builder.RegisterModule(new DbDiModule(FlymarkAppSettings.Instance.DbContextConnection,
FlymarkAppSettings.Instance.StorageConnectionString));
builder.RegisterModule<FlymarkWebDiModule>();
}
private CultureInfo CreateCulture(string key)
{
return new CultureInfo(key)
{
NumberFormat = {NumberDecimalSeparator = "."},
DateTimeFormat = {ShortDatePattern = "dd/MM/yyyy"}
};
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env,
ILoggerFactory loggerFactory, IMapper mapper)
{
#if DEBUG
mapper.ConfigurationProvider.AssertConfigurationIsValid();
#endif
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
app.UseStaticFiles(new StaticFileOptions
{
OnPrepareResponse = context =>
{
context.Context.Response.Headers.Add("Cache-Control", "no-cache, no-store");
context.Context.Response.Headers.Add("Expires", "-1");
}
});
}
else
{
app.UseExceptionHandler("/Error/Error500");
app.UseStaticFiles();
}
app.UseCors(builder =>
{
builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowCredentials()
.SetPreflightMaxAge(TimeSpan.FromMinutes(5))
.AllowAnyHeader();
});
app.UseRequestDecompression();
app.UseLegacyTokenContentTypeFixMiddleware();
var supportedCultures = new[]
{
CreateCulture("en"),
CreateCulture("ru"),
CreateCulture("uk")
};
app.UseFlymarkExceptionMiddleware();
app.UseCookiePolicy();
app
.UseAuthentication()
.UseDomainMiddleware()
.UseRequestLocalization(new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture("en"),
SupportedCultures = supportedCultures,
SupportedUICultures = supportedCultures
})
.UseWebMarkupMin();
app.Use(async (ctx, next) =>
{
await next();
if (ctx.Response.StatusCode == 404 && !ctx.Response.HasStarted)
{
//Re-execute the request so the user gets the error page
var originalPath = ctx.Request.Path.Value;
ctx.Items["originalPath"] = originalPath;
ctx.Request.Path = "/error/error404";
await next();
}
});
app
.UseMvc(routes =>
{
routes.MapRoute(
"areaRoute",
"{area:exists}/{controller=Dashboard}/{action=Index}/{id?}");
routes.MapRoute(
"default",
"{controller=Home}/{action=Index}/{id?}");
});
}
}
}
我正在生成这样的电子邮件确认url:
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
var callbackUrl = Url.Page("/Account/ConfirmEmail",
null,
new {userId = user.Id, code = code.ToBase64String()},
returnDomainUrl.Scheme,
returnDomainUrl.Host);
我还认为它可能是angularjs(我的页面上仍然有它),但由于它是由中间件处理的,所以它没有在/signin facebook上加载。
我认为数据保护有问题,因为我在登录和确认电子邮件中收到了它们
我也尝试了基于64电子邮件令牌,但这没有帮助。此外,我认为url是由Page.url 自动编码的
令牌验证很可能失败,因为令牌是在一个域中生成的,并且正在另一个域进行验证。
在ASP.Net中,这可以通过在两个域的web.config文件中使用相同的machineKey来解决。
对于ASP.Net Core,您可以按照此处所述替换machineKey,以便在两个域中都具有相同的加密设置。
请参阅:替换ASP.NET Core 中的ASP.NET machineKey
经过数周的调查,我终于发现了一个问题。
当用户注册时,我会发送电子邮件和短信,然后用户去确认短信,这将触发安全戳的更新。稍后,如果用户单击确认电子邮件,但由于安全戳与令牌中的不同而失败
因此,在确认电话号码后发送确认电子邮件。解决了我一半的问题。