防止在 asp.net core 2.2 中重定向到 /Account/Login

Posted

技术标签:

【中文标题】防止在 asp.net core 2.2 中重定向到 /Account/Login【英文标题】:Prevent redirect to /Account/Login in asp.net core 2.2 【发布时间】:2019-05-24 08:03:34 【问题描述】:

当用户未登录时,我试图阻止应用重定向到 asp.net core 2.2 中的/Account/Login

即使我写了LoginPath = new PathString("/api/contests");,任何未经授权的请求仍然被重定向到/Account/Login

这是我的 Startup.cs:

using System;
using System.Reflection;
using AutoMapper;
using Contest.Models;
using Contest.Tokens;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Authorization;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using Swashbuckle.AspNetCore.Swagger;

namespace Contest

    public class Startup
    
        public IConfiguration Configuration  get; 

        public Startup(IConfiguration configuration)
        
            Configuration = configuration;
        

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        

            services.AddAutoMapper();

            // In production, the React files will be served from this directory
            services.AddSpaStaticFiles(configuration =>
            
                configuration.RootPath = "clientapp/build";
            );

            // ===== Add our DbContext ========
            string connection = Configuration.GetConnectionString("DBLocalConnection");
            string migrationAssemblyName = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;
            services.AddDbContext<ContestContext>(options =>
                    options.UseSqlServer(connection,
                    sql => sql.MigrationsAssembly(migrationAssemblyName)));

            // ===== Add Identity ========
            services.AddIdentity<User, IdentityRole>(o =>
            
                o.User.RequireUniqueEmail = true;
                o.Tokens.EmailConfirmationTokenProvider = "EMAILCONF";
                // I want to be able to resend an `Email` confirmation email
                // o.SignIn.RequireConfirmedEmail = true; 
            ).AddRoles<IdentityRole>()
                .AddEntityFrameworkStores<ContestContext>()
                .AddTokenProvider<EmailConfirmationTokenProvider<User>>("EMAILCONF")
                .AddDefaultTokenProviders();

            services.Configure<DataProtectionTokenProviderOptions>(o =>
                o.TokenLifespan = TimeSpan.FromHours(3)
            );

            services.Configure<EmailConfirmationTokenProviderOptions>(o =>
                o.TokenLifespan = TimeSpan.FromDays(2)
            );

            // ===== Add Authentication ========

            services.AddAuthentication(o => o.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme)
                .AddCookie(options =>
                
                    options.Cookie.Name = "auth_cookie";
                    options.Cookie.SameSite = SameSiteMode.None;
                    options.LoginPath = new PathString("/api/contests");
                    options.AccessDeniedPath = new PathString("/api/contests");
                    options.Events = new CookieAuthenticationEvents
                    
                        OnRedirectToLogin = context =>
                        
                            context.Response.StatusCode = StatusCodes.Status401Unauthorized;
                            return Task.CompletedTask;
                        ,
                    ;
                );

            // ===== Add Authorization ========

            services.AddAuthorization(o =>
            

            );

            services.AddCors();

            // ===== Add MVC ========
            services.AddMvc(config =>
            
                var policy = new AuthorizationPolicyBuilder()
                                    .RequireAuthenticatedUser()
                                    .Build();
                config.Filters.Add(new AuthorizeFilter(policy));
            )
                .AddJsonOptions(options => options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore)
                .SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

            // ===== Add Swagger ========
            services.AddSwaggerGen(c =>
            
                c.SwaggerDoc("v1", new Info
                
                    Title = "Core API",
                    Description = "Documentation",
                );

                var xmlPath = $"System.AppDomain.CurrentDomain.BaseDirectoryContest.xml";
                c.IncludeXmlComments(xmlPath);
            );

        

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        
            if (env.IsDevelopment())
            
                app.UseDeveloperExceptionPage();
            
            else
            
                app.UseExceptionHandler("/Error");
                app.UseHsts();
            

            app.UseHttpsRedirection();
            app.UseSpaStaticFiles(new StaticFileOptions()
            

            );

            app.UseCors(policy =>
            
                policy.AllowAnyHeader();
                policy.AllowAnyMethod();
                policy.AllowAnyOrigin();
                policy.AllowCredentials();
            );

            app.UseAuthentication();

            app.UseMvc();

            if (env.IsDevelopment())
            
                app.UseSwagger();
                app.UseSwaggerUI(c =>
                
                    c.SwaggerEndpoint("/swagger/v1/swagger.json", "Core API");
                );
            

            app.UseSpa(spa =>
            
                spa.Options.SourcePath = "clientapp";

                if (env.IsDevelopment())
                
                    // spa.UseReactDevelopmentServer(npmScript: "start");
                    spa.UseProxyToSpaDevelopmentServer("http://localhost:3000");
                
            );

        

    

我通过创建一个控制器来处理这个路由来绕过这个:

[Route("/")]
[ApiController]
public class UnauthorizedController : ControllerBase

    public UnauthorizedController()
    

    

    [HttpGet("/Account/Login")]
    [AllowAnonymous]
    public IActionResult Login()
    
        HttpContext.Response.StatusCode = StatusCodes.Status401Unauthorized;
        return new ObjectResult(new
        
            StatusCode = StatusCodes.Status401Unauthorized,
            Message = "Unauthorized",
        );
    


我的设置有什么问题? 谢谢

【问题讨论】:

【参考方案1】:

如果您使用 ASP.NET Identity,则可以遵循 kuldeep chopra 在另一个答案中提到的相同模式,而是在 AddCookie 方法中:

public void ConfigureServices(IServiceCollection services)

 services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
    .AddCookie((o) =>
    
      o.Cookie.HttpOnly = true;
      o.LoginPath = string.Empty;
      o.AccessDeniedPath = string.Empty;
      o.Events.OnRedirectToLogin = context =>
      
        context.Response.StatusCode = StatusCodes.Status401Unauthorized;
        return Task.CompletedTask;
      ;
    );

仅将路径设置为空/null 是不够的。

【讨论】:

【参考方案2】:

这有点晚了,但也许其他人会和我有同样的事情。

那么我有什么代码?

services
            .AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
            .AddScheme<CerveraProxyAuthenticationOptions, CerveraProxyAuthenticationSchemeHandler>(ProxyAuthentication.AuthenticationScheme, options =>  )
            .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
            
                options.Events.OnRedirectToLogin = (o) =>
                
                    o.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
                    return Task.CompletedTask;
                ;
                options.Events.OnRedirectToAccessDenied = (o) =>
                
                    o.Response.StatusCode = (int)HttpStatusCode.Forbidden;
                    return Task.CompletedTask;
                ;
                if (cookieSettings.LocalizationCookieExpiration.TryConvertToTimeSpan(out TimeSpan expiration))
                
                    options.ExpireTimeSpan = expiration;
                    options.Cookie.MaxAge = expiration;
                

                options.Cookie.SecurePolicy = cookieSettings.SecureCookies
                    ? CookieSecurePolicy.Always
                    : CookieSecurePolicy.SameAsRequest;
                options.Cookie.Path = "/";
                options.Cookie.HttpOnly = true;
                options.Cookie.IsEssential = true;
                SameSiteMode sameSiteMode = Enum.Parse<SameSiteMode>(cookieSettings.SameSite);
                options.Cookie.SameSite = sameSiteMode;
                options.Cookie.Name = cookieSettings.AuthenticationCookieName;
                options.Events = new CookieAuthenticationEvents
                
                    OnSigningIn = async context =>
                    
                        context.Options.Cookie.Domain = context.HttpContext.Request.Host.Host;
                    
                ;
            );

问题是我在范围底部有这段代码!

options.Events = new CookieAuthenticationEvents
                
                    OnSigningIn = async context =>
                    
                        context.Options.Cookie.Domain = context.HttpContext.Request.Host.Host;
                    
                ;

一旦我把它移到顶部,它就开始工作了!奇怪但有效!

【讨论】:

【参考方案3】:

我遇到了这个问题并做了一个解决方法,我只是创建一个控制器“帐户”并在其中写入重定向:

    public class AccountController : Controller
    
        public IActionResult Login()
        
            return RedirectToAction("Login", "Home");
        
    

【讨论】:

这只是针对实际问题的一个创可贴,另外它增加了一个不必要的重定向。【参考方案4】:
 services.ConfigureApplicationCookie(options => 
            options.AccessDeniedPath = "/Account/Login";
            options.LoginPath = "/Account/Denied";
            options.Cookie.HttpOnly = true;
            options.ExpireTimeSpan = TimeSpan.FromMinutes(30);
            options.Events.OnRedirectToLogin = context => 
                context.Response.StatusCode = StatusCodes.Status401Unauthorized;
                return Task.CompletedTask;
            ;
        );

【讨论】:

options.Events.OnRedirectToLogin = context => context.Response.StatusCode = StatusCodes.Status401Unauthorized;返回Task.CompletedTask; ;当 http 状态代码为 401 unathorized 时,此事件会阻止您将页面重定向到日志【参考方案5】:

大家好,欢迎来到 *** ?

您遇到的行为与您使用 ASP.NET Identity 的事实有关。 当您调用services.AddIdentity 时,会在后台注册一个基于cookie 的身份验证方案并将其设置为默认质询方案,如代码here on GitHub 中所示。

即使您自己注册了一个 cookie 身份验证方案并将其设置为默认方案,但特定的默认方案(如 AuthenticateSchemeChallengeSchemeSignInScheme 等)仍占优先地位。 DefaultScheme 仅在未设置特定的情况下由身份验证系统使用。

要回答您的问题,您可以使用辅助方法 services.ConfigureApplicationCookie 将配置设置应用于 ASP.NET 身份 cookie 选项,如下所示:

// ===== Add Identity ========
services.AddIdentity<User, IdentityRole>(o =>

    o.User.RequireUniqueEmail = true;
    o.Tokens.EmailConfirmationTokenProvider = "EMAILCONF";
    // I want to be able to resend an `Email` confirmation email
    // o.SignIn.RequireConfirmedEmail = true; 
).AddRoles<IdentityRole>()
    .AddEntityFrameworkStores<ContestContext>()
    .AddTokenProvider<EmailConfirmationTokenProvider<User>>("EMAILCONF")
    .AddDefaultTokenProviders();

services.Configure<DataProtectionTokenProviderOptions>(o =>
    o.TokenLifespan = TimeSpan.FromHours(3)
);

services.Configure<EmailConfirmationTokenProviderOptions>(o =>
    o.TokenLifespan = TimeSpan.FromDays(2)
);

// ===== Configure Identity =======
service.ConfigureApplicationCookie(options =>

    options.Cookie.Name = "auth_cookie";
    options.Cookie.SameSite = SameSiteMode.None;
    options.LoginPath = new PathString("/api/contests");
    options.AccessDeniedPath = new PathString("/api/contests");

    // Not creating a new object since ASP.NET Identity has created
    // one already and hooked to the OnValidatePrincipal event.
    // See https://github.com/aspnet/AspNetCore/blob/5a64688d8e192cacffda9440e8725c1ed41a30cf/src/Identity/src/Identity/IdentityServiceCollectionExtensions.cs#L56
    options.Events.OnRedirectToLogin = context =>
    
        context.Response.StatusCode = StatusCodes.Status401Unauthorized;
        return Task.CompletedTask;
    ;
);

这也意味着您可以安全地删除添加基于 cookie 的身份验证方案的部分,因为这由 ASP.NET Identity 本身负责。

告诉我你的进展如何!

【讨论】:

谢谢。像魅力一样工作! 如果您想确保所有内容都被重定向到 https 耦合,该怎么办?看,我发现我的网站重定向到 http,即使我的条目是 https 页面。

以上是关于防止在 asp.net core 2.2 中重定向到 /Account/Login的主要内容,如果未能解决你的问题,请参考以下文章

在 ASP.NET 中重定向之前的 Javascript 警报

ASP.NET Core 2.2 JWT 身份验证

在 ASP.NET MVC 中重定向未经授权的控制器

如何在 ASP.NET MVC 中重定向到动态登录 URL

如何在 asp.net mvc 中重定向到正确的控制器操作

在 ASP.Net MVC3 中重定向到不影响 URL 的区域