.net中的认证(authentication)与授权(authorization)
[2] .net中的认证(authentication)与授权(authorization)
答案就是客户端的浏览器Cookie!所以在WebForm中的做法稍有不同:
创建一个webApplication,里面新建4个页面:login.aspx,logout.aspx,default.aspx,gotoUrl.aspx,这四个页面的作用如下:
login.aspx : 登录页面
logout.aspx: 用来处理用户注销 (非必需,但建议把注销逻辑放在这里,以便任何需要注销的地方重复利用)
default.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>
后置代码:
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; //当然实际开发中,您可以到数据库里查询校验,这里只是示例而已
}
}
}
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);
}
}
}
接下来应该是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>
最后一个是注销页面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");
}
}
}
如果您已经等不急的按下了F5想看下最终的结果,可能会令人失望:
咱还没登录呢,甚至连用户名,密码都没输入,咋会显示已登录?是不是想起了小沈阳的那句经典台词:为~什么呢?
这就是webform与winform不同的地方,asp.net默认的表单认证方式是Windows,所以程序一运行,asp.net就把windows当前的登录用户视为已经登录了,因此我们得改变asp.net的默认“傻帽”行为,修改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>
哦,忘了告诉大家,我用的是asp.net 4.0,所以web.config显示十分简洁清爽。
ok,再来跑一下:
这回对了,点击“登录",转到login.aspx,然后在用户名里输入点啥(比如:"菩提树下的杨过"),然后会得到下面的结果:
认证已经成功了!但是好象还有点问题:并没有识别出身份!(即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的变种么?
顺便再回忆一下Asp.Net的页面生命周期,每个AspX页面在请求认证时,都会触发Application_AuthenticateRequest事件,而这个事件是定义在Global.ascx中的,所以可以从这个入手:
新建一个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)
{
}
}
}
再测试一下,结果总算正常了:
最后再帮.Net做点广告:.Net是一个平台,其中的很多技术是全平台通用的(不管是winform还是webform),强烈建议大家尽量向微软自带的标准模型靠拢,这样在多种不同类型的应用整合时,将非常方便,而且兼容性好,容易升级。
经常看见有人winform中登录用一种做法(比如设置一个全局的静态变量,判断用户是否已经登录),然后webform中又动不少脑筋想一种做法(比如自己建用户表,搞加密算法,然后用session做判断),假如以后这二种应用要整合起来,估计要费不少劲(当然,也有设计得很好,一开始就考虑到日后的扩展的,但是这种毕竟少数,而且相对而言,对程序员的要求比较高),但是如果大家都用文中所提的标准模型(IIdentity,IPrincipal),要整合这二种应用是非常方便的。