我无法让页面从 cookie 加载用户角色

Posted

技术标签:

【中文标题】我无法让页面从 cookie 加载用户角色【英文标题】:I can't get the pages to load user roles from cookies 【发布时间】:2017-05-20 07:14:29 【问题描述】:

我在 iis7 上采用了曾经是经过广告验证的网站并将其迁移到 iis 8.5。在此过程中,我尝试切换到表单身份验证。并非站点上的每个页面都是 aspx 页面 - 那里有静态内容和文件,因为它是一个 Intranet 站点 - 但基本表单身份验证有效,因为我将应用程序池设置为集成模式并将 web.config 设置为使用表单身份验证。我很高兴如果您尝试加载 pdf 文件,它会确保您首先登录到 cas 服务器。

我的问题在于角色 - 我已经在登录页面中设置了 cookie 以在其中包含角色(我从 CAS 属性中提取),当我在该页面上执行 User.IsInRole() 时,它说是,人有角色。但是,如果我移动到一个新页面,不管是否是 aspx,这个人仍然是经过身份验证的,但没有角色。我试图避免使用角色管理器,因为我不想与提供者打交道(cas 服务器正在处理身份验证部分),但如果必须,我将使用角色管理器。到目前为止,我运气不好。我相信角色没有从 cookie 中加载,因为我把相同的 (User.IsInRole("staff"))?"True":"False";如下所示的主页中的行,但在登录页面上它返回true,在主页上它返回false。

我将附加我的网络配置和登录页面代码的相关部分。

Web 配置(仅通告文件夹使用角色 atm):

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <system.webServer>
    <modules>
      <!-- Re-add auth modules (in their original order) to run for all static and dynamic requests -->
      <remove name="FormsAuthentication" />
      <add name="FormsAuthentication" type="System.Web.Security.FormsAuthenticationModule" />
      <remove name="DefaultAuthentication" />
      <add name="DefaultAuthentication" type="System.Web.Security.DefaultAuthenticationModule" />
      <remove name="RoleManager" />
      <add name="RoleManager" type="System.Web.Security.RoleManagerModule" />
      <remove name="UrlAuthorization" />
      <add name="UrlAuthorization" type="System.Web.Security.UrlAuthorizationModule" />
    </modules>
  </system.webServer>
  <system.web>
    <authentication mode="Forms" />
    <authorization>
      <deny users="?" />
      <allow users="*" />
    </authorization>
  </system.web>
  <location path="announce">
    <system.web>
      <authorization>
        <allow roles="staff, faculty" />
        <deny users="*" />
      </authorization>
    </system.web>
  </location>
</configuration>

后面登录页面代码的相关部分(重定向被注释掉,所以我可以看到他们的角色):

  // Second time (back from CAS) there is a ticket= to validate
  // CAS 3 doesn't list attributes through /serviceValidate, so lets build a SAML request and use that instead.
  string SAMLstr = "<?xml version='1.0'?><SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\">" +
    "<SOAP-ENV:Header/><SOAP-ENV:Body><samlp:Request xmlns:samlp=\"urn:oasis:names:tc:SAML:1.0:protocol\" " +
    "MajorVersion=\"1\" MinorVersion=\"1\" RequestID=\"_" + Request.ServerVariables["REMOTE_HOST"] + "." + DateTime.Now.Ticks + "\"" +
    "IssueInstant=\"" + DateTime.Now + "\"><samlp:AssertionArtifact>" + tkt + "</samlp:AssertionArtifact>" +
    "</samlp:Request></SOAP-ENV:Body></SOAP-ENV:Envelope>";
  string validateurl = CASHOST + "samlValidate?TARGET=" + service + "&ticket=" + tkt;
  // Set up an xml document to catch the SAML responce and then parse it.
  XmlDocument xmlDoc = new XmlDocument();
  xmlDoc.LoadXml(new WebClient().UploadString(validateurl, SAMLstr));
  // The response uses multiple namespaces, whee!
  var nsmgr = new XmlNamespaceManager(xmlDoc.NameTable);
  nsmgr.AddNamespace("s", "http://schemas.xmlsoap.org/soap/envelope/");
  nsmgr.AddNamespace("nsR", "urn:oasis:names:tc:SAML:1.0:protocol");
  nsmgr.AddNamespace("nsA", "urn:oasis:names:tc:SAML:1.0:assertion");
  // Let's see if they authed successfully:
  if (xmlDoc.SelectSingleNode("/s:Envelope/s:Body/nsR:Response/nsR:Status/nsR:StatusCode", nsmgr).Attributes["Value"].Value == ("samlp:Success")) 
    // They authenticated successfully; populate their id and roles.
    string netid = xmlDoc.SelectSingleNode("/s:Envelope/s:Body/nsR:Response/nsA:Assertion/nsA:AuthenticationStatement/nsA:Subject/nsA:NameIdentifier", nsmgr).InnerText; 
    // Grab the "memberOf" attriubute - it contains a child node for each group distinguished name.
    XmlNode memberOf = xmlDoc.SelectSingleNode("/s:Envelope/s:Body/nsR:Response/nsA:Assertion/nsA:AttributeStatement/nsA:Attribute[@AttributeName='memberOf']", nsmgr);
    // I don't want to iterate through that, so I'll just grab all the text and split it with a regex to split up the OUs and DCs.
    string[] groups = Regex.Split(memberOf.InnerText.ToLower().Substring(3), "cn=(.*?),ou=");
    HttpContext.Current.User = new GenericPrincipal(new GenericIdentity(netid),new string[] "staff", "union", "UTech");

    // Splicing together the groups names but droping the OUs (every other entry)
    string roles=groups[1];
    for (int x=3;x<groups.Length;x+=2) roles+="|"+groups[x];
    // Let's bake cookies with the netid, roles, and persistence.  1: Make the ticket.
    FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1, netid, DateTime.Now, DateTime.Now.AddMinutes(2880), isPersistent, roles, FormsAuthentication.FormsCookiePath);
    // 2. Encrypt the ticket.
    string hash = FormsAuthentication.Encrypt(ticket);
    // 3. Turn the encrypted ticket into a cookie!
    HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, hash);
    // 4. Set expiration on the cookie to match the ticket.
    if (ticket.IsPersistent) cookie.Expires = ticket.Expiration;
    // 5. Put the cookie in the jar.
    Response.Cookies.Add(cookie);
    // We're done!  Send them home.
    // [TEMP, next 3 lines are debug] Response.Redirect(service + Session["backto"], true);
    Label1.Text = "Welcome, "+netid+"!";
    Label2.Text = roles;
    Label3.Text = (User.IsInRole("staff"))?"True":"False";

【问题讨论】:

【参考方案1】:

我不想回答自己的问题,但经过一周的搜索和阅读教程后,我解决了问题。

上述问题中的cookies和forms认证没问题;未加载角色的原因是因为我的 global.asax 中没有 PostAuthenticateRequest (实际上根本没有 global.asax)。 This question 最有帮助,不是来自接受的答案,而是来自 P.Hoxha 的附加项目。其中有 0 人投了票,但我投了赞成票。 Microsoft 教程 here 也有助于更正我所投票的回复中不起作用的内容。

添加 global.asax 通过捕获 PostAuthenticateRequest 并运行以下代码来加载角色解决了问题:

<%@ Application Language="C#" %>
<%@ Import Namespace="System.Security.Principal" %>
<%@ Import Namespace="System.Threading" %>

<script runat="server">
void Application_OnPostAuthenticateRequest(object sender, EventArgs e) 
  HttpCookie authCookie = Context.Request.Cookies[FormsAuthentication.FormsCookieName];
  if (authCookie == null || authCookie.Value == "") return;
  FormsAuthenticationTicket authTicket;
  try 
    authTicket = FormsAuthentication.Decrypt(authCookie.Value);
   catch 
    return;
  

  // retrieve roles from UserData
  string[] roles = authTicket.UserData.Split('|');

  if (Context.User != null) Context.User = new GenericPrincipal(Context.User.Identity, roles);

</script>

【讨论】:

以上是关于我无法让页面从 cookie 加载用户角色的主要内容,如果未能解决你的问题,请参考以下文章

如果页面仅加载一次,如何在 SPA 中获取 Laravel cookie

如何让js在页面加载后只执行一次,也就是再次刷新当前页,不再执行js

Instagram API 登录错误 - 无法加载此页面

网站性能:从 cookie 到本地存储

如何根据用户角色有条件地显示 JSP 页面的元素

让用户在暗模式和亮模式之间进行选择(保存每个页面的设置,cookie?)