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;

猜你喜欢

转载自www.cnblogs.com/mosakashaka/p/13205620.html
今日推荐