当Facebook或Google登录时,Aspnetcore关联失败;当确认电子邮件时,令牌无效。

时间:2019-01-28 22:32:16

标签: azure asp.net-core asp.net-identity

我已经完成了将应用程序迁移到aspnetcore的工作,现在我确实遇到了验证令牌的随机问题。 1.问题是随机用户会收到

  

在处理远程登录时遇到错误。相关性   失败。

问题是,如果我去自我测试,它会起作用。

第二个问题是,当用户收到电子邮件确认令牌并单击电子邮件中的链接时,他们会得到

  

无效的令牌

因此他们无法确认电子邮件。

首先,我认为问题与UseCookiePolicy有关,但我已将其禁用。

Startup.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?}");
                });
        }
    }
}

我正在生成用于电子邮件确认的网址,如下所示:

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自动编码的。

2 个答案:

答案 0 :(得分:0)

很可能令牌验证失败,因为令牌是在一个域中生成并在另一个域中验证的。

在ASP.Net中,可以通过在两个域的web.config文件中使用相同的machineKey来解决此问题。

对于ASP.Net Core,您可以按照here所述替换machineKey,以便在两个域中都具有相同的密码设置。

请参阅:Replace the ASP.NET machineKey in ASP.NET Core

答案 1 :(得分:0)

最后几周的调查后,我发现了一个问题。

当用户注册时,我将发送电子邮件和短信,然后用户去确认短信,这将触发安全标记的更新。然后,如果用户单击“确认电子邮件”,但由于安全戳不是same as in a token

而失败,此操作将失败

因此,请在确认电话号码后发送确认电子邮件。解决了我一半的问题。