Asp.Net实现FORM认证的一些使用技巧(必看篇)
最近因为项目代码重构需要重新整理用户登录和权限控制的部分,现有的代码大体是参照了.NET的FORM认证,并结合了PORTAL KITS的登录控制,代码比较啰嗦,可维护性比较差。于是有了以下的几个需求(大多数系统应该都会碰到):
1.用.NET自带的FORM认证来实现安全登录
2.登录后需要记录登录用户的基本信息,方便所有页面调用
3.记录本机登录状态,短时间关闭窗口后不用重新登录
4.权限控制和代码的文件夹结构相呼应,即按角色允许访问不同的目录
5.权限控制有可能需要细化到每一个页面,即按角色允许访问不同的页面
6.以上的部分尽量自己少写代码,用自带的类库和机制实现
第一步:准备工作
先准备一个名为Test的WEB项目,包含:
Default.aspx,默认页,随便显示一些信息,
Login.aspx,登录页,上面放两个文本框,用来输入用户名和密码,一个登录按钮,一个指向Register.aspx的超链,
Register.aspx,用户注册页,注册用户信息,随便放一点文本框,主要是模拟一下注册,不用真正实现,
Web.config,配置页面。
注册页与登录页在同一目录的机妙后面会说。
第二步:Web.config文件的修改
1、打开Web.config文件,找到authentication节,将其改为如下:
<authentication mode=
"Forms"
>
<forms name=
".ASPXAUTH"
loginUrl=
"Login.aspx"
protection=
"All"
path=
"/"
timeout=
"20"
/>
</authentication>
<authorization>
<deny users=
"?"
></deny>
</authorization>
配置节属性的具体意义和其他没有加入的属性网上到处都有查。这里注意一下的是authentication节和authorization节,两个单词很相似,但却不是同一个单词,每个节下面的内容也不能写到一起。
其中,authorization节中的“allow”表示允许的意思,“*”表示所有用户;而“deny”表示拒绝的意思;“?”表示匿名用户;此处加入后,则代表根目录下的所有文件和所有的子目录都不能匿名访问,Login.aspx 页面除外。
2、Web.config中,Location节的应用
做了上面的配置之后,我们会发现,在没登录的情况下,用浏览器打开Default.aspx会自动转到Login.aspx,同理Register.aspx页面也会如此。问题:注册用户怎么可能在登录后才能访问呢?
那么我们就得说了,当注册页与登录页在同一目录,为了达到不用登录就能访问注册页的目的,我们就得对访问限制的Web.config配置处理一下。
方法一 :注册页与登录页放在不的目录内
我们在根目录添加一个文件夹Pub,将Register.aspx移动到此文件夹里,此时仍不能访问,需要在文件夹内添加一个Web.config文件,加入:
<configuration>
<system.web>
<authorization>
<allow users=
"*"
/>
</authorization>
</system.web>
</configuration>
此处,即说明此目录下的所有文件,允许所有人访问。
关于 Web.config 作用范围的说明:
• Web.config 的设置将作用于所在目录的所有文件及其子目录下的所有东东(继承:子随父姓)
• 子目录下的 Web.config 设置将覆盖由父目录继承下来的设置(覆盖:县官不如现管)
• 也就是,属性设置由最深一层的目录里的Web.config决定;如果子目录里没有Web.config文件,则由离它最近的父目录里的Web.config决定
方法二:仍然保持注册页和登录页在同一目录下
只需要在根目录下的Web.config 中加入以下一段:
<location path=
"Register.aspx"
>
<system.web>
<authorization>
<allow users=
"*"
/>
</authorization>
</system.web>
</location>
通过location节的path属性的值指定Register.aspx页面,以及下面authorization节的设置,说明了Register.aspx页面是允许被所有人访问。
注意:
location节应加在原有的system.web节的外面,包含在configuration节内,和system.web节是同级的。
当根目录下,有多个页面不需要登录就可以访问时,可以设置多个location节,修改对应path属性值指向的页面就可以了。
另外,path属性的值也可以指定目录,用来指定该目录的访问限制。通过修改authorization节的内容来限定访问权限。详细的设置,后面会提到。
第三步:实现登录的代码
1、普通的代码实现
方法一:
如果forms节中设置了“defaultUrl”的属性,也就是登录后默认转向的页面,则可以用如下的方法:
private void Btn_Login_Click(object sender, System.EventArgs e)
{
if
(
this
.Txt_UserName.Text==
"Admin"
&&
this
.Txt_Password.Text==
"123456"
)
{
FormsAuthentication.RedirectFromLoginPage(
this
.Txt_UserName.Text,
false
);
}
}
private void Btn_Login_Click(object sender, System.EventArgs e)
{
if
(
this
.Txt_UserName.Text==
"Admin"
&&
this
.Txt_Password.Text==
"123456"
)
{
FormsAuthentication.SetAuthCookie(
this
.Txt_UserName.Text,
false
);
Response.Redirect(
"Default.aspx"
);
}
}
此处是分两步走:通过验证后就直接发放 Cookie ,跳转页面将由程序员自行指定,无需“defaultUrl”设置。此方法对于程序员来说,更灵活。
2、手工实现需要记录用户登录信息的情况
当我们需要记录用户登录的信息,不单单只是一个ID还需要更多属性的时候,一般都用一个类存储到Session或Cookie实现,然后做一个基类页,在基类页中设置属性来读取Session或Cookie。
Session实际也和支不支持Cookie有关,且存在服务器,多少会占用服务器端资源。因此这里还是考虑用Cookie实现。那么在RedirectFromLoginPage方法或SetAuthCookie方法已经设置了验证票据并设置了Cookie,我们能不能把用户登录信息也存储到这个默认的Cookie里呢?答案是能。
首先,我们在项目里添加AppCode目录,增加一个UserInfo的类,用以简单模拟用户登录信息。代码如下:
[Serializable]
public class UserInfo
{
//用户登录信息
private int _nId;
private string _sRealName;
private string _sName;
private string _sPassword;
private string _sRoles;
public int Id
{
get {
return
this
._nId; }
set {
this
._nId = value; }
}
public string RealName
{
get {
return
this
._sRealName; }
set {
this
._sRealName = value; }
}
public string Name
{
get {
return
this
._sName; }
set {
this
._sName = value; }
}
public string Password
{
get {
return
this
._sPassword; }
set {
this
._sPassword = value; }
}
public string Roles
{
get {
return
this
._sRoles; }
set {
this
._sRoles = value; }
}
public UserInfo()
{
}
}
需要注意, 类的属性中一定要加[Serializable],表示类可以序列化。
Forms验证在内部的机制是,把用户数据加密后保存在一个基于cookie的票据FormsAuthenticationTicket中,通过RedirectFromLoginPage方法或SetAuthCookie方法就已经实现了Ticket和Cookie的设置,也就是设置了Context.User的值,Context.User在取值和判断是否经过验证的时候很有用处。Cookie的属性是在Web.config的<forms name=".ASPXAUTH" loginUrl="Login.aspx" protection="All" path="/" timeout="20"/>中设置的。因为是经过特殊加密的,所以应该来说是比较安全的。
而.net除了用这个票据存放自己的信息外,还留了一个地给用户自由支配,这就是现在要说的Ticket的UserData。 UserData用来存储string类型的信息,并且也享受Forms验证提供的加密保护,当我们需要这些信息时,也可以通过简单的Ticket的 UserData属性得到,兼顾了安全性和易用性,用来保存一些必须的敏感信息还是很有用的。我们就准备将用户的登录信息记录在UserData中,代码如下:
protected void Button1_Click(object sender, EventArgs e)
{
if
(
this
.TextBox1.Text ==
"Admin"
&&
this
.TextBox2.Text ==
"123456"
)
{
// 加密UserInfo
UserInfo user =
new
UserInfo();
user.Id = 1;
user.Name =
this
.TextBox1.Text;
user.Password =
this
.TextBox2.Text;
user.RealName =
"系统管理员"
;
user.Roles =
"Administrators,Users"
;
string strUser = Serialize.Encrypt<UserInfo>(user);
// 设置Ticket信息
FormsAuthenticationTicket ticket =
new
FormsAuthenticationTicket(
1, user.Name, DateTime.Now, DateTime.Now.AddMinutes(20),
false
, strUser);
// 加密验证票据
string strTicket = FormsAuthentication.Encrypt(ticket);
// 使用新userdata保存cookie
HttpCookie cookie =
new
HttpCookie(FormsAuthentication.FormsCookieName, strTicket);
cookie.Expires = ticket.Expiration;
this
.Response.Cookies.Add(cookie);
this
.Response.Redirect(
"Default.aspx"
);
}
}
上面的代码,实际上类似于手工实现了SetAuthCookie方法的过程。
首先,模拟实现登录,我们手动设置了一个UserInfo的对象,string strUser = Serialize.Encrypt<UserInfo>(user) 是将对象序列化成字符串的一个方法。
然后,生成一个FormsAuthenticationTicket票据。此处用到的FormsAuthenticationTicket构造函数的重载方法的签名解释
public FormsAuthenticationTicket(
int version,
//版本号
string name,
//与身份验证票关联的用户名
DateTime issueDate,
//票据的发出时间
DateTime expiration,
//票据的到期日期
bool isPersistent,
//票据是否存储在持久的 Cookie 中,是为 true;否则为 false
string userData
//票据中存储的用户定义数据
);
再后,string strTicket = FormsAuthentication.Encrypt(ticket) 将票据加密成字符创
最后,HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, strTicket) 生成Cookie。
FormsAuthentication.FormsCookieName获取的就是Web.config中配置的Cookie名称,也就是默认验证时产生的Cookie。cookie.Expires = ticket.Expiration 将票据的过期时间和Cookie的过期时间做了同步,也就避免了两者不同所产生的矛盾。这样,验证票据生成了,存储到默认配置的Cookie中,也就是类似实现了一个SetAuthCookie方法的过程。通过Context.User就能获取票据的相关信息
了。
3、获取信息
为了在其他登录后的页面比较简单的获取登录用户信息,我们先生成一个基类页面。在AppCode中新增LoginBasePage类,代码如下:
public class LoginBasePage : Page
{
protected UserInfo LoginUser
{
get
{
string strUser = ((FormsIdentity)
this
.Context.User.Identity).Ticket.UserData;
return
Serialize.Decrypt<UserInfo>(strUser);
}
}
public LoginBasePage()
{
//
// TODO: 在此处添加构造函数逻辑
//
}
}
LoginBasePage : Page,基类页要继承Page,成为所有登录以后的页面的基类。
属性protected UserInfo LoginUser{ get;}用来访问登录信息。将Context.User.Identity强制转换为FormsIdentity类的对象,通过访问Ticket属性的UserData属性,获得被序列化后的对象的字符串,最后用方法Serialize.Decrypt<UserInfo>(strUser)将字符串反序列化成对象后再返回UserInfo类型的对象。
我们只需要将Default页面的后台代码改为public partial class _Default : LoginBasePage,就可以通过this.LoginUser来访问用户登录信息了。
第四步:实现不同目录的权限控制
上面,实现了记录用户登录信息的模拟登录过程,以及根目录下文件的访问控制。但是系统一般都会有多个目录,接下来就说说目录的访问控制。
其实,上面多多少少已经提到过了,通过在每个目录下增加Web.config文件来进行访问限制。
首先,我们在根目录增加一个文件夹ManageAdmin,在此文件夹内增加页面UserInfo.aspx,页面内放几个Label用来展现登录用户信息。
然后,再增加一个Web.config文件,配置内容如下:
<configuration>
<appSettings/>
<connectionStrings/>
<system.web>
<authorization>
<allow users=
"Admin"
></allow>
<deny users=
"*"
></deny>
</authorization>
</system.web>
</configuration>
配置中说明只允许“Admin”用户访问,禁止其他所有用户访问。
这里,特别要注意的是,FormsAuthenticationTicket票据的name属性的赋值,一定要和<allow users="Admin"></allow>设置的用户想对应,且大小写敏感。如果要设置允许多个用户访问,则用“,”隔开,例如<allow users="Admin,User1"></allow>。
不同的目录,设置不同的允许访问的用户,就可以对所有目录进行访问控制了。
第五步:实现不同目录的按角色的权限控制
以上实现了对不同目录按用户的访问限制。但是一般来说,一个网站系统的用户会很多,如果一直使用精确到用户的访问控制,则会造成设置Web.config的工作量加大。
而一般,我们会将用户分到不同的用户组来进行权限控制,因此,我们也可以配置Web.config实现按角色来控制不同的目录的访问权限。
首先,我们在根目录下再增加一个目录ManageUsers,在此文件夹内也增加页面UserInfo.aspx用来展现登录用户信息。此目录将模拟控制Users组的用户,文件夹ManageAdmin将模拟控制Administrators组的用户。
然后,在目录ManageUsers增加Web.config文件,配置内容如下:
configuration>
<appSettings/>
<connectionStrings/>
<system.web>
<authorization>
<allow roles=
"Users"
></allow>
<deny users=
"*"
></deny>
</authorization>
</system.web>
</configuration>
再将文件夹ManageAdmin下的Web.config文件的<allow users="Admin"></allow>改成<allow roles="Administrators"></allow>。
最后,修改代码。
1、注意,我们在模拟用户信息的时候,有这么一句,user.Roles = "Administrators,Users";也就是用户Admin具备两种角色
2、为模拟Users组的用户登录,我们再添加如下代码:
if
(
this
.TextBox1.Text ==
"User1"
&&
this
.TextBox2.Text ==
"111111"
)
{
// 加密UserInfo
UserInfo user =
new
UserInfo();
user.Id = 2;
user.Name =
this
.TextBox1.Text;
user.Password =
this
.TextBox2.Text;
user.RealName =
"普通用户1"
;
user.Roles =
"Users"
;
string strUser = Serialize.Encrypt<UserInfo>(user);
// 设置Ticket信息
FormsAuthenticationTicket ticket =
new
FormsAuthenticationTicket(
1, user.Name, DateTime.Now, DateTime.Now.AddMinutes(20),
false
, strUser);
// 加密验证票据
string strTicket = FormsAuthentication.Encrypt(ticket);
// 使用新userdata保存cookie
HttpCookie cookie =
new
HttpCookie(FormsAuthentication.FormsCookieName, strTicket);
cookie.Expires = ticket.Expiration;
this
.Response.Cookies.Add(cookie);
this
.Response.Redirect(
"Default.aspx"
);
}
这样,我们登录时,输入“Admin”和“User1”的时候,就可以模拟不同角色的用户登录了。
3、Forms基于角色的验证的内部机制是,将角色的属性也设置到了Context.User中,这里也需要手工代码处理一下。
首先,为了支持基于角色的验证,我们每进入一个页面都需要将角色信息设置到Context.User中,那么最好的办法就是在Global.asax 文件中的Application_AuthenticateRequest方法中设置。
Application_AuthenticateRequest方法,是在每次验证请求时触发,它与另外一个方法Application_BeginRequest的区别就在于,Application_AuthenticateRequest方法内,能够访问Context.User.Identity,而Application_BeginRequest则无法访问。
我们在根目录添加一个Global.asax 文件,增加如下代码:
protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
if
(
this
.Context.User !=
null
)
{
if
(
this
.Context.User.Identity.IsAuthenticated)
{
if
(
this
.Context.User.Identity is FormsIdentity)
{
string strUser = ((FormsIdentity)
this
.Context.User.Identity).Ticket.UserData;
string[] roles = Serialize.Decrypt<UserInfo>(strUser).Roles.Split(
','
);
this
.Context.User =
new
GenericPrincipal(
this
.Context.User.Identity, roles);
}
}
}
}
此处,主要代码就是将Context.User.Identity强制转换为FormsIdentity类的对象,通过访问Ticket属性的UserData属性,获得被序列化后的对象的字符串,最后用方法Serialize.Decrypt<UserInfo>(strUser)将字符串反序列化成对象,再将UserInfo对象的Roles属性以“,”为分隔符分隔成角色数组,再用Context.User.Identity和角色数组生成一个新的GenericPrincipal对象,赋值给Context.User ,则Context.User 为已经设置好角色的验证对象。
按照我们的设置,Admin用户能访问两个目录,而User1用户,则只能访问ManageUsers一个目录。
第六步:集中管理Web.config文件
目录的访问权限控制,是按用户还是按角色,一般由具体业务决定。
但是,随着目录的增多,每个目录下都存在一个Web.config文件,管理起来特别不方便。
通过上面提到过的location节的path属性,我们可以实现Web.config配置的统一管理。我们可以将各个文件或目录的配置都放置在根目录的Web.config文件内,代码如下:
<configuration>
<appSettings/>
<connectionStrings/>
<location path =
"Register.aspx"
>
<system.web>
<authorization>
<allow users=
"*"
/>
</authorization>
</system.web>
</location>
<location path =
"ManageAdmin"
>
<system.web>
<authorization>
<allow roles=
"Administrators"
></allow>
<deny users=
"*"
></deny>
</authorization>
</system.web>
</location>
<location path =
"ManageUsers"
>
<system.web>
<authorization>
<allow roles=
"Users"
></allow>
<deny users=
"*"
></deny>
</authorization>
</system.web>
</location>
<system.web>
<!-- 这里放置原来根目录 Web.config 的内容,就不列出来了 -->
</system.web>
</configuration>
Asp.Net实现FORM认证的一些使用技巧(必看篇)的更多相关文章
- Asp.Net实现FORM认证的一些使用技巧(转)
最近因为项目代码重构需要重新整理用户登录和权限控制的部分,现有的代码大体是参照了.NET的FORM认证,并结合了PORTAL KITS的登录控制,代码比较啰嗦,可维护性比较差.于是有了以下的几个需求( ...
- Asp.Net实现FORM认证的一些使用技巧
原文转发:http://www.cnblogs.com/Showshare/archive/2010/07/09/1772886.html 最近因为项目代码重构需要重新整理用户登录和权限控制的部分,现 ...
- Asp.net MVC Form认证,IIS改成集成模式后,FormsAuthentication.SetAuthCookie无效,Request.IsAuthenticated值,始终为false,页面提示HTTP 错误 401.0 - Unauthorized,您无权查看此目录或页面
最近公司领导要求,IIS网站要由经典模式改为集成模式,以提高性能.改完之后,登录成功跳转到主页之后,页面提示“”HTTP 错误 401.0 - Unauthorized“,“您无权查看此目录或页面”, ...
- Asp.Net 之 使用Form认证实现用户登录 (LoginView的使用)
1. 创建一个WebSite,新建一个页面命名为SignIn.aspx,然后在页面中添加如下的代码 <div class="div_logView"> <asp: ...
- asp.net Form 认证【转】
第一部分 如何运用 Form 表单认证 一. 新建一个测试项目 为了更好说明,有必要新建一个测试项目(暂且为“FormTest”吧),包含三张页面足矣(Default.aspx.Logi ...
- 细说ASP.NET Forms身份认证
阅读目录 开始 ASP.NET身份认证基础 ASP.NET身份认证过程 如何实现登录与注销 保护受限制的页面 登录页不能正常显示的问题 认识Forms身份认证 理解Forms身份认证 实现自定义的身份 ...
- SharePoint 2013 配置基于AD的Form认证
前 言 配置SharePoint 2013基于AD的Form认证,主要有三步: 1. 修改管理中心的web.config: 2. 修改STS Application的web.config: 3. 修改 ...
- 简单的ASP.NET Forms身份认证
读了几篇牛人的此方面的文章,自己也动手做了一下,就想有必要总结一下.当然我的文章质量自然不能与人家相比,只是写给从没有接触过这个知识点的朋友. 网站的身份认证我以前只知道session,偶然发现一些牛 ...
- ASP.NET 表单认证与角色授权
参考 : http://hi.baidu.com/iykqqlpugocfnqe/item/e132329bdea22acbb6253105 ASP.NET中处理请求的流程图 http://www. ...
随机推荐
- android framework-下载Android系统源代码
□ apt-get install git-core curl #先下载这两个工具 □ mkdir android-froyo #建立下载目录 □ cd android-froyo #进入下载目录 □ ...
- Cocos2d-x 3.0 rc0中加入附加项目,解决无法打开包括文件:“extensions/ExtensionMacros.h”
Cocos2d-x 3.0 Alpha 1开始 对目录结构进行了整合.结果有些附加项目也被在项目中被精简出去. 比如说如果你需要使用CocoStdio导出的JSON.或使用Extensions扩展库, ...
- 【HBase】Rowkey设计【转】
本章将深入介绍由HBase的存储架构在设计上带来的影响.如何设计表.row key.column等等,尽可能地使用到HBase存储上的优势. Key设计 HBase有两个基础的主键结构:row key ...
- 工具WinSCP:windows和Linux中进行文件传输
工具WinSCP:windows和Linux中进行文件传输 2016-09-21 [转自]使用WinSCP软件在windows和Linux中进行文件传输 当我们的开发机是Windows,服务器是Lin ...
- C#中http请求下载的常用方式demo
//通过webClient方式 WebClient client = new WebClient(); string url="http://down6.3987.com:801/2010/ ...
- js图片转base64并压缩
/* 2015-09-28 上传图片*/ function convertImgToBase64(url, callback, outputFormat){ var canvas = document ...
- tomcat 的线程池配置,字符编码设置
优化tomcat配置 ,修改原先的配置 conf/server.xml 配置 <Executor name="tomcatThreadPool" namePrefix=&q ...
- c++之——多态性
先看一个例子: #include<iostream> using namespace std; class Liberation { public: Liberation(int a):c ...
- activemq用户手册
1 JMS 在介绍ActiveMQ之前,首先简要介绍一下JMS规范. 1.1 JMS的基本构件 1.1.1 连接工厂 连接工厂是客户用来创建连接的对象,例如ActiveMQ提供的ActiveMQCon ...
- 我的电脑(ACER 4750G)升级
升级原因 近期电脑卡的要死,卡到什么程序呢?就是打开"我的电脑"须要2秒中的缓冲时间,这怎样受的了--于是就有种特别想换电脑的冲动:买一个顶配版的台式机.让你给我慢. 一心想着顶配 ...