.NET MVC跨域请求导致用户凭证失效问题的解决
最近在项目中遇到一个可奇怪的问题,在同一台服务器用不同的端口部署了两个系统A和B。其中B系统的一个功能去请求A系统的方法。
首先这个地方涉及了一个跨域的问题,通过对请求头里添加Access-Control-Allow-Origin解决了这个问题。这两个系统都是基于.net mvc中的form认证进行登录的,现在遇到
的问题就是,在同一个浏览器上两个系统都登录后,B系统的一个功能跨域去请求A系统的一个方法,能正常请求,请求的结果也是正常的,但在这个请求完成后A和B的认证都失效了
需要重新登录才行。接下来我就围绕着这一问题,来详细的写出问题排查的过程
一.FormsAuthenticationTicket 类
FormsAuthenticationTicket 类用于创建一个对象,该对象表示 forms 身份验证用于标识经过身份验证的用户的身份验证票证。 Forms 身份验证票证的属性和值与存储在 cookie 或 URL 中的加密字符串进行转换。FormsAuthentication 类提供 Encrypt 方法来创建一个字符串值,该字符串值可以存储在一个 cookie 中,也可以存储在 FormsAuthenticationTicket的 URL 中。 FormsAuthentication 类还提供了一个 Decrypt 方法,用于根据从 forms 身份验证 cookie 或 URL 检索到的加密的身份验证票证创建 FormsAuthenticationTicket 对象。这是官网给出的定义,通俗一点这个类就是一个身份验证的票证,用来记录用户的登录信息。当用户请求时它会进行验证。
二.Form认证的原理
1.用户输入密码进行登录
2.登录成功后,根据用户产出一个FormsAuthenticationTicket票据用来记录用户的登录
3.FormsAuthentication 的Encrypt进行加密FormsAuthenticationTicket
4.将加密的内容生成cookie,并进行返回
5.用户访问时将带着生成的cookie,服务端先解密,根据FormsAuthenticationTicket还原登录信息
6.设置HttpContext.User
FormsAuthenticationTicket是一个身份验证的票证,我们可以用两种方法来生成FormsAuthenticationTicket
1.直接new 一个
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1, username, DateTime.Now, DateTime.Now.AddMinutes(30), isPersistent, userData, FormsAuthentication.FormsCookiePath); // Encrypt the ticket. string encTicket = FormsAuthentication.Encrypt(ticket); // Create the cookie. Response.Cookies.Add(new HttpCookie(FormsAuthentication.FormsCookieName, encTicket)); // Redirect back to original URL. Response.Redirect(FormsAuthentication.GetRedirectUrl(username, isPersistent));
2.FormsAuthentication.SetAuthCookie();
//FormsAuthentication.SetAuthCookie() 中有一个GetAuthCookie能帮助我们生成FormsAuthenticationTicket private static HttpCookie GetAuthCookie(string userName, bool createPersistentCookie, string strCookiePath, bool hexEncodedTicket) { Initialize(); //完成初始化,读取web.config中的authentication节点,进行相应的设置(包括过期时间和登录地址以及form名称) if (userName == null) { userName = string.Empty; } if (strCookiePath == null || strCookiePath.Length < 1) { strCookiePath = FormsCookiePath; } DateTime utcNow = DateTime.UtcNow; DateTime expirationUtc = utcNow.AddMinutes(_Timeout); FormsAuthenticationTicket formsAuthenticationTicket = FormsAuthenticationTicket.FromUtc(2, userName, utcNow, expirationUtc, createPersistentCookie, string.Empty, strCookiePath); //初始化FormsAuthenticationTicket string text = Encrypt(formsAuthenticationTicket, hexEncodedTicket); //加密FormsAuthenticationTicket if (text == null || text.Length < 1) { throw new HttpException(SR.GetString("Unable_to_encrypt_cookie_ticket")); } HttpCookie httpCookie = new HttpCookie(FormsCookieName, text);//创建一个cookie,先剧透一下我们的问题也就是这里引起的。这里的FormsCookieName其实就是读取的authentication节点下,forms节点的名称 httpCookie.HttpOnly = true; httpCookie.Path = strCookiePath; httpCookie.Secure = _RequireSSL; if (_CookieDomain != null) { httpCookie.Domain = _CookieDomain; } if (formsAuthenticationTicket.IsPersistent) { httpCookie.Expires = formsAuthenticationTicket.Expiration; //设置cookie的过期时间 } httpCookie.SameSite = _cookieSameSite; return httpCookie; //返回根据FormsAuthenticationTicket生成的cookie }
三.回到我们的问题
经过上面分析Form认证的原理,我们了解到form认证的大致流程。那我们遇到的问题是在那个环节出错了呢。伦理上来讲,就算我是跨域请求但也不会影响到我的认证吧。经排查发现我们的问题出在HttpCookie httpCookie = new HttpCookie(FormsCookieName, text);这个地方。生成cookie的时候根据FormsCookieName进行生成。A系统和B系统都是Form认证,而且web.config文件下生成的forms配置节点的name也都是用的默认的_authTicket,也就是说A系统和B系统的FormsCookieName是相同的。这就会导致在同一浏览器上,两个系统都登录会产生两个cookie,跨域请求完成后,会拿着我们A系统的cookie去请求B系统,导致认证异常所以要重新登录。
解决方案就是:
1.修改将两个系统forms节点的name修改成不同的就能避免这个问题
2.使用不同的浏览器进行访问