.net中的認證(authentication)與授權(authorization)
[2] .net中的認證(authentication)與授權(authorization)
“認證”與“授權”是幾乎所有系統中都會涉及的概念,通俗點講:
1、認證(authentication) 就是 "判斷用戶有沒有登錄?",好比windows系統,沒登錄就無法使用(不管你是用Administrator或Guest用戶,總之要先正確登錄后,才能進入系統)。
2、授權(authorization) 就是"用戶登錄后的身份/角色識別",好比"管理員用戶"登錄windows后,能安裝軟件、修改windows設置等所有操作,而Guest用戶登錄后,只有做有限的操作(比如安裝軟件就被禁止了)。
.net中與"認證"對應的是IIdentity接口,而與"授權"對應的則是IPrincipal接口,這二個接口的定義均在命名空間System.Security.Principal中:
using System.Runtime.InteropServices;
namespace System.Security.Principal
{
[ComVisible(true)]
public interface IIdentity
{
string AuthenticationType { get; }
bool IsAuthenticated { get; }
string Name { get; }
}
}
using System.Runtime.InteropServices;
namespace System.Security.Principal
{
[ComVisible(true)]
public interface IPrincipal
{
IIdentity Identity { get; }
bool IsInRole(string role);
}
}
用Membership/Role做過asp.net開發的朋友們,看到這二個接口的定義,應該會覺得很眼熟,想想我們在Asp.Net頁面中是如何判斷用戶是否登錄以及角色的?
{
HttpContext ctx = HttpContext.Current;
if (ctx.User.Identity.IsAuthenticated && ctx.User.IsInRole("管理員"))
{
//管理員該做的事,就寫在這里
}
else
{
//Hi,您不是管理員,別胡來!
}
}
這段代碼再熟悉不過了,沒錯!membership/role的原理就是基于這二個接口的,如果再對HttpContext.Current.User刨根問底,能發現下面的定義:
即:HttpContext.Current.User本身就是一個IPrincipal接口的實例。有了上面的預備知識,可以直奔主題了,先來一個Console控制臺程序測試一下用法:
using System.Security.Principal;
using System.Threading;
namespace ConsoleTest
{
class Program
{
static void Main(string[] args)
{
GenericIdentity _identity = new GenericIdentity("菩提樹下的楊過");
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();
}
}
}
輸出如下:
當前用戶: 菩提樹下的楊過
是否已經登錄? True
是否管理員? True
是否網站會員? True
一切正常,沒什么大不了,但Console默認只是一個單線程的程序,也沒有豐富的GUI界面,所以...這個只不過是熱身,看下接口定義的幾個方法是否管用而已。
這二個接口同樣也能用在Winform程序中,下面將創建一個WinForm應用,里面有二個窗口:Form1以及Form2,可以把Form1當成登錄界面,而Form2則是程序主窗口,在很多管理軟件中,主窗口都要求登錄以后才能訪問,我們就來模擬一下:
Form1的界面:
Form2更簡單:(就一個只讀的TextBox)
我想做的事情:在Form1上登錄后,看看在Form2中,能否判斷出用戶已經登錄,以及識別出身份。
Form1 中的代碼:
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中的代碼:
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中直接就能判斷用戶是否登錄,以及當前登錄用戶的角色。這里有一個關鍵的細節:
在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到客戶端瀏覽器后,客戶端的頁面就與服務器再無瓜葛了(你甚至可以離線瀏覽,前提是不刷新),那么最后的認證信息保存在什么地方呢?