首页 > 技术文章 > .NET MVC跨域请求导致用户凭证失效问题的解决

HTLucky 2020-04-23 15:14 原文

最近在项目中遇到一个可奇怪的问题,在同一台服务器用不同的端口部署了两个系统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.使用不同的浏览器进行访问

 

推荐阅读