zoukankan      html  css  js  c++  java
  • .net中的认证(authentication)与授权(authorization)

    本文转自:http://www.cnblogs.com/yjmyzz/archive/2010/08/29/1812038.html

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

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

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

    using System;
    using System.Runtime.InteropServices;
     
    namespace System.Security.Principal
    {
        [ComVisible(true)]
        public interface IIdentity
        {
               string AuthenticationType { get; }
               bool IsAuthenticated { get; }
               string Name { get; }
        }
    }
    View Code
    using System;
    using System.Runtime.InteropServices;
     
    namespace System.Security.Principal
    {
        [ComVisible(true)]
        public interface IPrincipal
        {
              IIdentity Identity { get; }
              bool IsInRole(string role);
        }
    }
    View Code

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

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

    protected void Page_Load(object sender, EventArgs e)
            {
                HttpContext ctx = HttpContext.Current;
                if (ctx.User.Identity.IsAuthenticated && ctx.User.IsInRole("管理员"))
                {
                    //管理员该做的事,就写在这里
                }
                else
                {
                    //Hi,您不是管理员,别胡来!
                }
            }
    View Code

    这段代码再熟悉不过了,没错!membership/role的原理就是基于这二个接口的,如果再对HttpContext.Current.User刨根问底,能发现:HttpContext.Current.User本身就是一个IPrincipal接口的实例。

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

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

    再来考虑一下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: 用来处理用户注销 (非必需,但建议把注销逻辑放在这里,以便任何需要注销的地方重复利用)
    
    deafult.aspx: 登录完成后的显示页面
    
    gotoUrl.aspx : 登录完成后,用来辅助做页面跳转的页面(非必需,但建议加上)

    login.aspx代码:

    <%@ 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="200px"></asp:TextBox></td>
            </tr>
            <tr>
                <td>密  码:</td>
                <td>
                    <asp:TextBox ID="txtPassword" runat="server" TextMode="Password" style="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>
    View Code

    后台代码:

    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; //当然实际开发中,您可以到数据库里查询校验,这里只是示例而已
            }
        }
    }
    View Code

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

    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);
            }
        }
    }
    View Code

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

    <%@ 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>
    View Code

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

    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");
            }
        }
    }
    View Code

    修改web.config成下面这样:

    <?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>
    View Code

    运行,认证已经成功了!但是好象还有点问题:并没有识别出身份!(即login.aspx.cs中代码指定的"管理员,会员"角色)。静下心来想想问题出在哪里?

    在winform中,我们用

    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的变种么?

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

    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)
            {
     
            }
        }
    }
    View Code

    再测试一下,结果就正常了。

  • 相关阅读:
    个人信息
    两个整数的最小公倍数和最大公约数
    java杨辉三角实现
    只会用这简单的递归求阶乘
    图形界面设计
    圆的面积,周长,圆柱体的体积(类的封装与抽象)
    杨辉三角
    1~10的阶乘java语言编程
    个人信息与计算器
    个人信息显示界面
  • 原文地址:https://www.cnblogs.com/zhengwk/p/5691080.html
Copyright © 2011-2022 走看看