简体   繁体   English

登录成功后授权页面重定向回登录

[英]Authorized page redirects back to login after successful login

I have a simple asp.net-core app using version 2.1.我有一个使用 2.1 版的简单 asp.net-core 应用程序。 The HomeController has a page with the authorized attribute. HomeController 有一个带有authorized 属性的页面。 When I click on the About page that needs authorization I get to the Login page and after typing my username and password the following happens:当我点击需要授权的关于页面时,我会进入登录页面,在输入我的用户名和密码后,会发生以下情况:

  • the user is successfully logged in用户已成功登录
  • the user is redirected to /Home/About用户被重定向到 /Home/About
  • HomeController.About method is hit in the debugger and the About view is served. HomeController.About 方法在调试器中被命中,并提供了 About 视图。
  • Somehow the user is redirected back to the AccountController.Login以某种方式用户被重定向回 AccountController.Login
  • the user is logged in so I can navigate to any page now that needs authorization.用户已登录,因此我现在可以导航到任何需要授权的页面。

I tried this with Chrome and Edge as well.我也用 Chrome 和 Edge 试过这个。 I can reproduce the error with both browser.我可以用两个浏览器重现错误。

I created a small repro project that reproduces the issue on my machine and setup.我创建了一个小型 repro 项目,可以在我的机器和设置上重现该问题。

Portfolio_Authentication Portfolio_Authentication

The steps I am using to reproduce the problem are the following:我用来重现问题的步骤如下:

  1. Register a user on the website在网站上注册用户
  2. Log out the user if it is logged in.如果用户已登录,则注销该用户。
  3. Click on the About menu link in the header.单击标题中的“关于”菜单链接。
  4. Type in the username and password输入用户名和密码
  5. Notice the authentication is ok as the user name can be see top right side of the screen but the login does not redirect to the About page.请注意,身份验证是可以的,因为用户名可以在屏幕的右上角看到,但登录不会重定向到“关于”页面。

I am wondering why this is happening and how to correct this?我想知道为什么会发生这种情况以及如何纠正这个问题? Thanks for helping me.谢谢你帮助我。 All feedback is welcome.欢迎所有反馈。

HomeController:家庭控制器:

public class HomeController : Controller
{
    public IActionResult Index()
    {
        return View();
    }

    [Authorize]
    public IActionResult About()
    {
        ViewData["Message"] = "Your application description page.";

        return View();
    } 
}

My Startup.cs looks the following:我的 Startup.cs 如下所示:

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddDbContext<ApplicationDbContext>(options =>
            options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

        services.AddIdentity<ApplicationUser, IdentityRole>()
            .AddEntityFrameworkStores<ApplicationDbContext>()
            .AddDefaultTokenProviders();

        // services.AddAuthentication();
        services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
                .AddCookie(options =>
                {
                    options.LoginPath = "/Account/LogIn";
                    options.LogoutPath = "/Account/LogOff";
                });

        // Add application services.
        services.AddTransient<IEmailSender, EmailSender>();

        services.AddMvc()
                .AddFeatureFolders(); // .SetCompatibilityVersion(CompatibilityVersion.Version_2_1); ;
    }

    // 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.UseBrowserLink();
            app.UseDeveloperExceptionPage();
            app.UseDatabaseErrorPage();
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");
        }

        app.UseStaticFiles();

        app.UseAuthentication();

        app.UseMvc(routes =>
        {
            routes.MapRoute(
                name: "default",
                template: "{controller=Home}/{action=Index}/{id?}");
        });
    }
}

and this is the log I see in my output window in Visual Studio:这是我在 Visual Studio 的输出窗口中看到的日志:

Microsoft.EntityFrameworkCore.Database.Command:Information: Executed DbCommand (1ms) [Parameters=[@__normalizedUserName_0='?' (Size = 256)], CommandType='Text', CommandTimeout='30']
SELECT TOP(1) [u].[Id], [u].[AccessFailedCount], [u].[ConcurrencyStamp], [u].[Email], [u].[EmailConfirmed], [u].[LockoutEnabled], [u].[LockoutEnd], [u].[NormalizedEmail], [u].[NormalizedUserName], [u].[PasswordHash], [u].[PhoneNumber], [u].[PhoneNumberConfirmed], [u].[SecurityStamp], [u].[TwoFactorEnabled], [u].[UserName]
FROM [AspNetUsers] AS [u]
WHERE [u].[NormalizedUserName] = @__normalizedUserName_0
Microsoft.EntityFrameworkCore.Database.Command:Information: Executed DbCommand (2ms) [Parameters=[@__user_Id_0='?' (Size = 450)], CommandType='Text', CommandTimeout='30']
SELECT [uc].[Id], [uc].[ClaimType], [uc].[ClaimValue], [uc].[UserId]
FROM [AspNetUserClaims] AS [uc]
WHERE [uc].[UserId] = @__user_Id_0
Microsoft.EntityFrameworkCore.Database.Command:Information: Executed DbCommand (1ms) [Parameters=[@__userId_0='?' (Size = 450)], CommandType='Text', CommandTimeout='30']
SELECT [role].[Name]
FROM [AspNetUserRoles] AS [userRole]
INNER JOIN [AspNetRoles] AS [role] ON [userRole].[RoleId] = [role].[Id]
WHERE [userRole].[UserId] = @__userId_0
Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler:Information: AuthenticationScheme: Identity.Application signed in.
Portfolio.Features.Account.AccountController:Information: User logged in.
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Executed action method Portfolio.Features.Account.AccountController.Login (Portfolio), returned result Microsoft.AspNetCore.Mvc.RedirectResult in 32.6215ms.
Microsoft.AspNetCore.Mvc.Infrastructure.RedirectResultExecutor:Information: Executing RedirectResult, redirecting to /Home/About.
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Executed action Portfolio.Features.Account.AccountController.Login (Portfolio) in 41.5571ms
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request finished in 49.3022ms 302 
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request starting HTTP/1.1 GET http://localhost:44392/Home/About  
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Route matched with {action = "About", controller = "Home"}. Executing action Portfolio.Features.Home.HomeController.About (Portfolio)
Microsoft.AspNetCore.Authorization.DefaultAuthorizationService:Information: Authorization was successful.
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Executing action method Portfolio.Features.Home.HomeController.About (Portfolio) - Validation state: Valid
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Executed action method Portfolio.Features.Home.HomeController.About (Portfolio), returned result Microsoft.AspNetCore.Mvc.ViewResult in 2212.7896ms.
Microsoft.AspNetCore.Mvc.ViewFeatures.ViewResultExecutor:Information: Executing ViewResult, running view About.
Microsoft.AspNetCore.Mvc.ViewFeatures.ViewResultExecutor:Information: Executed ViewResult - view About executed in 3.1424ms.
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Executed action Portfolio.Features.Home.HomeController.About (Portfolio) in 2225.297ms
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request finished in 2233.0907ms 200 text/html; charset=utf-8
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request starting HTTP/1.1 GET http://localhost:44392/Account/Login?ReturnUrl=%2FHome%2FAbout  
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Route matched with {action = "Login", controller = "Account"}. Executing action Portfolio.Features.Account.AccountController.Login (Portfolio)
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Executing action method Portfolio.Features.Account.AccountController.Login (Portfolio) with arguments (/Home/About) - Validation state: Valid
Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler:Information: AuthenticationScheme: Identity.External signed out.
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Executed action method Portfolio.Features.Account.AccountController.Login (Portfolio), returned result Microsoft.AspNetCore.Mvc.ViewResult in 1528.1878ms.
Microsoft.AspNetCore.Mvc.ViewFeatures.ViewResultExecutor:Information: Executing ViewResult, running view Login.
Microsoft.AspNetCore.Mvc.ViewFeatures.ViewResultExecutor:Information: Executed ViewResult - view Login executed in 5.8984ms.
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Executed action Portfolio.Features.Account.AccountController.Login (Portfolio) in 1543.8386ms
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request finished in 1553.3133ms 200 text/html; charset=utf-8
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request starting HTTP/1.1 GET http://localhost:44392/lib/bootstrap/dist/css/bootstrap.css  
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request starting HTTP/1.1 GET http://localhost:44392/css/site.css  
Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware:Information: Sending file. Request path: '/css/site.css'. Physical path: 'C:\dev\web\portfolio-variants\Portfolio_Controller_V2\Portfolio\wwwroot\css\site.css'
Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware:Information: Sending file. Request path: '/lib/bootstrap/dist/css/bootstrap.css'. Physical path: 'C:\dev\web\portfolio-variants\Portfolio_Controller_V2\Portfolio\wwwroot\lib\bootstrap\dist\css\bootstrap.css'
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request finished in 28.4192ms 200 text/css
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request finished in 41.4384ms 200 text/css

In your Github project, you have a site.js file that contains (amongst other things), the following jQuery event-handler:在您的 Github 项目中,您有一个site.js文件,其中包含(除其他外)以下 jQuery 事件处理程序:

$('form[method=post]').not('.no-ajax').on('submit', function () {
    ...

    $.ajax({
        url: $this.attr('action'),
        ...
        statusCode: {
            200: redirect
        },
        ...
    }).error(highlightErrors);

    return false;
}

When you submit your login form, you end up running through this block of code above, which then invokes your redirect callback function for a statusCode of 200 , shown below:当您提交登录表单时,您最终会运行上面的这段代码,然后调用您的redirect回调函数以获取statusCode 200 ,如下所示:

var redirect = function (data) {
    if (data.redirect) {
        window.location = data.redirect;
    } else {
        window.scrollTo(0, 0);
        window.location.reload();
    }
};

In the scenario you've described, data.redirect is undefined .在您描述的场景中, data.redirectundefined In that case, you end up calling window.location.reload() , which, of course, reloads the login page and explains the issue you've been having clearly.在这种情况下,您最终会调用window.location.reload() ,这当然会重新加载登录页面并清楚地解释您一直遇到的问题。

Here's a step-by-step breakdown of what happens:以下是对发生的事情的分步细分:

  1. The submit event is triggered when clicking "Log In".单击“登录”时会触发提交事件。
  2. The browser-based POST is intercepted, being sent instead as an XHR request.基于浏览器的 POST 被拦截,而是作为 XHR 请求发送。
  3. The server logs in the user, assigns the cookie and returns a 302 response for redirecting to /Home/About .服务器登录用户,分配 cookie 并返回 302 响应以重定向到/Home/About
  4. The XHR internal mechanics follows the redirect and pulls down the HTML for the /Home/About page. XHR 内部机制遵循重定向并下拉/Home/About页面的 HTML。
  5. Your Javascript redirect callback is invoked where data represents the response to the /Home/About page (the text/html response).调用您的 Javascript redirect回调,其中data表示对/Home/About页面的响应( text/html响应)。
  6. Finally, whilst still on the /Account/Login page , the page is reloaded as described above.最后,当仍然在/Account/Login页面时,页面被重新加载,如上所述。

Due to how you've set up the jQuery selector shown in the first code snippet, you can simply add the no-ajax class to your login form and it will behave as expected.由于您设置了第一个代码片段中显示的 jQuery 选择器的方式,您只需将no-ajax类添加到您的登录表单,它就会按预期运行。

For anyone who might be having the same problem, for me, options.ExpireTimeSpan was set to 5 seconds.对于可能遇到同样问题的任何人,对我来说,options.ExpireTimeSpan 设置为 5 秒。 Changing to something like 4 hours fixed the problem.更改为 4 小时之类的内容可以解决问题。 在此处输入图片说明

You should also call services.AddAuthentication in the ConfigureServices method in you start-up file.您还应该在启动文件的ConfigureServices方法中调用services.AddAuthentication

The call to AddAuthentication should be configured to your application needs.应根据您的应用程序需要配置对AddAuthentication的调用。 (Cookie, External Logins, etc.) (Cookie、外部登录等)

Even though you are not migrating from 1.x to 2.x the following article I find it very useful: Migrate authentication and Identity to ASP.NET Core 2.0 .尽管您没有从 1.x 迁移到 2.x,但我发现以下文章非常有用: Migrate authentication and Identity to ASP.NET Core 2.0

You haven't used this middleware in Configure method of startup class.你还没有在启动类的Configure方法中使用过这个中间件。

app.UseAuthorization();

Place this middleware after app.UseAuthentication();将此中间件app.UseAuthentication();之后app.UseAuthentication();

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM