.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="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>
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

再測試一下,結果就正常了。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM