zoukankan      html  css  js  c++  java
  • windows form认证简介

    前言

    .net framework不再进行演进,.net的form认证也被各种OAuth/OpenId认证流程取代。但是旧系统很多可能还是会使用这一种方式。

    文章参考了微软官方的这两篇文章:
    第一篇
    第二篇

    介绍

    .net框架在处理请求的每个阶段,都会触发各种事件,每个事件又有对应的处理模块。涉及授权的是这两个模块:

    • FormsAuthenticationModule:从用户ticket种获取用户信息,设置到HttpContext中。而ticket默认是在Cookie中的。
    • UrlAuthorizationModule:检查指定的地址是否获得授权,检查规则一般是在配置文件中指定的。如果未登录/授权,会返回HTTP 401 Unauthorized

    当未获得授权时,FormsAuthenticationModule会捕获401错误,将用户引导到登录页登录。

    在登录页,一般通过校验用户名密码,生成用ticket,存入cookie中,接下来就会跳转到之前未认证的页面。

    来自官方的流程图:

    认证过程

    基本使用(Cookie设置,IPrincipal和IIDentity)

    使能

    通过在web.config文件配置

    <configuration>
        <system.web>
            ... Unrelated configuration settings and comments removed for brevity ...
            <!--
                The <authentication> section enables configuration 
                of the security authentication mode used by 
                ASP.NET to identify an incoming user. 
            -->
            <authentication mode="Forms" />
        </system.web>
    </configuration>
    

    登录页

    用户名密码的校验可以有各种方法,比如数据库、三方系统,这里不做介绍。

    如果校验成功,就要让用户登录,而所谓登录就是为该用户生成票据ticket,在下次访问系统时,将ticket传入系统,这样FormsAuthenticationModule就会将用户信息识别并存到这个请求相应的上下文HttpContext中,供后续模块使用。

    System.Web.Security命名控件提供了三个基本方法,帮助登录。

    • GetAuthCookie(username, persistCookie) 由用户名创建一个ticket,并由该ticket创建一个Cookie,persistCookie控制的cookie是否持久有效。注意,此时生成的Cookie需要手动加入到响应中,才会生效
    • SetAuthCookie(username, persistCookie) 除了GetAuthCookie的功能,该函数直接会将生成的Cookie添加到请求响应的Cookie中。
    • RedirectFromLoginPage(username, persistCookie),除了上一步SetAuthCookie的功能外,该函数还会直接跳转到returnUrl参数标识的页面

    使用

    Request对象中的IsAuthenticated属性可以用来判断用户是否登录。

    当前登录用户的信息,可以通过HttpContext.Current.User获取,这个值是FormsAuthenticationModule设置的。

    HttpContext.Current.User实际是GenericPrincipal类,而GenericPrincipal扩展了IPrincipal接口。principal有主角的意思(注意不是principle)。这个接口代表了用户信息。而IPrincipal接口又包含两个成员

    • IsInRole(roleName) 判断当前用户是否属于某个组

    • Identity 该类扩展IIdentity接口,代表了用户身份。它又包含几个主要成员
      - AuthenticationType 认证方式,表明身分来源,对于form认证来说,会被设置成Forms
      - IsAuthenticated 是否已认证
      - Name 用户名,来自创建ticket时的参数
      - 而Form认证时,Identity类又被扩展为FormsIdentity,从而包含了一个额外的成员Ticket,可以获取原始的Ticket信息

      这里可以对比`.net core`中的`HttpContext.Current.User`属性,同样实现了`IPrincipal`,但是实现类是`ClaimPrincipal`,.net core切换为了基于`Claim`声明类型的认证方式。而不是像这里Form认证中的基于`Role`角色的认证方式。
      

    配置

    认证基本配置

    在web.config中的<form></form>节点可以通过属性对认证进行扩展配置:

    <authentication mode="Forms">
    
      <forms
    
     propertyName1="value1"
    
     propertyName2="value2"
    
      ...
    
     propertyNameN="valueN"
    
      />
    
    </authentication>
    

    支持的属性:

    • cookieless:对于不支持cookie的情况,ticket可以直接编码在url参数中
    • domain:ticket适用的路径,比如可以仅在网站的http://url/subdomain这一个路径下才发送ticket
    • loginUrl:修改FormsAuthenticationModule跳转的登录页面地址
    • name: 存储的cookie的名称,默认是.ASPXAUTH
    • protection:控制ticket是否加密、校验,默认ALL代表即加密,也增加签名校验,这是最安全、推荐的方式
    • slidingExpiration:ticket会过期,配置这个参数,用户每次登录会刷新过期时间。不过对于cookie来说,cookie默认的机制是过期时间过半才会刷新。
    • timeout:过期时间,默认30分钟。在默认通过cookie存储的情况下,这个值即代表cookie的过期时间,也代表ticket的过期时间
    • 。。。其他一些参数,不一一列举

    安全考虑

    <machineKey>节点下,可以配置ticket安全key参数。

    分为加解密和校验两部分内容

    • decryption和decryptionKey:加解密的算法和密钥
    • validation和validationKey:校验的算法和密钥

    密钥默认都是自动生成,通过算法保证唯一。但是对于集群部署,或者单点登录,ticket要在其他系统使用的情况,就需要在这些系统中,手动配置相同的密钥了。

    扩展使用

    在ticket中增加自定义数据

    之前没有具体提到ticket的内容,ticket实际是FormsAuthenticationTicket类,除了包含有效期、用户名信息以外,还有一个UserData字段,可以在创建时存储一些用户自定义的信息(string类型)。比如有一些数据存在这里,可以不用每次都在数据库重新获取一遍。

    但是设置UserData的方式只能通过构造函数,可能是因为ticket创建时,就是加密和签名好的了,不允许补充数据。而携带UserCode的构造函数,又需要很多注入版本号、过期时间等,所以通常的做法是,先创建一个不带UserData的ticket,然后取出这个ticket的变量,连同我们所需的UserData,创建一个新的Ticket:

     // Query the user store to get this user's User Data
     string userDataString = "My Data";
    
      // Create the cookie that contains the forms authentication ticket
      HttpCookie authCookie = FormsAuthentication.GetAuthCookie(UserName.Text, RememberMe.Checked);
    
      // Get the FormsAuthenticationTicket out of the encrypted cookie
      FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(authCookie.Value);
    
      // Create a new FormsAuthenticationTicket that includes our custom User Data
      FormsAuthenticationTicket newTicket = new FormsAuthenticationTicket(ticket.Version, ticket.Name, ticket.IssueDate, ticket.Expiration, ticket.IsPersistent, userDataString);
    
      // Update the authCookie's Value to use the encrypted version of newTicket
      authCookie.Value = FormsAuthentication.Encrypt(newTicket);
    
      // Manually add the authCookie to the Cookies collection
      Response.Cookies.Add(authCookie);
    

    获取UserData

      // Get User Data from FormsAuthenticationTicket
      FormsIdentity ident = User.Identity as FormsIdentity;
    
      if (ident != null)
      {
    
      FormsAuthenticationTicket ticket = ident.Ticket;
      // This as out UserData!
      string userDataString = ticket.UserData;
      }
    

    自定义Principal

    Form认证默认使用GenericPrincipal,如果想增加自定义属性,我们可以通过UserData属性添加,或者也可以替换GenericPrincipal而使用自定义的类。

    自定义的类CustomPrincipal也实现IPrincipal,可以添加自定义的属性,其中的IDentity属性,也可以用自定义的CustomIDentity替换,只要实现了IIdentity接口

    那么在何时替换GenericPrincipal呢?

    GenericPrincipalFormsAuthenticationModule模块在AuthenticateRequest时间阶段被执行的,而在这个事件之后,asp.net会触发PostAuthenticateRequest事件,于是在这里,可以手动设置所需要的CustomPrincipal类,替换原有的GenericPrincipal

    替换说明

    而注册监听该事件,可以在Global.ascx中:

      void Application_OnPostAuthenticateRequest(object sender, EventArgs e)
      {
    
      // Get a reference to the current User
      IPrincipal usr = HttpContext.Current.User;
    
      // If we are dealing with an authenticated forms authentication request
      if (usr.Identity.IsAuthenticated && usr.Identity.AuthenticationType == "Forms")
      {
    
      FormsIdentity fIdent = usr.Identity as FormsIdentity;
      // Create a CustomIdentity based on the FormsAuthenticationTicket  
    
      CustomIdentity ci = new CustomIdentity(fIdent.Ticket);
      // Create the CustomPrincipal
    
      CustomPrincipal p = new CustomPrincipal(ci);
    
      // Attach the CustomPrincipal to HttpContext.User and Thread.CurrentPrincipal
      HttpContext.Current.User = p;
      Thread.CurrentPrincipal = p;
      }
    
      }
    

    这里需要同时设置HttpContext.Current.UserThread.CurrentPrincipal,这两个值在处理流程中都有可能被用到,需要保持一致。

    这样设置之后,可以像之前获取GenericPrincipal一样,获取CustomPrincipal:

    CustomIdentity ident = User.Identity as CustomIdentity;
    

  • 相关阅读:
    window.onload的一些说明
    关于js函数,方法,对象实例的一些说明
    js之数组,对象,类数组对象
    js之数据类型及类型转换
    浅析JSONP
    php小记
    php下载文件,解压文件,读取并写入新文件
    python 循环列表的同时做删除操作
    Mysql压缩包版zip的安装方法
    django Cookie 和 Session 装饰器修复技术
  • 原文地址:https://www.cnblogs.com/mosakashaka/p/13205620.html
Copyright © 2011-2022 走看看