• .net中的认证(authentication)与授权(authorization)(转)


    原文地址:http://www.cnblogs.com/yjmyzz/archive/2010/08/29/1812038.html

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

     MSDN:http://msdn.microsoft.com/zh-cn/library/532aee0e.aspx

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

     

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

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

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

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

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

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

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

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

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

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

    输出如下:

    当前用户: 菩提树下的杨过
    是否已经登录? True
    是否管理员? True
    是否网站会员? True

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

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

    Form1的界面:

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

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

    Form1 中的代码:

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

    Form2中的代码:

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

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

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

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

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

    后置代码:

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

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

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

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

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

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

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

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

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

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

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

    哦,忘了告诉大家,我用的是asp.net 4.0,所以web.config显示十分简洁清爽。还有一点需要注意:如果timeout的时间没有设置,那么系统会自动默认30分钟。

    ok,再来跑一下:

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

     

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

    在winform中,我们用

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

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

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

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

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

  • 相关阅读:
    javaHTTP请求工具类-使用HttpURLConnection实现
    windows 无法启动redis 服务(位于本地计算机上)错误1053 服务没有及时响应启动或控制请求
    Redis 教程
    谢娜离开《快本》103天,暴露了芒果一姐的假相:有一种天真,叫错把平台当本事
    Api 在线文档目录:java8 中文、java11中文
    Linux关闭防火墙命令red hat/CentOs7
    Win10使用RedisDesktopManager工具连接虚拟机(CentOS 7)Redis
    如何win10 上访问虚拟机(linux)上redis方法
    汽车车牌JS正则表达式验证(含新能源车牌)
    vue 直接输入路由地址进入_vue地址栏直接输入路由无效问题
  • 原文地址:https://www.cnblogs.com/caosenianhuan/p/3338217.html
Copyright © 2020-2023  润新知