注:这篇文章主要给新手看的,老手们可能会觉得没啥营养,就请绕过吧。

“认证”与“授权”是几乎所有系统中都会涉及的概念,通俗点讲:

认证(authentication) 就是 "判断用户有没有登录?",好比windows系统,没登录就无法使用(不管你是用Administrator或Guest用户,总之要先正确登录后,才能进入系统).

授权(authorization) 就是"用户登录后的身份/角色识别",好比"管理员用户"登录windows后,能安装软件、修改windows设置等所有操作,而Guest用户登录后,只有做有限的操作(比如安装软件就被禁止了).

.net中与"认证"对应的是IIdentity接口,而与"授权"对应的则是IPrincipal接口,这二个接口的定义均在命名空间System.Security.Principal中:

1
2
3
4
5
6
7
8
9
10
11
12
13
using  System;
using  System.Runtime.InteropServices;
namespace  System.Security.Principal
{
     [ComVisible( true )]
     public  interface  IIdentity
     {
            string  AuthenticationType { get ; }
            bool  IsAuthenticated { get ; }
            string  Name { get ; }
     }
}

1
2
3
4
5
6
7
8
9
10
11
12
using  System;
using  System.Runtime.InteropServices;
namespace  System.Security.Principal
{
     [ComVisible( true )]
     public  interface  IPrincipal
     {
           IIdentity Identity { get ; }
           bool  IsInRole( string  role);
     }
}

应该注意到:IPrincipal接口中包含着一个只读的IIdentity,这也跟最开始提到的概念一致:识别身份的前提是先登录,只有登录成功后能进一步确认身份。

用Membership/Role做过asp.net开发的朋友们,看到这二个接口的定义,应该会觉得很眼熟,想想我们在Asp.Net页面中是如何判断用户是否登录以及角色的?

1
2
3
4
5
6
7
8
9
10
11
12
protected  void  Page_Load( object  sender, EventArgs e)
         {
             HttpContext ctx = HttpContext.Current;
             if  (ctx.User.Identity.IsAuthenticated && ctx.User.IsInRole( "管理员" ))
             {
                 //管理员该做的事,就写在这里
             }
             else
             {
                 //Hi,您不是管理员,别胡来!
             }
         }

这段代码再熟悉不过了,没错!membership/role的原理就是基于这二个接口的,如果再对HttpContext.Current.User刨根问底,能发现下面的定义:

即:HttpContext.Current.User本身就是一个IPrincipal接口的实例。有了上面的预备知识,可以直奔主题了,先来一个Console控制台程序测试一下用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
using  System;
using  System.Security.Principal;
using  System.Threading;
namespace  ConsoleTest
{
     class  Program
     {
         static  void  Main( string [] args)
         {
             GenericIdentity _identity = new  GenericIdentity( "chr" );
             GenericPrincipal _principal = new  GenericPrincipal(_identity, new  string [] { "管理员" , "网站会员"  });
             Thread.CurrentPrincipal = _principal; //并非必需,但在winform程序中有很用(后面会提到)
             string  loginName = _principal.Identity.Name;
             bool  isLogin = _principal.Identity.IsAuthenticated;
             bool  isAdmin = _principal.IsInRole( "管理员" );
             bool  isWebUser = _principal.IsInRole( "网站会员" );
             Console.WriteLine( "当前用户: {0}" , loginName);
             Console.WriteLine( "是否已经登录? {0}" , isLogin);
             Console.WriteLine( "是否管理员? {0}" , isAdmin);
             Console.WriteLine( "是否网站会员? {0}" , isWebUser);
             Console.Read();           
         }
     }
}

输出如下:

当前用户: chr
是否已经登录? True
是否管理员? True
是否网站会员? True

一切正常,没什么大不了,但Console默认只是一个单线程的程序,也没有丰富的GUI界面,所以...这个只不过是热身,看下接口定义的几个方法是否管用而已。

这二个接口同样也能用在Winform程序中,下面将创建一个WinForm应用,里面有二个窗口:Form1以及Form2,可以把Form1当成登录界面,而Form2则是程序主窗口,在很多管理软件中,主窗口都要求登录以后才能访问,我们就来模拟一下:

Form1的界面:

Form2更简单:(就一个只读的TextBox)

我想做的事情:在Form1上登录后,看看在Form2中,能否判断出用户已经登录,以及识别出身份。

Form1 中的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
using  System;
using  System.Security.Principal;
using  System.Threading;
using  System.Windows.Forms;
namespace  WinformTest
{
     public  partial  class  Form1 : Form
     {
         public  Form1()
         {
             InitializeComponent();
         }
         private  void  btnLogin_Click( object  sender, EventArgs e)
         {
             if  (txtUserName.Text.Trim() == "" ) {
                 MessageBox.Show( "请输入用户名!" );
                 txtUserName.Focus();
                 return ;
             }
             IIdentity _identity = new  GenericIdentity(txtUserName.Text.Trim());
             IPrincipal _principal = new  GenericPrincipal(_identity, new  string [] { "管理员"  });
             Thread.CurrentPrincipal = _principal; //将其附加到当前线程的CurrentPrincipal
             MessageBox.Show( "登录成功!" );
         }
         private  void  btnShow_Click( object  sender, EventArgs e)
         {
             ( new  Form2()).ShowDialog();
         }
         private  void  btnLogOut_Click( object  sender, EventArgs e)
         {
             Thread.CurrentPrincipal = null ;
             MessageBox.Show( "已经退出!" );
         }
     }
}

Form2中的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
using  System;
using  System.Security.Principal;
using  System.Threading;
using  System.Windows.Forms;
namespace  WinformTest
{
     public  partial  class  Form2 : Form
     {
         public  Form2()
         {
             InitializeComponent();
         }
         private  void  Form2_Load( object  sender, EventArgs e)
         {
             IPrincipal _principal = Thread.CurrentPrincipal;
             if  (_principal.Identity.IsAuthenticated)
             {
                 this .textBox1.Text = "您已经登录,当前用户:"  + _principal.Identity.Name;
                 this .textBox1.Text += Environment.NewLine + "当前角色:"  + (_principal.IsInRole( "管理员" ) ? "管理员"  : "非管理员" );
             }
             else
             {
                 this .textBox1.Text = "您还没有登录" ;
             }
         }
     }
}

测试一下:如果在未登录的情况下,直接点击"Show窗体2",结果如下

如果输入用户名,并点击"登录"后,再点击"Show窗体2",结果如下:

很理想!Form2中直接就能判断用户是否登录,以及当前登录用户的角色。这里有一个关键的细节:

1
Thread.CurrentPrincipal = _principal; //将其附加到当前线程的CurrentPrincipal

在Form1中,将登录后的_principal附加到当前线程的CurrentPrincipal,我们知道:每个程序不管它是不是多线程,总归是有一个默认的主线程的。所以只要把主线程的CurrentPrincipal与登录后的_principal关联起来后,其它任何窗体,都可以直接用它来做判断,如果判断通过,则可以这样或那样(包括创建多线程进行自己的处理),如果判断不通过,则可以拒绝继续操作。

Winform的问题解决了,再来考虑一下Webform,当然,你可以直接使用从Asp.Net2.0就支持的membership/role机制,但membership/role默认只支持sqlserver数据库(通过membership provider for oracle也可以支持oracle,但总有一些数据库不被支持,比如access、mysql、sqlite、db2等),假如你不想把用户名/密码这类信息保存在sqlserver中(甚至不想保存在数据库中,比如:xml),这时候就得开动脑筋了。

其实...就算不用membership/role,上面提到的这二个接口仍然是可以使用的,但有一个问题:winform中,IPrincipal接口的实例可以一直存储在内存中(直到程序退出),所以其它窗口就能继续访问它,以便做进一步的判断,但是在webform中,页面本身是无状态的,一旦服务器输出html到客户端浏览器后,客户端的页面就与服务器再无瓜葛了(你甚至可以离线浏览,前提是不刷新),那么最后的认证信息保存在什么地方呢?

答案就是客户端的浏览器Cookie!所以在WebForm中的做法稍有不同:

创建一个webApplication,里面新建4个页面:login.aspx,logout.aspx,default.aspx,gotoUrl.aspx,这四个页面的作用如下:

login.aspx : 登录页面

logout.aspx: 用来处理用户注销 (非必需,但建议把注销逻辑放在这里,以便任何需要注销的地方重复利用)

default.aspx: 登录完成后的显示页面

gotoUrl.aspx : 登录完成后,用来辅助做页面跳转的页面(非必需,但建议加上)

login.aspx代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Login.aspx.cs" Inherits="LoginTest.Login" %>
<! DOCTYPE  html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
< html  xmlns="http://www.w3.org/1999/xhtml">
< head  runat="server">
     < title ></ title >
</ head >
< body >
     < form  id="form1" runat="server">
     < table >
         < tr >
             < td >用户名:</ td >
             < td >
                 < asp:TextBox  ID="txtUserName" runat="server" style="width:200px"></ asp:TextBox ></ td >
         </ tr >
         < tr >
             < td >密  码:</ td >
             < td >
                 < asp:TextBox  ID="txtPassword" runat="server" TextMode="Password" style="width:200px"></ asp:TextBox >
             </ td >
         </ tr >
         < tr >
             < td ></ td >
             < td >
                 < asp:Button  ID="Button1" runat="server" Text="登 录" onclick="Button1_Click" />
             </ td >
         </ tr >
     </ table >
     </ form >
</ body >
</ html >

后置代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
using  System;
using  System.Web;
using  System.Web.Security;
namespace  LoginTest
{
     public  partial  class  Login : System.Web.UI.Page
     {
         protected  void  Page_Load( object  sender, EventArgs e)
         {
             
         }
         protected  void  Button1_Click( object  sender, EventArgs e)
         {
             string  user = this .txtUserName.Text; //读取用户名
             string  password = this .txtPassword.Text; //读取密码
             if  (ValidateUser(user, password) == true ) //ValidateUser方法用来验证用户合法性的
             {
                 //建立表单验证票据
                 FormsAuthenticationTicket Ticket = new  FormsAuthenticationTicket(1, user, DateTime.Now, DateTime.Now.AddMinutes(30), true , "管理员,会员" , "/" );
                 //使用webcongfi中定义的方式,加密序列化票据为字符串
                 string  HashTicket = FormsAuthentication.Encrypt(Ticket);
                 //将加密后的票据转化成cookie
                 HttpCookie UserCookie = new  HttpCookie(FormsAuthentication.FormsCookieName, HashTicket);
                 //添加到客户端cookie
                 Context.Response.Cookies.Add(UserCookie);
                 //登录成功后重定向
                 Response.Redirect( "GotoUrl.aspx?returnUrl="  + Server.UrlEncode( "Default.aspx" ));
             }
             else
             {
                 //登录失败后的处理
             }          
         }
         /// <summary>
         /// 验证用户名/密码是否正确
         /// </summary>
         /// <param name="userName"></param>
         /// <param name="pwd"></param>
         /// <returns></returns>
         private  bool  ValidateUser( string  userName, string  pwd) {
             return  true ; //当然实际开发中,您可以到数据库里查询校验,这里只是示例而已
         }
     }
}

GotoUrl.aspx:这个页面只是单纯的辅助跳转而已,所以aspx页面本身不用加什么代码,只需要在后置cs代码里简单处理一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
using  System;
namespace  LoginTest
{
     public  partial  class  GotoUrl : System.Web.UI.Page
     {
         protected  void  Page_Load( object  sender, EventArgs e)
         {
             string  _returnUrl = Request[ "returnUrl" ];
             if  ( string .IsNullOrEmpty(_returnUrl))
             {
                 _returnUrl = "~/default.aspx" ;
             }
             Response.Redirect(_returnUrl);
         }
     }
}

接下来应该是Default.aspx了,这里只是演示,所以没有后置代码,判断的逻辑全写在default.aspx本身:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="LoginTest.Default" %>
<! DOCTYPE  html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
< html  xmlns="http://www.w3.org/1999/xhtml">
< head  runat="server">
     < title ></ title >
</ head >
< body >
     < form  id="form1" runat="server">
     < div >
         <% if (User.Identity.IsAuthenticated)
            {
                Response.Write("< span  style='color:red'>" + User.Identity.Name + "</ span >已登录!");
                if (User.IsInRole("管理员"))
                {
                    Response.Write(" 当前用户角色:管理员");
                }
                if (User.IsInRole("会员"))
                {
                    Response.Write(",会员。");
                }
                Response.Write(" < a  href='logout.aspx'>安全退出</ a >");
            }
            else
            {
                Response.Write("请先< a  href='login.aspx'>登录</ a >");
            }
         %>
     </ div >
     </ form >
</ body >
</ html >

最后一个是注销页面logout.aspx,类似的,这个页面本身只负责注销cookie票据,所以界面上没东西,只有后置代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
using  System;
using  System.Web.Security;
namespace  LoginTest
{
     public  partial  class  Logout : System.Web.UI.Page
     {
         protected  void  Page_Load( object  sender, EventArgs e)
         {
             FormsAuthentication.SignOut();
             Response.Redirect( "default.aspx" );
         }
     }
}

如果您已经等不急的按下了F5想看下最终的结果,可能会令人失望:

咱还没登录呢,甚至连用户名,密码都没输入,咋会显示已登录?是不是想起了小沈阳的那句经典台词:为~什么呢?

这就是webform与winform不同的地方,asp.net默认的表单认证方式是Windows,所以程序一运行,asp.net就把windows当前的登录用户视为已经登录了,因此我们得改变asp.net的默认“傻帽”行为,修改web.config成下面这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<? xml  version="1.0"?>
< configuration >
   < system.web >
     
     < compilation  debug="true" targetFramework="4.0" />
     
    
     < authentication  mode="Forms">
       < forms
          name=".ASPXAUTH"
          loginUrl="login.aspx"
          timeout="30"
          path="/"
          requireSSL="false"
          domain="">
       </ forms >
     </ authentication >
   </ system.web >
</ configuration >

哦,忘了告诉大家,我用的是asp.net 4.0,所以web.config显示十分简洁清爽。

ok,再来跑一下:

这回对了,点击“登录",转到login.aspx,然后在用户名里输入点啥(比如:"菩提树下的杨过"),然后会得到下面的结果:

认证已经成功了!但是好象还有点问题:并没有识别出身份!(即login.aspx.cs中代码指定的"管理员,会员"角色)

静下心来想想问题出在哪里?

在winform中,我们用

1
2
IPrincipal _principal = new  GenericPrincipal(_identity, new  string [] { "管理员"  });
Thread.CurrentPrincipal = _principal; //将其附加到当前线程的CurrentPrincipal

给_principal授权为"管理员"(当然还能给它更多的角色),然后将其赋值为线程的CurrentPrincipal,所以就ok了,但是webform中并没有Thread.CurrentPrincipal,而且http本身又是无状态的,下一次http请求,根本无法记得上次请求时的状态(就好象每次http请求都是重新投胎一样,前世忘记得一干二净),幸好:微软为asp.net搞出一个上下文Context的概念,一个webApplication中,虽然http协议本身是无状态的,但是每个aspx页面被请求时,总会附带一个HttpContext上下文,可以用它来找回一些前世的记忆,而且文章最开头提到了 HttpContext.Current.User本身就是IPrincipal,这不就是Thread.CurrentPrincipal的变种么?

顺便再回忆一下Asp.Net的页面生命周期,每个AspX页面在请求认证时,都会触发Application_AuthenticateRequest事件,而这个事件是定义在Global.ascx中的,所以可以从这个入手:

新建一个Global.ascx,打开后置代码,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
using  System;
using  System.Security.Principal;
using  System.Web;
using  System.Web.Security;
namespace  LoginTest
{
     public  class  Global : System.Web.HttpApplication
     {
         protected  void  Application_Start( object  sender, EventArgs e)
         {
         }
         protected  void  Session_Start( object  sender, EventArgs e)
         {
         }
         protected  void  Application_BeginRequest( object  sender, EventArgs e)
         {
         }
         /// <summary>
         /// 每个aspx页面要求认证时被触发
         /// </summary>
         /// <param name="sender"></param>
         /// <param name="e"></param>
         protected  void  Application_AuthenticateRequest( object  sender, EventArgs e)
         {
             HttpContext _ctx = HttpContext.Current;
             if  (_ctx.User != null )
             {
                 if  (_ctx.User.Identity.IsAuthenticated == true ) //认证成功的用户,才进行授权处理
                 {
                     FormsIdentity _Identity = (FormsIdentity)_ctx.User.Identity;
                     string [] Roles = _Identity.Ticket.UserData.Split( ',' ); //将角色字符串,即login.aspx.cs中的“管理员,会员”,变成数组
                     _ctx.User = new  GenericPrincipal(_Identity, Roles); //将带有角色的信息,重新生成一个GenericPrincipal赋值给User,相当于winform中的Thread.CurrentPrincipal = _principal
                 }
             }
         }
         protected  void  Application_Error( object  sender, EventArgs e)
         {
         }
         protected  void  Session_End( object  sender, EventArgs e)
         {
         }
         protected  void  Application_End( object  sender, EventArgs e)
         {
         }
     }
}

再测试一下,结果总算正常了:

最后再帮.Net做点广告:.Net是一个平台,其中的很多技术是全平台通用的(不管是winform还是webform),强烈建议大家尽量向微软自带的标准模型靠拢,这样在多种不同类型的应用整合时,将非常方便,而且兼容性好,容易升级。

经常看见有人winform中登录用一种做法(比如设置一个全局的静态变量,判断用户是否已经登录),然后webform中又动不少脑筋想一种做法(比如自己建用户表,搞加密算法,然后用session做判断),假如以后这二种应用要整合起来,估计要费不少劲(当然,也有设计得很好,一开始就考虑到日后的扩展的,但是这种毕竟少数,而且相对而言,对程序员的要求比较高),但是如果大家都用文中所提的标准模型(IIdentity,IPrincipal),要整合这二种应用是非常方便的。

.net中的认证(authentication)与授权(authorization)相关推荐

  1. Flex与.NET互操作(九):FluorineFx.NET的认证(Authentication )与授权(Authorization)

    FluorineFx.NET的认证(Authentication )与授权(Authorization)和ASP.NET中的大同小异,核实用户的身份既为认证,授权则是确定一个用户是否有某种执行权限,应 ...

  2. 谈谈系统认证 (Authentication) 和授权 (Authorization)

    这是一个绝大多数人都会混淆的问题. 说简单点就是: 认证 (Authentication): who,你是谁 授权 (Authorization): what,你有权限干什么 稍微正式点的说法就是: ...

  3. 认证 (Authentication) 和授权 (Authorization)的区别是什么?

    说简单点就是: 认证 (Authentication): 你是谁. 授权 (Authorization): 你有哪些权限 干什么事情. 稍微正式点(啰嗦点)的说法就是: Authentication( ...

  4. 认证(Authentication)和授权(Authorization)

    如果是客户端系统,比如winform,wpf构建的系统,你登陆之后,会看到自己权限可以操作的东西.你也很难通过其他方式,调用系统的方法.但是在BS系统中,请求和响应都是通过HTTP协议进行的,而且通过 ...

  5. 认证(Authentication),授权(Authorization) 以及Cookie、Session

    1.认证和授权 1.1 首先二者的读法不同 认证: Authentication 授权: Authorization 1.2  认证和鉴权是什么? 从简单的角度来说: 认证: 理解为你是谁 授权: 理 ...

  6. 认证 (Authentication) 和授权 (Authorization)

    RBAC 模型 系统权限控制最常采用的访问控制模型就是 RBAC 模型 . 在 RBAC 中,权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限.这就极大地简化了权限的管理. 什么是 C ...

  7. 认证 (authentication) 和授权 (authorization)小记

    原文地址 以前一直分不清 authentication 和 authorization,其实很简单,举个例子来说: 你要登机,你需要出示你的身份证和机票,身份证是为了证明你张三确实是你张三,这就是 a ...

  8. 认证 (authentication) 和授权 (authorization) 的区别.md

    以前一直分不清 authentication 和 authorization,其实很简单: 举个例子来说: 你要登机,你需要出示你的身份证和机票,身份证是为了证明你张三确实是你张三,这就是 authe ...

  9. 认证 (authentication) 和授权 (authorization) 的区别(通俗易懂!!)

    以前一直分不清 authentication 和 authorization,其实很简单,举个例子来说: 你要登机,你需要出示你的身份证和机票,身份证是为了证明你张三确实是你张三,这就是 authen ...

最新文章

  1. 20181102 T1 相遇
  2. opencv 其他形态学变换
  3. 【MM】更改供应商账户组
  4. mysql查询表名匹配只有字母的_MySQL按某些匹配字母查询表
  5. leetcode - 64. 最小路径和
  6. 无需一行代码,完成模型训练和部署,这个AI工具开始公测
  7. 高级网络配置:Bond网络接口、Team网络接口和网桥
  8. Muduo 网络编程示例之三:定时器
  9. C语言超时错误,C语言题目运行得到超时错误
  10. 万能淘口令生成api,淘口令转化api,淘口令万能版api,淘口令生成器api
  11. matlab批量处理图片压缩
  12. Skype for business 2015 综合部署系列七:配置 Skype 边缘传输服务器
  13. 视频直播时连麦功能使用说明
  14. 使用预训练模型训练YOLOv3-Keras
  15. 【Unity】Sprite Atlas功能讲解
  16. 股票内参|港股暴力拉升 恒生科技指数大涨
  17. pcb元器件通孔焊盘激光焊锡的优势
  18. ZGC学习笔记:ZGC简介和JDK17对ZGC的优化
  19. smalltalk资源
  20. 助力丽水市周安村“数字乡村”建设 ,复杂美区块链赋能农产品溯源营销

热门文章

  1. java+testng+selenium的自动化测试代码
  2. 信息竞赛(NOI)如何高效刷题 建议刷题历程
  3. 基于MFC的OpenDDS发布订阅例子(PubSubDemo)
  4. win10安装TensorFlow2.8.0
  5. Linux中父子进程、兄弟子进程之间通信方式--匿名管道pipe(适用于有血缘关系的进程)
  6. 微服务 分布式配置中心Apollo详解
  7. Linux-centos7-防火墙
  8. 面试题:kafka的ACK参数-1,0,1分别代表的含义
  9. 盲盒系统的玩法运营如何盈利
  10. 资产管理业务和财富管理