c# - Google chrome 在后续调用中丢失 MVC Auth Cookie(Set-Cookie 指令)

标签 c# asp.net-mvc cookies asp.net-core-mvc

我正在使用 ASP.net Core 1、MVC 6。我正在使用 SignInManager 和 UserManager,从另一个 MVC 应用程序(Web API 登录方法实际上称为来自 Jquery Ajax 请求)。

在 IE 中,我调用了 Login 方法,成功后,它会给我一个带有 ASP.net 身份验证 cookie 的 Set-Cookie 响应。然后我可以看到后续请求附加了 ASP.net 身份验证 cookie。

在 chrome 中,Set-Cookie 指令在响应中返回,但后续请求没有附加 cookie。

为什么会这样?

我能看到的唯一区别是,在 Chrome 中,发送了一个飞行前 OPTIONS 请求,但我已经在 web api 的 startup.cs 文件中处理了它,并且本质上是忽略它。

互联网浏览器

我对登录 web api 的请求如下所示:

   Accept */*
   Accept-Encoding gzip, deflate
   Accept-Language en-IE
   Cache-Control no-cache
   Connection Keep-Alive
   Content-Length 246
   Content-Type application/x-www-form-urlencoded; charset=UTF-8
   Cookie BeaeN4tYO5M=CfDJ8KMNkK4F2ylMlo1LFxNzxWLNDECVWfhxBYRQrw_MkNQBrVIwfO6FoMIMqg1PP-nZa8Dhp3IV1ZS1uXKpknUDYegiMlEvFaNG-wqUXErvQ5wkMMc_HBI88j-7bCbD2Q7P_B6fEQOQSTKHoL5sTcH0MoM
   DNT 1
   Host localhost:44338
   Referer https://localhost:44356/
   Request POST /api/account/Login HTTP/1.1
   User-Agent Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko
   X-ACL-Key 4A6F0007-95E7-4423-B786-6FBF981799FE

这样的响应:

   Response HTTP/1.1 200 OK
   Cache-Control no-cache
   Pragma no-cache
   Content-Type application/json; charset=utf-8
   Expires -1
   Server Kestrel
   Set-Cookie oAuthInterop=CfDJ8Asqo6qO2cNHlXpsdNLsuoQWhLxXcnaNkAMTB-VvpkMRIz2AiM_7feoIM29gza_zZz97qaE6TKdqK8y1jDPjDDyiiMdOMiuCmCoV5X4IQ9xtHvpGgmFoxOSiYFVeVOBbHsLx4BccL647F9sJ07M55zvjMx_7wrt32omhONH64vmc12P3nepwZjNSIFYfom1U0Z4r4EX_0tZjKRH7FrdvO0PI2iY5SMaKhCcBw1QXpQHSUxL6Hm-Wr8Q46gFAYoa6YffJV0Rx80FvJHmr1LMAA6PAF0dU_DzNdRVHdXm14t_nbfl-6xb6o7WQN259moUhkT1ZQ9CZsYwWvn7VBmpjfIXNJvIu0FDnRaHnNMrj3uN77_cAMdO3OcyCuy-CAKJ9c-0PxKToStb9juGSNa9ClpVQPADzpUxFqxZU029AXBPavXQK2Ezvy7YT4FwCkL8TEf5AnB5hfOZ5YCBlqD30n2heMdHDbXRHpxeaQB4aoY_6uSpJ3cPazBDsbvGi4fV2-0g5NvoTGgJUXa5p4UntRmuiJ2tZHbMmEjXzf-GV6QtTFIhseKsS3n6TMX68yqQOhYOzxvHdJXPjYxvjmm6-vJw5w2FDgiEXoQJQ7qaSmGzRwOA_cE4VBV_RhzrZELmp3A; path=/; secure; httponly
   X-SourceFiles =?UTF-8?B?QzpcVXNlcnNcUm9iZXJ0XERlc2t0b3BcSEJFIE1hbmFnZXJcTUFJTlxCbHVlem9uZSBXZWJBcGlcc3JjXEJ6LkFwcGxpY2F0aW9uXEJ6LkFwcGxpY2F0aW9uLkFwaVx3d3dyb290XGFwaVxhY2NvdW50XExvZ2lu?=
   X-Powered-By ASP.NET
   Access-Control-Allow-Methods GET,PUT,POST,DELETE
   Access-Control-Allow-Headers Content-Type,x-xsrf-token,X-ACL-Key
   Date Fri, 06 May 2016 14:23:22 GMT
   Content-Length 16

后续测试 web api 调用 (IsLoggedIn):

   Request GET /api/account/IsLoggedIn HTTP/1.1
   X-ACL-Key 4A6F0007-95E7-4423-B786-6FBF981799FE
   Accept */*
   Referer https://localhost:44356/
   Accept-Language en-IE
   Accept-Encoding gzip, deflate
   User-Agent Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko
   Host localhost:44338
   DNT 1
   Connection Keep-Alive
   Cache-Control no-cache
   Cookie BeaeN4tYO5M=CfDJ8KMNkK4F2ylMlo1LFxNzxWLNDECVWfhxBYRQrw_MkNQBrVIwfO6FoMIMqg1PP-nZa8Dhp3IV1ZS1uXKpknUDYegiMlEvFaNG-wqUXErvQ5wkMMc_HBI88j-7bCbD2Q7P_B6fEQOQSTKHoL5sTcH0MoM; oAuthInterop=CfDJ8Asqo6qO2cNHlXpsdNLsuoQWhLxXcnaNkAMTB-VvpkMRIz2AiM_7feoIM29gza_zZz97qaE6TKdqK8y1jDPjDDyiiMdOMiuCmCoV5X4IQ9xtHvpGgmFoxOSiYFVeVOBbHsLx4BccL647F9sJ07M55zvjMx_7wrt32omhONH64vmc12P3nepwZjNSIFYfom1U0Z4r4EX_0tZjKRH7FrdvO0PI2iY5SMaKhCcBw1QXpQHSUxL6Hm-Wr8Q46gFAYoa6YffJV0Rx80FvJHmr1LMAA6PAF0dU_DzNdRVHdXm14t_nbfl-6xb6o7WQN259moUhkT1ZQ9CZsYwWvn7VBmpjfIXNJvIu0FDnRaHnNMrj3uN77_cAMdO3OcyCuy-CAKJ9c-0PxKToStb9juGSNa9ClpVQPADzpUxFqxZU029AXBPavXQK2Ezvy7YT4FwCkL8TEf5AnB5hfOZ5YCBlqD30n2heMdHDbXRHpxeaQB4aoY_6uSpJ3cPazBDsbvGi4fV2-0g5NvoTGgJUXa5p4UntRmuiJ2tZHbMmEjXzf-GV6QtTFIhseKsS3n6TMX68yqQOhYOzxvHdJXPjYxvjmm6-vJw5w2FDgiEXoQJQ7qaSmGzRwOA_cE4VBV_RhzrZELmp3A

这样的响应:

   Response HTTP/1.1 200 OK
   Content-Type application/json; charset=utf-8
   Server Kestrel
   X-SourceFiles =?UTF-8?B?QzpcVXNlcnNcUm9iZXJ0XERlc2t0b3BcSEJFIE1hbmFnZXJcTUFJTlxCbHVlem9uZSBXZWJBcGlcc3JjXEJ6LkFwcGxpY2F0aW9uXEJ6LkFwcGxpY2F0aW9uLkFwaVx3d3dyb290XGFwaVxhY2NvdW50XElzTG9nZ2VkSW4=?=
   X-Powered-By ASP.NET
   Access-Control-Allow-Methods GET,PUT,POST,DELETE
   Access-Control-Allow-Headers Content-Type,x-xsrf-token,X-ACL-Key
   Date Fri, 06 May 2016 14:23:22 GMT
   Content-Length 68

Chrome

我对登录 web api 的请求如下所示:

 POST /api/account/Login HTTP/1.1
 Host: localhost:44338
 Connection: keep-alive
 Content-Length: 246
 Accept: */*
 Origin: https://localhost:44356
 Content-Type: application/x-www-form-urlencoded; charset=UTF-8
 User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.94 Safari/537.36
 X-ACL-Key: 4A6F0007-95E7-4423-B786-6FBF981799FE
 Referer: https://localhost:44356/
 Accept-Encoding: gzip, deflate
 Accept-Language: en-GB,en-US;q=0.8,en;q=0.6

这样的响应:

   HTTP/1.1 200 OK
   Cache-Control: no-cache
   Pragma: no-cache
   Content-Type: application/json; charset=utf-8
   Expires: -1
   Vary: Origin
   Server: Kestrel
   Set-Cookie: oAuthInterop=CfDJ8Asqo6qO2cNHlXpsdNLsuoRvlRjfUBWrkt3W3NzBJIoFYA6DcQivnfYmZV2O5xuiqpd75oRjZ-JeHBcjiOK0HoFJQ9f61RyJ2HDeuCNmQk0H-pA3Lzs5ft_F49dpQt0kFn3_-FzEh5-NScCbY4N6TiuYlWY4VSoKsdJJ91k7Z4LQO-0Wm3cZ6HfX0E6pLzGG4lWaZGuV-gOsVCRygR5nv_O_YpWwfaLsT_51aX6fNXVSotU6MECEkFdfWseqOGyYVj7KJrxY2mPwksE0XGACs12TnmfJzCABrzd06FnTPy3RuqJF2IWOobX6ZAHGMoAVFR07mhy9gMPyaHQ12RKmhBhZSXE-Yi3BHow2ER9d2Niligx7JjwYR7UfHFHWJdoYzewLRkZZGE5pw67O710hYyA2UCM2ODB9l9x-WDQ1A_3xjxu2Mrkp0lrF0V-h3y6V2gzEP9RyQAjDISEEZQqvb-GzfZrsRzzQcMn0TMhq5_LUKkX3AScSGRiarBzZ2O9Af3jzwTmN1BciJknJwMKRefq_zrXH7kymCD1kJM89aGkswqp2bycMQjlsjqg5k8EEhv8u1kLA7hA9NyE2ZaamB1PAWYz4NXi3Agccgw83nFi4bs6VE8ZLnyZFEwxdyEGyvQ; path=/; secure; httponly
   Access-Control-Allow-Origin: https://localhost:44356
   Access-Control-Allow-Credentials: true
   X-SourceFiles: =?UTF-8?B?QzpcVXNlcnNcUm9iZXJ0XERlc2t0b3BcSEJFIE1hbmFnZXJcTUFJTlxCbHVlem9uZSBXZWJBcGlcc3JjXEJ6LkFwcGxpY2F0aW9uXEJ6LkFwcGxpY2F0aW9uLkFwaVx3d3dyb290XGFwaVxhY2NvdW50XExvZ2lu?=
   X-Powered-By: ASP.NET
   Access-Control-Allow-Methods: GET,PUT,POST,DELETE
   Access-Control-Allow-Headers: Content-Type,x-xsrf-token,X-ACL-Key
   Date: Fri, 06 May 2016 12:59:36 GMT
   Content-Length: 16

后续测试 web api 调用 (IsLoggedIn):

GET /api/account/IsLoggedIn HTTP/1.1
   Host: localhost:44338
   Connection: keep-alive
   Accept: */*
   Origin: https://localhost:44356
   User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.94 Safari/537.36
   X-ACL-Key: 4A6F0007-95E7-4423-B786-6FBF981799FE
   Referer: https://localhost:44356/
   Accept-Encoding: gzip, deflate, sdch
   Accept-Language: en-GB,en-US;q=0.8,en;q=0.6

这样的响应:

HTTP/1.1 401 Unauthorized
   Content-Length: 0
   Content-Type: text/plain; charset=utf-8
   Server: Kestrel
   X-SourceFiles: =?UTF-8?B?QzpcVXNlcnNcUm9iZXJ0XERlc2t0b3BcSEJFIE1hbmFnZXJcTUFJTlxCbHVlem9uZSBXZWJBcGlcc3JjXEJ6LkFwcGxpY2F0aW9uXEJ6LkFwcGxpY2F0aW9uLkFwaVx3d3dyb290XGFwaVxhY2NvdW50XElzTG9nZ2VkSW4=?=
   X-Powered-By: ASP.NET
   Access-Control-Allow-Methods: GET,PUT,POST,DELETE
   Access-Control-Allow-Headers: Content-Type,x-xsrf-token,X-ACL-Key
   Date: Fri, 06 May 2016 12:59:43 GMT

我的网络 API Controller 代码如下所示:

[Authorize]
    [EnableCors("AllowAll")]
    [Route("api/[controller]")]
    public class AccountController : Controller
    {
        private readonly UserManager<ApplicationUser> _userManager;
        private readonly SignInManager<ApplicationUser> _signInManager;

        public AccountController(UserManager<ApplicationUser> userManager, SignInManager<ApplicationUser> signInManager)
        {
            _userManager = userManager;
            _signInManager = signInManager;
        }

        [HttpPost("login")]
        [AllowAnonymous]
        public async Task<IActionResult> Login(UserLogin model)
        {
            if (ModelState.IsValid) {
                var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false);

                if (result.Succeeded) {
                    return Json(new { success = true });
                }
                if (result.RequiresTwoFactor) {
                    return Json(new { success = false, errType = 1 });
                }
                if (result.IsLockedOut) {
                    return Json(new { success = false, errType = 2 });
                } else {
                    ModelState.AddModelError(string.Empty, "Invalid login attempt.");
                    return Json(new { success = false, errType = 3 });
                }
            }

            return Json(new { success = false, errType = 0 });
        }

        [HttpGet("IsLoggedIn")]
        public IActionResult IsLoggedIn()
        {
            return Json(new {
                loggedon = (HttpContext.User.Identity.Name != null && HttpContext.User.Identity.IsAuthenticated),
                isauthenticated = HttpContext.User.Identity.IsAuthenticated,
                username = HttpContext.User.Identity.Name
            });
        }
    }

我的 web api 的 Startup.cs 看起来像这样:

public class Startup
    {
        public static int SessionLength { get; private set; }
        private string Connection;

        public Startup(IHostingEnvironment env)
        {
            // Set up configuration sources.
            var builder = new ConfigurationBuilder()
                .AddJsonFile("appsettings.json")
                .AddEnvironmentVariables();
            Configuration = builder.Build();

            SessionLength = 30;
        }

        public IConfigurationRoot Configuration { get; set; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            // Get the configured connection string.
            Connection = Configuration["Data:DefaultConnection:ConnectionString"];

            var userStore = new CustomUserStore();
            var roleStore = new CustomRoleStore();
            var userPrincipalFactory = new CustomUserPrincipalFactory();

            services.AddInstance<IUserStore<ApplicationUser>>(userStore);
            services.AddInstance<IRoleStore<ApplicationRole>>(roleStore);
            services.AddInstance<IUserClaimsPrincipalFactory<ApplicationUser>>(userPrincipalFactory);

            services.AddIdentity<ApplicationUser, ApplicationRole>(options => {
                options.Cookies.ApplicationCookie.Events = new CookieAuthenticationEvents() {
                    OnRedirectToAccessDenied = ctx =>
                    {
                        if (ctx.Response.StatusCode == (int)HttpStatusCode.Unauthorized || ctx.Response.StatusCode == (int)HttpStatusCode.Forbidden) {
                            return Task.FromResult<object>(null);
                        }
                        ctx.Response.Redirect(ctx.RedirectUri);
                        return Task.FromResult<object>(null);
                    },
                    OnRedirectToLogin = ctx =>
                    {
                        if (ctx.Response.StatusCode == (int)HttpStatusCode.Unauthorized || ctx.Response.StatusCode == (int)HttpStatusCode.Forbidden) {
                            return Task.FromResult<object>(null);
                        }
                        ctx.Response.Redirect(ctx.RedirectUri);
                        return Task.FromResult<object>(null);
                    }
                };

                //options.Cookies.ApplicationCookie.CookieHttpOnly = false;
                options.Cookies.ApplicationCookieAuthenticationScheme = "ApplicationCookie";
                options.Cookies.ApplicationCookie.AuthenticationScheme = "ApplicationCookie";
                options.Cookies.ApplicationCookie.CookieName = "oAuthInterop";
                options.Cookies.ApplicationCookie.AutomaticChallenge = true;
                options.Cookies.ApplicationCookie.AutomaticAuthenticate = true;
                options.Cookies.ApplicationCookie.DataProtectionProvider = new DataProtectionProvider(new DirectoryInfo("d:\\development\\artefacts"),
                    configure =>
                    {
                        configure.SetApplicationName("TestAuthApp");
                        //configure.ProtectKeysWithCertificate("thumbprint");
                    });
                options.Cookies.ApplicationCookie.ExpireTimeSpan = TimeSpan.FromMinutes(SessionLength);

            }).AddDefaultTokenProviders();

            // Add framework services.
            services.AddMvc();

            // Add cross site calls.
            //TODO: implement with better security instead of allowing everything through.
            services.AddCors(options => options.AddPolicy("AllowAll", p => p.AllowAnyOrigin()
                                                                            .AllowAnyMethod()
                                                                            .AllowAnyHeader().AllowCredentials()));
        }

        // 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)
        {
            loggerFactory.AddConsole(Configuration.GetSection("Logging"));
            loggerFactory.AddDebug();

            app.UseIISPlatformHandler(options => options.AuthenticationDescriptions.Clear());

            app.UseStaticFiles();

            app.UseIdentity();

            app.UseMvc();

        }
    }

最佳答案

一个疯狂的猜测是你没有设置 withCredentials通过 ajax 从 javascript 发出跨域请求时,在 XMLHttpRequest 上标记。该标志基本上控制是否在跨域请求中包含凭据(例如 cookies、授权 header 或客户端证书)。为什么它仍然适用于 IE?不完全确定,但可能是因为此标志的正确实现仅出现在 IE10 中,您可能会使用其他版本的 IE。如果您使用 jquery 发出请求,请参阅 here如何设置这个标志。

如果不是这种情况,请包含您的客户端代码 + Chrome 的 OPTIONS 请求的请求和响应 header 。

关于c# - Google chrome 在后续调用中丢失 MVC Auth Cookie(Set-Cookie 指令),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37075533/

相关文章:

asp.net-mvc - 使用 Windows Azure Active Directory (WAAD) 转变声明

php - cookie中要写什么?

c# - 如何调试 DebuggerTypeProxy 代理?

c# - WebView2 DevToolsProtocolEvent 未引发

c# - 当 URL 包含特定查询参数时,如何绕过所有其他 MVC 路由?

javascript - ngx-cookie - 服务器端渲染中的空白 cookie 对象

ssl - 这是有效的 Set-Cookie 语法吗

c# - 使用 System::DateTime 参数调用 C++/CLI 方法需要 'ValueType' 作为参数?

asp.net-mvc - 卡住了创建 "security trimmed"html.ActionLink 扩展方法

c# - Asp.net MVC 批量打印