Posted on 2005-04-18 11:11
二十四画生 阅读(8184)
评论(38) 编辑 收藏 网摘 所属分类:
ASP.NET Starter Kits
ASP.NET Portal Starter Kit是采用的“基于窗体的身份验证”的身份验证模式。Forms 身份验证通常指这样一个系统,在该系统中使用 HTTP 客户端重定向将未经身份验证的请求重定向到 HTML 窗体。如果应用程序需要在登录时通过 HTML 窗体收集自己的用户凭据,那么选择 Forms 身份验证就很好。用户提供凭据并提交该窗体。如果应用程序对请求进行身份验证,系统会发出一个 Cookie,在其中包含用于重新获取标识的凭据或密钥。随后发出在请求头中具有该 Cookie 的请求。ASP.NET 事件处理程序使用应用程序指定的任何验证方法对这些请求进行身份验证和授权。
数据库设计:
在ASP.NET Portal Starter Kit中存储用户角色相关的表有三个:用户信息表(Portal_Users),角色信息表(Portal_Roles),用户角色关系表(Portal_UserRoles)。通过用户角色关系表将用户信息和角色信息管理起来,可实现一个用户可有多种角色,一个角色也可以同时是多个用户。三表之间的关系如下:
程序实现:
在ASP.NET Portal Starter Kit中用户登录成功后以email为用户标识名称建立用户标识时同时触发Global.asax.cs中的Application_AuthenticateRequest事件,将登录用户的角色信息读入Context.User中。在DesktopDefault.aspx根据portalSettings.ActiveTab.AuthorizedRoles(当前活动标签的可访问属性)判断是否可访问该标签的内容。如果可访问则呈现该标签下的用户模块,不能访问就重定向到访问错误页(AccessDenied.aspx)。在管理用户是否可编辑用户模块信息时,是判断用户角色是否在模块指定的可编辑角色(模块的IsEditable属性)中。
关键代码:
1、 根据用户的Email获取用户的角色(以String[]的形式返回,一项表示一个角色,一个用户可有多个角色)(Security.cs中)
public String[] GetRoles(String email)


{
// 访问数据库的几步曲
SqlConnection myConnection = new SqlConnection(ConfigurationSettings.AppSettings["connectionString"]);
SqlCommand myCommand = new SqlCommand("Portal_GetRolesByUser", myConnection);
myCommand.CommandType = CommandType.StoredProcedure;
SqlParameter parameterEmail = new SqlParameter("@Email", SqlDbType.NVarChar, 100);
parameterEmail.Value = email;
myCommand.Parameters.Add(parameterEmail);
// 打开链接用SqlDataReader执行查询
SqlDataReader dr;
myConnection.Open();
dr = myCommand.ExecuteReader(CommandBehavior.CloseConnection);
// 读取用户的角色信息
ArrayList userRoles = new ArrayList();

while (dr.Read())
{
userRoles.Add(dr["RoleName"]);
}
dr.Close();
return (String[]) userRoles.ToArray(typeof(String));
}
2、 检查当前角色是否在指定的角色中(Security.cs中)
public static bool IsInRoles(String roles)


{
HttpContext context = HttpContext.Current;

foreach (String role in roles.Split( new char[]
{';'} ))

{
//指定角色中有All Users的也表示通过
if (role != "" && role != null && ((role == "All Users") || (context.User.IsInRole(role))))

{
return true;
}
}
return false;
}
3、登录验证代码(SignIn.ascx.cs中)
private void SigninBtn_Click(object sender, System.Web.UI.ImageClickEventArgs e)


{
// 通过UsersDB类尝试并验证用户是否合法
UsersDB accountSystem = new UsersDB();
String userId = accountSystem.Login(email.Text, PortalSecurity.Encrypt(password.Text));
if ((userId != null) && (userId != ""))

{
// 为给定的 userName 和 createPersistentCookie 创建身份验证票,并将其附加到 Cookie 的传出响应的集合。它不执行重定向。
// 以email为用户标识名称建立用户标识时同时触发Global.asax.cs中的Application_AuthenticateRequest事件
FormsAuthentication.SetAuthCookie(email.Text, RememberCheckbox.Checked);
// 从定向到起始页
Response.Redirect(Request.ApplicationPath);
}
else

{
Message.Text = "<" + "br" + ">登录失败!" + "<" + "br" + ">";
}
}
4、Global.asax.cs中的Application_AuthenticateRequest事件
protected void Application_AuthenticateRequest(Object sender, EventArgs e)


{
if (Request.IsAuthenticated == true)

{
String[] roles;
// 将用户角色信息存入到cookie
if ((Request.Cookies["portalroles"] == null) || (Request.Cookies["portalroles"].Value == ""))

{
//当Cookies中没有时,从数据库中读取
UsersDB user = new UsersDB();
roles = user.GetRoles(User.Identity.Name);
// 以字符串的形式存储用户角色信息用";"分隔,一个用户的多个角色
String roleStr = "";
foreach (String role in roles)

{
roleStr += role;
roleStr += ";";
}
// 创建身份角色验证的凭据
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(
1, // version 版本
Context.User.Identity.Name, // user name cookie 名
DateTime.Now, // issue time 发布日期
DateTime.Now.AddHours(1), // expires every hour 过期日期
false, // don't persist cookie 持久性(false)
roleStr // roles 用户定义的数据初始化(";"分隔的角色字符串)
);
// 加密凭证
String cookieStr = FormsAuthentication.Encrypt(ticket);
// 将存有用户角色信息的字符串存入cookie
Response.Cookies["portalroles"].Value = cookieStr;
Response.Cookies["portalroles"].Path = "/";
Response.Cookies["portalroles"].Expires = DateTime.Now.AddMinutes(1);
}
else

{
//当Cookies中有时,从Cookies中读取
FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(Context.Request.Cookies["portalroles"].Value);
ArrayList userRoles = new ArrayList();

foreach (String role in ticket.UserData.Split( new char[]
{';'} ))

{
userRoles.Add(role);
}
roles = (String[]) userRoles.ToArray(typeof(String));
}
// 从 GenericIdentity 和角色名称数组(GenericIdentity 表示的用户属于该数组)初始化 GenericPrincipal 类的新实例。
Context.User = new GenericPrincipal(Context.User.Identity, roles);
}
}
5、DesktopDefault.aspx中的判断是否可访问该页的代码
// 当前用户的角色不在当前活动标签的可访问角色中时,重定向到访问错误页
if (PortalSecurity.IsInRoles(portalSettings.ActiveTab.AuthorizedRoles) == false)


{
Response.Redirect("~/Admin/AccessDenied.aspx");
}
6、用户是否可编辑用户模块的代码
public static bool HasEditPermissions(int moduleId)


{
string accessRoles;
string editRoles;
// 获取站点的设置信息
SiteConfiguration siteSettings = (SiteConfiguration) HttpContext.Current.Items["SiteSettings"];
// 在设置信息中找到指定模块的行(XML中的用户模块表Module)
SiteConfiguration.ModuleRow moduleRow = siteSettings.Module.FindByModuleId(moduleId);
//可编辑指定模块的角色信息
editRoles = moduleRow.EditRoles;
//可访问模块所属标签的角色信息
accessRoles = moduleRow.TabRow.AccessRoles;
//既有模块的编辑权,又有模块所属标签的访问权的才可修改指定模块
if(PortalSecurity.IsInRoles(accessRoles) == false || PortalSecurity.IsInRoles(editRoles) == false)
return false;
else
return true;
}
(先列出一部分,等我全部整理好后再提供整站程序的下载)
更多相关内容:点击这里>>
Feedback
@aierong
恩,的确是一个很严重的Bug。你不说我还真没测试到。
如果用户的角色在每次建立用户标识的时候都从数据库读取就可以解决。但会不会产生其他的问题呢?
楼主
我已经给出了解决方案
就是在
protected void Application_AuthenticateRequest(Object sender, EventArgs e)
事件中
定义一个私有的变量来存放cookie的名称
下面的我给出了解决方案
http://www.cnblogs.com/aierong/archive/2005/01/17/93113.html">http://www.cnblogs.com/aierong/archive/2005/01/17/93113.html
我看的是
ASP.NET StartKit TimeTracker
@aierong
你的解决方案我已经看过,谢谢!我觉得不从cookie读取,岂不是更好!反正每次建立用户标识的时候才从数据库读取,不会加重数据库负担。
对
你的思路可以参考
好像是可以不放在cookie中
实际上程序中是把用户的角色数组放在身份cookie的用户数据中
要修改的话我们把角色数组放在会话中
要是担心会话中数据的安全性,我们可以先加密
使用会话cookie不就没有这个问题了吗?关闭浏览器的时候,cookie就失效了。
会的,会话cookie就像session一样。
其实session就是一个特殊的cookie。
我就是这样改的,没有问题。
多交流,我是把portal向dotnetnuke方向更改。
想问一下,PortalSecurity这个类有没有生成实例,怎样被其他的cs文件调用呢
PortalSecurity类中都是静态方法(static),无需实例化。
在DesktopPortalBanner.ascx.cs中有如下代码:
// 当用户登录后显示欢迎**用户的消息
if (Request.IsAuthenticated == true)
我想问的是,这个Request.IsAuthenticated 判断用户是否验证了,在何处对其进行验证呢?
仔细看了一下PortalSecurity类确实都是静态方法
public static bool IsInRole(String role)
:)
@tan
在SignIn用户控件中,用户登录成功后有这么一句:
// 为给定的 userName 和 createPersistentCookie 创建身份验证票据,并将其附加到 Cookie 的传出响应的集合。它不执行重定向。
// 以email为用户标识名称建立用户标识时同时触发Global.asax.cs中的Application_AuthenticateRequest事件
FormsAuthentication.SetAuthCookie(email.Text, RememberCheckbox.Checked);
在Forms 身份验证模式中,建立了身份验证票据就表示通过了验证。详细情况可参见MSDN中《Forms 身份验证提供程序》一节的内容。
我是新手!!看了些天代码!!可想通过调试来更能理解其框架思想和逻辑思维!!可怎样作呢 !!??我设置了断点了,可根本不起作用!!望赐教??!!
@sunwangji
仔细看看MSDN关于调试器方面的内容,里面讲的很详细。
Global.asax.cs中的Application_AuthenticateRequest事件的一个注释老大好像写错了
//当Cookies中没有时,从Cookies中读取
FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(Context.Request.Cookies["portalroles"].Value);
ArrayList userRoles = new ArrayList();
@西门吹雪
呵呵,是写错了,应该是“当Cookies中有时,从Cookies中读取”。谢谢指出!^_^
应当感谢你才对,除了portal本身的说明,网上的相关资料不多。还好读了你的许多文章获益匪浅,少走许多弯路。上次听取了你的意见先了解 starter kit系列然后再准备学DNN.
我测试了代码,发现Application_AuthenticateRequest根本不起作用,Request.IsAuthenticated 一直是false;这是怎么回事?
是不是在提交按钮事件SigninBtn_Click里面添加“FormsAuthentication.SetAuthCookie(email.Text, false); ”Application_AuthenticateRequest中的Request.IsAuthenticated 就一定为true?
已经解决,FormsAuthentication.SetAuthCookie(email.Text, false);一定要放在Response.Redirect(Request.ApplicationPath); 之前才行的。
现在又有一个新的问题,BLL.PortalSecurity.IsInRoles("tea") 一直都为false。真是郁闷。有没人告诉我原因啊?惨!
我不小心在数据库里面把Host帐户的一些键值给搞乱了,现在用Host帐户登录时提示密码错误,嘿嘿,该怎么呢?
@morn
重新注册一个用户,将该用户的密码复制到复制到host密码字段中应该就可以了。
密码在aspnet_Membership表中,分别要修改Password和PasswordSalt字段。
ApplicationId 、UserId、RoleId都让我不小心改了,就是没有改与PassWord有关的字段。
那么按照您的方法现在修改PassWord和PassWordSalt字段可以解决问题吗?
还有,该如何重建HOst帐户呢,如果这个用户被删除了或有其他不幸事情发生的话。
继续上面的问题,在DNN3.2.2版本中好容易使得host帐户可以登录了,但是管理用户的时候出现下面的问题:
错误: 管理用户 当前无效.
DotNetNuke.Services.Exceptions.ModuleLoadException: 未将对象引用设置到对象的实例。 ---> System.NullReferenceException: 未将对象引用设置到对象的实例。 at _ASP.Users_ascx.__DataBind__control19(Object sender, EventArgs e) at System.Web.UI.Control.OnDataBinding(EventArgs e) at System.Web.UI.Control.DataBind() at System.Web.UI.Control.DataBind() at System.Web.UI.Control.DataBind() at System.Web.UI.WebControls.DataGrid.CreateItem(Int32 itemIndex, Int32 dataSourceIndex, ListItemType itemType, Boolean dataBind, Object dataItem, DataGridColumn[] columns, TableRowCollection rows, PagedDataSource pagedDataSource) at System.Web.UI.WebControls.DataGrid.CreateControlHierarchy(Boolean useDataSource) at System.Web.UI.WebControls.BaseDataList.OnDataBinding(EventArgs e) at System.Web.UI.WebControls.BaseDataList.DataBind() at DotNetNuke.Modules.Admin.Users.UserAccounts.BindData(String SearchText, String SearchField) at DotNetNuke.Modules.Admin.Users.UserAccounts.Page_Load(Object sender, EventArgs e) --- 内部异常堆栈跟踪的结尾 ---
救命,该如何是好呀?
@morn
将Users表某一个用户的IsSuperUser字段置为1试试。
@二十四画生
将一普通用户IsSuperUser字段置为True后该用户无法登陆,提示密码错误,将IsSuperUser字段置回为False之后该用户可以正常登陆。
触发Application_AuthenticateRequest事件的应该是 FormsAuthentication.SetAuthCookie(email.Text, RememberCheckbox.Checked); 吧?