当前位置: 首页 > 编程日记 > 正文

使用Forms Authentication实现用户注册、登录 (二)用户注册与登录

从这一部分开始,我们将通过一个实际的完整示例来看一下如何实现用户注册与登录。在介绍注册与登录之前,我们首先介绍一下如何判断用户是否已登录,并未后面的示例编写一些基础代码。
判断用户是否已经登录
首先,在Web站点项目中添加一个MasterPage,例如MasterPage.master。在这个母版页的ContentPlaceHolder控件之前、<From>标签之内插入如下代码:
 1 <asp:Panel ID="pnlAnonymous" runat="server">
 2      <asp:LinkButton ID="btnLogin" runat="server" Text="登录" OnClick="btnLogin_Click"></asp:LinkButton> |
 3      <asp:HyperLink ID="lnkRegister" runat="server" NavigateUrl="~/register.aspx" Text="注册"></asp:HyperLink>
 4 </asp:Panel>
 5 <asp:Panel ID="pnlLoggedin" runat="server">
 6      欢迎您,<asp:Label ID="lblUserName" runat="server"></asp:Label>
 7      [<asp:LinkButton ID="btnLogout" runat="server" Text="注销"
 8          onclick="btnLogout_Click"></asp:LinkButton>]
 9 </asp:Panel>
10 <asp:Panel ID="pnlNavigate" runat="server">
11      <asp:HyperLink ID="lnkDefault" runat="server" NavigateUrl="~/default.aspx" Text="首页"></asp:HyperLink> |
12      <asp:HyperLink ID="lnkTest" runat="server" NavigateUrl="~/test.aspx" Text="测试页"></asp:HyperLink>
13 </asp:Panel>
14 
这里提供了三个Panel控件——pnlAnonymous、pnlLoggedin和pnlNavigate。pnlAnonymous用于在用户未登录时显示“登录”和“注册”链接;pnlLoggedin用于在用户已登录时显示用户信息(如用户名和到用户个人信息页的链接等,这里仅显示用户名),以及一个“注销”按钮;pnlNavigate在任何时候都显示,是站点的导航栏。
现在我们要实现的是,判断用户是否登录,并显示pnlAnonymous和pnlLoggedin二者之一。这里,如果是使用ASP.NET 2.0 Membership,则可以方便地使用LoginView、LoginName和LoginStatus等控件实现这些功能;然而我们不得不为此忍受Membership所带来的庞大而繁多的数据库对象,或者花更多时间去编写自定义的MembershipPorvider。
这一系列的第一部分曾介绍过,如果用户已经登录,则可以从HttpContext.User.Identity.Name得到已登录用户的标识(通常是用户名)。然而,如果用户未登录,这个值则为空字符串。因此,通过判断该值是否为空字符串,即可的值用户是否已登录。
由此,我们在MasterPage的后台代码中添加Page_Init方法,在页面初始化时判断用户是否登录,并显示对应的Panel控件;此外,在用户已登录时,在pnlLoggedin中的欢迎语里插入已登录用户的用户名。
 1 protected void Page_Init(object sender, EventArgs e)
 2 {
 3      // 判断用户是否已登录。
 4      if(HttpContext.Current.User.Identity.Name == "")
 5      {
 6          // 用户未登录。
 7          pnlAnonymous.Visible = true;
 8          pnlLoggedin.Visible = false;
 9      }
10      else
11      {
12          // 用户已登录。
13          pnlAnonymous.Visible = false;
14          pnlLoggedin.Visible = true;
15 
16          lblUserName.Text = HttpContext.Current.User.Identity.Name;
17      }
18 }
此外,我们还向项目中添加了default.aspx、login.aspx、register.aspx、test.aspx这样四个页面。其中test.aspx用于检验重定向到登录页后的ReturnUrl参数值,和登录后回到之前页面的效果。
用户注册
用户注册部分并不属于验证的范畴,因此Forms Authentication也没有提供过多的支持。然而用户注册是比较简单的,只需通过一个页面搜集用户信息,并存放到数据库中即可。在这个示例中,为了方便和突出真正要讨论的内容,用户信息并不是真的放到数据库中的,而是放在一个集合里,不过通过一个DataAccess类来隐藏这种差异。理论上,一旦关闭应用程序并重新开启,放在集合中的数据就会丢失。然而实际上,再重复运行这里的示例时,用户数据并不会丢失,即便应用程序有所改动也是。这是因为ASP.NET 2.0对于Web项目都是动态重新编译的,应用程序从未直接终止过。
此外,就用户的数据实体类而言,我们仅提供了注册、登录所必需的属性——用户名、密码散列值、密码Salt值。(关于密码的加Salt值散列,请参见之前的一篇短文。)
为了进行用户注册,我们建立了register.aspx页面,该页面仅负责搜集用户信息,真正的创建用户动作在App_Code下的Membership类中完成。注意,此Membership非彼Membership,与ASP.NET 2.0 Membership没有任何关系。
首先在Membership类中添加一个静态方法CreateUser,用于创建用户。该方法完成用户实体的创建和密码的散列等功能。其代码如下所示:
 1 public static void CreateUser(string userName, string password)
 2 {
 3      UserObject user = new UserObject();
 4      user.Name = userName;
 5      user.PasswordSalt = GenerateSalt();
 6      user.PasswordHash = EncodePassword(password, user.PasswordSalt);
 7 
 8      DataAccess.AddUser(user);
 9 }
10 
这段代码非常简单,其中用到了GenerateSalt和EncodePassword方法,这两个方法用于完成salt值的生成和密码的加salt散列。其代码抽取自MembershipProvider类,进行了精简如下所示:
 1 public const string PasswordHashAlgorithmName = "SHA1";
 2 
 3 static string EncodePassword(string password, string salt)
 4 {
 5      byte[] src = Encoding.Unicode.GetBytes(password);
 6      byte[] saltbuf = Convert.FromBase64String(salt);
 7      byte[] dst = new byte[saltbuf.Length + src.Length];
 8      byte[] inArray = null;
 9      Buffer.BlockCopy(saltbuf, 0, dst, 0, saltbuf.Length);
10      Buffer.BlockCopy(src, 0, dst, saltbuf.Length, src.Length);
11 
12      HashAlgorithm algorithm = HashAlgorithm.Create(PasswordHashAlgorithmName);
13      inArray = algorithm.ComputeHash(dst);
14 
15      return Convert.ToBase64String(inArray);
16 }
17 
18 static string GenerateSalt()
19 {
20      byte[] data = new byte[0x10];
21      new RNGCryptoServiceProvider().GetBytes(data);
22      return Convert.ToBase64String(data);
23 }
然后在register.aspx页面中创建搜集用户信息的表单,这个表单非常简单,仅需在ContentPlaceHolder1内添加如下代码:
1 <table>
2 <tr><td>用户名:</td><td><asp:TextBox ID="txtUserName" runat="server"></asp:TextBox></td></tr>
3 <tr><td>密码:</td><td><asp:TextBox ID="txtPassword" runat="server" TextMode="Password"></asp:TextBox></td></tr>
4 <tr><td colspan="2"><asp:Button ID="btnOK" runat="server" Text="确定"
5          onclick="btnOK_Click" /></td></tr>
6 </table>
7 <asp:Label ID="lblMessage" runat="server"></asp:Label>
注意这比实际的注册页要简单许多,没有对用户名和密码的格式做验证,也没有让用户重复输入密码以确保没有键入错误。但Anders Liu相信,对于聪明的读者而言这些都不是问题,所以不再赘述。
在“确定”按钮的事件处理器中添加如下代码,完成注册功能:
 1 protected void btnOK_Click(object sender, EventArgs e)
 2 {
 3      try
 4      {
 5          Membership.CreateUser(txtUserName.Text, txtPassword.Text);
 6 
 7          lblMessage.Text = "用户创建成功!";
 8      }
 9      catch(Exception ex)
10      {
11          lblMessage.Text = "错误:" + ex.Message;
12      }
13 }
14 
用户登录
用户登录大致由下面几个步骤完成:
l 根据用户名取得与用户相关的信息(用户实体对象);
l 判断用户密码是否正确;
l 设置用户凭据Cookie并重定向到登录前页面。
我们主要还是在Membership类中来完成这些工作。为Membership类添加一个Login方法:
 1 public static void Login(string userName, string password, bool rememberMe)
 2 {
 3      // 获取用户。
 4      UserObject user = DataAccess.GetUserByName(userName);
 5      if(user == null)
 6          throw new ArgumentException("用户不存在!""UserName");
 7 
 8      // 检查密码是否正确。
 9      string pwdHash = EncodePassword(password, user.PasswordSalt);
10      if(pwdHash != user.PasswordHash)
11          throw new ArgumentException("密码错误!""Password");
12 
13      // 设置安全Cookie并进行重定向。
14      FormsAuthentication.RedirectFromLoginPage(userName, rememberMe);
15 }
这里重点在于检查密码,不过其工作方式已经在上一篇文章中介绍过了,此处不再赘述。读者应该注意到,对密码进行散列使用的是已经存放在数据实体中的Salt值,而不是重新生成Salt值。
这段示例代码通过抛异常来返回错误信息。读者在实际编写这个方法时,可以创建多个Exception类的派生类,分别表示不同的错误;或者采取其他无需抛异常的方式。这一点仁者见仁,智者见智。
接下来,提供一个简单的登录页,完成收集用户名和密码的工作。login.aspx页面的结构也很简单,只需在<ContentPlaceHolder1>中添加下列代码:
1 <table>
2 <tr><td>用户名:</td><td><asp:TextBox ID="txtUserName" runat="server"></asp:TextBox></td></tr>
3 <tr><td>密码:</td><td><asp:TextBox ID="txtPassword" runat="server" TextMode="Password"></asp:TextBox></td></tr>
4 <tr><td colspan="2">
5 <asp:Button ID="btnLogin" runat="server" Text="登录" onclick="btnLogin_Click" />
6 <asp:CheckBox ID="chkRememberMe" runat="server" Text="记住我" />
7 </td></tr>
8 </table>
9 <asp:Label ID="lblMessage" runat="server"></asp:Label>
最后,在“登录”按钮的事件处理器中添加如下代码,调用刚刚写好的Membership.Login方法,完成登录。
 1 protected void btnLogin_Click(object sender, EventArgs e)
 2 {
 3      try
 4      {
 5          Membership.Login(txtUserName.Text, txtPassword.Text, chkRememberMe.Checked);
 6      }
 7      catch(Exception ex)
 8      {
 9          lblMessage.Text = "错误:" + ex.Message;
10      }
11 }
12 
至此,用户登录功能就OK了。由于之前已经完成了判断用户是否登录的代码,现在就可以运行示例,注册一个用户并尝试从各个页面进入登录页进行登录了。
用户注销
至此,我们已经为用户进入我们的系统提供了宽敞大路;但作为一个负责任的程序,我们还得确保当用户离开我们的程序时,能够“挥一挥衣袖”不留下一丝痕迹。这个安全离开的行为,我们称之为“注销”;要抹去的“痕迹”也就是登录时留下的用户凭据Cookie。
本文的第一部分已经介绍过,FormsAuthentication.SignOut方法可以协助完成这一任务。我们在MasterPage中也预先放置好了一个“注销”链接按钮,在这里,只要为该按钮添加如下事件处理器即可:
1 protected void btnLogout_Click(object sender, EventArgs e)
2 {
3      FormsAuthentication.SignOut();
4      Response.Redirect(Request.RawUrl);
5 }
在这里,首先通过调用FormsAuthentication.SignOut方法移除了用户凭证Cookie。这个方法并不能自动完成跳转,因此我么必须自己完成跳转任务。关于注销后跳转到哪个页面,每个应用程序都有自己的方式(如跳到登录页或跳到首页等),这里选择的是刷新当前页。
小结
好了,到现在为止,完整的用户注册、登录功能就都实现了。由于有了FormsAuthentication类,这一任务已经非常简单了。这一部分仅仅是从实践的角度顺序操作了一下。
下一部分将介绍如何将我们自己的用户实体放到HttpContext.User属性中。

相关文章:

libev源码解析——调度策略

在《libev源码解析——监视器&#xff08;watcher&#xff09;结构和组织形式》中介绍过&#xff0c;监视器分为[2,-2]区间5个等级的优先级。等级为2的监视器最高优&#xff0c;然后依次递减。不区分监视器类型和关联的文件描述符的值&#xff0c;权限高的要优先于权限低的执行…

特斯拉AI团队招兵买马:“英雄不问出处”

2月3日&#xff0c;特斯拉创始人兼CEO埃隆•马斯克发布推特&#xff0c;贴出了Autopilot AI团队招聘人才的信息。马斯克在推特中表示&#xff0c;特斯拉AI团队将直接向马斯克回报&#xff0c;他几乎每天都会与团队保持沟通和交流&#xff0c;并透露团队base在德州奥斯汀。据特斯…

java中重载与重写的区别

最近了解一下重载和重写 一、重载(Overloading) &#xff08;1&#xff09; 方法重载是让类以统一的方式处理不同类型数据的一种手段。多个同名函数同时存在&#xff0c;具有不同的参数个数/类型。 重载Overloading是一个类中多态性的一种表现。 &#xff08;2&#xff09; Ja…

libev源码解析——I/O模型

在《libev源码解析——总览》一文中&#xff0c;我们介绍过&#xff0c;libev是一个基于事件的循环库。本文将介绍其和事件及循环之间的关系。&#xff08;转载请指明出于breaksoftware的csdn博客&#xff09; 目前ibev支持如下IO事件模型&#xff1a; select模型。对应文件是…

“数学不好,干啥都不行!”骨灰级程序员:其实你们都是瞎努力

之前有很多程序员读者向我们抱怨&#xff1a;1&#xff09;做算法优化时&#xff0c;只能现搬书里的算法&#xff0c;遇到不一样的问题&#xff0c;就不会了。2&#xff09;面试一旦涉及到算法和数据结构&#xff0c;如果数学不行&#xff0c;面试基本就凉凉了。3&#xff09;一…

VISTA中注册表项LEGACY_****的删除

在VISTA中如果你错误安装了某个驱动软件&#xff0c;而如果这个驱动安装软件考虑不周&#xff0c;无法卸载&#xff0c;那么你就麻烦了&#xff01;比如我的U盘以前一直使用优易U盘加密软件1.2来做一个隐蔽的U盘。某天我在VISTA上运行了这个U盘加密软件&#xff0c;这个软件运行…

nodejs这个过程POST求

下面是一个web登陆模拟过程。当我们问一个链接&#xff0c;你得到一个表格&#xff0c;然后填写相应的表格值&#xff0c;然后提交登陆。 var http require(http); var querystring require(querystring); http.createServer(function (request, response) {var responseStri…

FTP、HTTP断点续传和多线程的协议基础

使用FTP或HTTP协议的下载软件支持断点续传和多线程的协议基础是&#xff1a;FTP用的是REST和SIZE&#xff1b;HTTP用的是Range。1、FTP实现断点续传的协议基础REST&#xff08;有的服务器可能不支持此命令&#xff09;Syntax: REST positionSets the point at which a file tra…

libev源码解析——定时器原理

本文将回答《libev源码解析——I/O模型》中抛出的两个问题。&#xff08;转载请指明出于breaksoftware的csdn博客&#xff09; 对于问题1&#xff1a;为什么backend_poll函数需要指定超时&#xff1f;我们让其一直等待到有事件发生不是更好么&#xff1f; 答案是“必须要指定超…

AI颠覆经济世界作用被夸大?影响远比媒体头条报道更加复杂

来源 | The Gradient译者 | 刘畅编辑 | 夕颜出品 | AI科技大本营&#xff08;ID&#xff1a;rgznai100&#xff09; 【导读】每天我们都听到有人声称人工智能将改变经济体系&#xff0c;造成大量的失业和垄断。但是&#xff0c;经济学家是如何看待的呢&#xff1f;在第三届AI经…

ArcGIS制图之Sub Points点抽稀

简介 Sub Points工具是 Esri 中国自主开发的一个插件&#xff0c;该工具优先考虑点在空间分布上的均匀合理性&#xff0c;并结合点数据中包含的 "优先级" 属性进行筛选。通过获取每个点在一定范围内拥有的相邻点的数目信息&#xff0c;得到地图中点密度的分布状况。抽…

libev源码解析——定时器监视器和组织形式

我们先看下定时器监视器的数据结构。&#xff08;转载请指明出于breaksoftware的csdn博客&#xff09; /* invoked after a specific time, repeatable (based on monotonic clock) */ /* revent EV_TIMEOUT */ typedef struct ev_timer {EV_WATCHER_TIME (ev_timer)ev_tstamp…

谁说AI无用?疫情下,AI已经代替人类做了很多...

整理 | 夕颜出品 | AI科技大本营&#xff08;ID:rgznai100&#xff09;经历过无比漫长的十多天&#xff0c;疫情至今还没有任何退散的迹象&#xff0c;形势越来越严峻。百度实时大数据报告显示&#xff0c;截至2020年2月4日9时&#xff0c;新型冠状病毒累计报告确诊病例20471例…

关于CSS样式浏览器兼容问题的一些注意事项

CSS技巧1.div的垂直居中问题 vertical-align:middle; 将行距增加到和整个DIV一样高 line-height:200px; 然后插入文字&#xff0c;就垂直居中了。缺点是要控制内容不要换行 2. margin加倍的问题 设置为float的div在ie下设置的margin会加倍。这是一个ie6都存在的bug。解决…

Ember.js 入门指南——查询记录

2019独角兽企业重金招聘Python工程师标准>>> store提供了统一的获取数据的接口。包括创建新记录、修改记录、删除记录等&#xff0c;更多有关Store API请看这个网址的介绍&#xff1a;http://devdocs.io/ember/data/classes/ds.store。 为了演示这些方法的使用我们结…

C# 视频监控系列(9):服务器端——数据捕获(抓图 + 录像)

前言 录像功能是监控系统中最重要的功能之一&#xff0c;除了本文的功能实现外&#xff0c;还需要你自己考虑合适的存储策略&#xff1a;存储大小、时间段、存储盘符等。 注意 本系列文章限于学习交流&#xff0c;注重过程&#xff0c;由于涉及公司&#xff0c;所以不提供源代码…

疫情当下,你是在家里躺着刷抖音?还是在做这些?

2020年本来可以是很开心的一年没想到一开头就给了我们一个重重的一击疫情的出现让我们非常的恐慌新型病毒肺炎让我们无处可躲原来热闹的新年因为疫情让我们逼不得已只能待在家里走亲访友更是不可能的就连原来约好的相亲也泡汤了因为封城、封村、封小区、封路了而这些也只是为了…

代码打补丁的利器——diff和patch

一般来说&#xff0c;如果我们在研发过程中需要对代码进行修改&#xff0c;是不需要通过打补丁的方式的&#xff0c;因为我们可以直接改动文件即可。但是如果针对一款要上线的产品&#xff0c;我们总不能在研发的电脑上编译通过后直接发布到线上的。&#xff08;转载请指明出于…

React Namespaced Components

2019独角兽企业重金招聘Python工程师标准>>> var MyForm React.createClass({...}); var MyForm.Row React.createClass({...}); var MyForm.Label React.createClass({...}); var MyForm.Input React.createClass({...}); This feature is available in v0.11 …

Linux下HOOK动态链接库中API的方法

2012年&#xff0c;我写了一篇介绍Windows系统下Ring3层API的hook方案——《一种注册表沙箱的思路、实现——Hook Nt函数》&#xff0c;其在底层使用了微软的Detours库。5年后&#xff0c;我又遇到这么一个问题&#xff0c;但是系统变成了Linux。我最开始的想法是找一个Linux下…

NAT的配置与相关概念的理解

试验背景&#xff1a;随着接入因特网的计算机数量不断猛增&#xff0c;IPv4版本地址资源也就愈加显得捉襟见肘。好多企业申请的IP地址都是经过子网不断划分得到的。A类&#xff0c;B类地址基本已用完&#xff0c;而一般的用户根本就申请不到整段的公网C类地址。如果&#xff0c…

AAAI 2020论文解读:商汤科技发布新视频语义分割和光流联合学习算法

来源 | Every Frame Counts: Joint Learning of Video Segmentation and Optical Flow编辑 | Carol出品 | AI科技大本营&#xff08;ID&#xff1a;rgznai100&#xff09; 商汤科技研究团队发表论文《Every Frame Counts: Joint Learning of VideoSegmentation and Optical Flo…

互联网+和创业潮,互联网+前提条件是什么?互联网+做什么?

在大众创业&#xff0c;万众创新的大浪下&#xff0c;凭着对新技术的敏感和青春激情&#xff0c;创业新军不断涌现.... 互联网创业浪潮, 如雨后春笋......,互联网渗透每个人的心中。创业不是赶时髦&#xff0c;而是一条非常孤独&#xff0c;艰难的路。实施“互联网&#xff0b;…

C++拾趣——C++11的语法糖auto

C是一种强类型的语言&#xff0c;比如变量a&#xff0c;如果声明它是整型&#xff0c;则之后只能将它作为整型来用。这和其他弱类型的语言有很大的区别&#xff0c;比如python中&#xff0c;我们可以让a在第一行是个整型&#xff0c;第三行是一个字符串。&#xff08;转载请指明…

“数学不行,啥都干不好!”骨灰级程序员:这比努力重要1000倍

之前有很多程序员读者向我们抱怨&#xff1a;1&#xff09;做算法优化时&#xff0c;只能现搬书里的算法&#xff0c;遇到不一样的问题&#xff0c;就不会了。2&#xff09;面试一旦涉及到算法和数据结构&#xff0c;如果数学不行&#xff0c;面试基本就凉凉了。3&#xff09;一…

跳槽 你准备好了吗

“人往高处走”&#xff0c;这固然没有错。但是&#xff0c;说来轻巧的一句话&#xff0c;它却包含了为什么“走”、什么是“高”、怎么“走”、什么时候“走”&#xff0c;以及“走”了以后怎么办等一系列问题。跳槽是一门学问&#xff0c;也是一种策略。“人往高处走”&#…

C++:常类型Const

常类型&#xff1a;使用类型修饰符const说明的类型&#xff0c;常类型的变量或对象成员的值在程序运行期间是不可改变的。 3.10.1 常引用 如果在说明引用时用const修饰&#xff0c;则被说明的引用为常引用。如果用常引用做形参&#xff0c;便不会产生对实参 的不希望的更改。常…

JQuery制作的toolTip,针对图片预览效果

昨天做了一个文字版的toolTip&#xff0c;后来想想现在大家都爱看图&#xff0c;文字未免有点单调了点&#xff0c;那我们就来个图片式的预览。代码比较简单&#xff0c;我就不多说了。 欢迎来到 买礼网 选购礼品&#xff01; 畅游鄂西山水风光尽在 恩施旅游资讯网首先看看调用…

29篇计算机视觉领域论文,篇篇惊艳!内附链接!

作者 | 微软亚洲研究院本文经授权转载自微软研究院AI头条&#xff08;ID&#xff1a;MSRAsia&#xff09;1. Deep High-Resolution Representation Learning for Human Pose Estimation论文链接&#xff1a;https://arxiv.org/pdf/1902.09212.pdf该论文在提出了一个新的网络Hig…

绑定CPU逻辑核心的利器——taskset

在工作中&#xff0c;我们可能遇到这样的需求&#xff1a;如何评估程序在一核和多核下的工作效率差距&#xff1f;最简单的想法是找一台只有一个CPU逻辑核的机器和一台有多个逻辑核的机器。&#xff08;转载请指明出于breaksoftware的csdn博客&#xff09;但是这种方式有明显的…