.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;
using System.Runtime.InteropServices;
namespace System.Security.Principal
{
[ComVisible(true)]
public interface IIdentity
{
string AuthenticationType { get; }
bool IsAuthenticated { get; }
string Name { get; }
}
}
using System;
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页面中是如何判断用户是否登录以及角色的?
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控制台程序测试一下用法:
using System;
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;
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;
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中直接就能判断用户是否登录,以及当前登录用户的角色。这里有一个关键的细节:
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到客户端浏览器后,客户端的页面就与服务器再无瓜葛了(你甚至可以离线浏览,前提是不刷新),那么最后的认证信息保存在什么地方呢?