欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

ASP.NET 身份教程 II:(用户管理 3)用户登录和电子邮件验证功能

最编程 2024-04-28 13:25:41
...
三.用户登录

当用户在你的网站上完成注册之后,下次再光顾你的网站时,只需要登录一下即可,登录时需要提供帐号和密码。还有在用户注册时,如果注册成功,也需要执行一下登录操作,在 7.1 节的注册用户逻辑中,没有实现登录操作,在本节中会完成登录操作。登录成功后,会在顶部导航栏上显示用户的帐号和注销按钮,表示当前用户已经登录该网站.

在 ASP.NET Identity 中,使用登录 API 就可以轻松的完成登录,不需要我们编写任何额外的登录逻辑代码,包括用户在客户端 Cookies 中的存储,都是由 ASP.NETIdentity 来完成的。

3.2. 继承 SignInManager<User, string>

在 ASP.NET Identity 中实现用户登录,需要配置应用程序登录管理器。应用程序登录管理器是继承了 SignInManager<User, string>的类来完成的。

在“App_Start”文件夹中的“IdentityConfig.cs”文件中的“Yidosoft.Identity”名称空间下编写如下代码::

/// <summary>
	/// 配置应用程序登录管理器
	/// </summary>
	public class SignInManager : SignInManager<User, string>
	{
		public SignInManager(UserManager userManager, IAuthenticationManager authenticationManager) : base(userManager, authenticationManager)
		{
		}

		public override Task<ClaimsIdentity> CreateUserIdentityAsync(User user)
		{
			return user.GenerateUserIdentityAsync((UserManager)UserManager);
		}

		public static SignInManager Create(IdentityFactoryOptions<SignInManager> options, IOwinContext context)
		{
			return new SignInManager(context.GetUserManager<UserManager>(), context.Authentication);
		}
	}

其实这个内容是一开始就在前面教程中进行了使用。

然后再在“Models”文件夹中打开“User.cs”文件,并在 User 类中添加如下方法,其实上次已加入了。

public class User: IdentityUser
	{
		public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<User> manager)
		{
			// 请注意,authenticationType 必须与 CookieAuthenticationOptions.AuthenticationType 中定义的相应项匹配
			var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
			// 在此处添加自定义用户声明
			return userIdentity;
		}
		#region 添加字段
		[Display(Name = "微信")]
		public virtual string WX { get; set; }
		 

在此代码可以看出,ASP.NET Identity 是完全支持基于声明的身份验证的

 

.3. 注册 SignInManager

配置好的应用程序登录管理器,还需要在“Startup”类中注册一下,打开“App_Start”文件夹下的“Startup.Auth.cs”文件,然后添加如下代码:

app.CreatePerOwinContext<SignInManager>(SignInManager.Create);

4..4. 编写 LoginViewModel

在“ViewModels”文件夹中添加一个名称为“LoginViewModel”的类,然后编写如下代码:因为我使用的userName登录,所以我声明的字段没有Email,而“RememberMe”属性是一个bool 类型,用于让用户选择是否在本地记住账号和密码信息。

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Web;

namespace jsdhh2.ViewModels
{
	public class LoginViewModel
	{
		[Required]
		[Display(Name = "用户名")]
		public string UserName { get; set; }

		[Required]
		[DataType(DataType.Password)]
		[Display(Name = "密码")]
		public string Password { get; set; }

		[Display(Name = "记住我?")]
		public bool RememberMe { get; set; }
	}
}
.5. 编写 Login 方法

在“UserController”控制器中编写一个带有 HttpGet 特性的 Login()方法,用于呈现用户填写登录账户和密码的页面。再编写一个带有 HttpPost 特性的 Login()方法,用于将用户填写的账户和密码提交到服务器进行验证。代码如下:

private SignInManager _signInManager;
		public SignInManager SignInManager
		{
			get
			{
				return _signInManager ?? HttpContext.GetOwinContext().Get<SignInManager>();
			}
			private set
			{
				_signInManager = value;
			}
		}

  private ActionResult RedirectToLocal(string returnUrl)
        {
            if (Url.IsLocalUrl(returnUrl))
            {
                return Redirect(returnUrl);
            }
            return RedirectToAction("Index", "Home");
        }
    }

SignInManager 属性用于获取当前的应用程序登录管理器,然后就可以使用与登录相关的方法或属性。RedirectToLocal()方法用于完成某个某个操作后进行跳转,如果存在返回的 Url,则跳转到返回 Url,如果没有,则跳转到 Home 控制器的 Index()方法上。

 

/// <summary>
		/// 登录
		/// </summary>
		/// <param name="returnUrl"></param>
		/// <returns></returns>
		[HttpGet]
		[AllowAnonymous]
		public ActionResult Login(string returnUrl)
		{
			ViewBag.ReturnUrl = returnUrl;
			return View();
		}

		/// <summary>
		/// 登录
		/// </summary>
		/// <param name="model"></param>
		/// <param name="returnUrl"></param>
		/// <returns></returns>
		[HttpPost]
		[AllowAnonymous]
		[ValidateAntiForgeryToken]
		public async Task<ActionResult> Login(jsdhh2.ViewModels.LoginViewModel model, string returnUrl)
		{
			if (!ModelState.IsValid)
			{
				return View(model);
			}

			// 这不会计入到为执行帐户锁定而统计的登录失败次数中
			// 若要在多次输入错误密码的情况下触发帐户锁定,请更改为 shouldLockout: true
			var result = await SignInManager.PasswordSignInAsync(model.UserName, model.Password, model.RememberMe, shouldLockout: false);
			switch (result)
			{
				case SignInStatus.Success:
					return RedirectToLocal(returnUrl);
				case SignInStatus.LockedOut:
					return View("Lockout");
				case SignInStatus.RequiresVerification:
					return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = model.RememberMe });
				case SignInStatus.Failure:
				default:
					ModelState.AddModelError("", "无效的登录尝试。");
					return View(model);
			}
		}

同时还有个新方法

  private ActionResult RedirectToLocal(string returnUrl)
        {
            if (Url.IsLocalUrl(returnUrl))
            {
                return Redirect(returnUrl);
            }
            return RedirectToAction("Index", "Home");
        }
    }

在 HttpPost 的 Login()方法中,如果登录成功,则转到返回 Url,如果用户已锁定,则返回到”Lockout”视图,如果用户未完成验证(邮箱手机验证或双重验证),则转到“SendCode”方法。其它情况,则添加登录失败的错误信息。

6. 编写 Login 视图

“Views”“User”文件夹中添加一个名称为“Login”的视图,并编写如下代码:

@using jsdhh2.Models
@model jsdhh2.ViewModels.LoginViewModel
@{
	ViewBag.Title = "登录";
}
<div class="row">
	<div class="col-md-8">
		<section id="loginForm">
			@using (Html.BeginForm("Login", "User", new { ReturnUrl = ViewBag.ReturnUrl }, FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
			{
				@Html.AntiForgeryToken()
				<h4>@ViewBag.Title。</h4>
				<hr />
				@Html.ValidationSummary(true, "", new { @class = "text-danger" })
				<div class="form-group">
					@Html.LabelFor(m => m.UserName, new { @class = "col-md-2 control-label" })
					<div class="col-md-10">
						@Html.TextBoxFor(m => m.UserName, new { @class = "form-control" })
						@Html.ValidationMessageFor(m => m.UserName, "", new { @class = "text-danger" })
					</div>
				</div>
				<div class="form-group">
					@Html.LabelFor(m => m.Password, new { @class = "col-md-2 control-label" })
					<div class="col-md-10">
						@Html.PasswordFor(m => m.Password, new { @class = "form-control" })
						@Html.ValidationMessageFor(m => m.Password, "", new { @class = "text-danger" })
					</div>
				</div>
				<div class="form-group">
					<div class="col-md-offset-2 col-md-10">
						<div class="checkbox">
							@Html.CheckBoxFor(m => m.RememberMe)
							@Html.LabelFor(m => m.RememberMe)
						</div>
					</div>
				</div>
				<div class="form-group">
					<div class="col-md-offset-2 col-md-10">
						<input type="submit" value="登录" class="btn btn-default" />
					</div>
				</div>
				<p>
					@Html.ActionLink("注册为新用户", "Add")
				</p>
				@* 在为密码重置功能启用帐户确认后启用此项
					<p>
						@Html.ActionLink("忘记了密码?", "ForgotPassword")
					</p>*@
			}
		</section>
	</div>
</div>

7.登录样式

 输入用户名,秘码登录 

 

 

 

图 7-23 上看,是转到了网站的首页,这个首页在“RouteConfig.cs“中默认配置好的。如下代码

namespace Yidosoft.Identity
{
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id =
UrlParameter.Optional }
);
}
}
}

在此代码中,配置的默认首页就是 Home 控制器下的 Index()方法,默认的首页地址,在浏览器的地址中,控制器和方法名都是可以省略的。所以图 7-23 转向的地址就是” htttp://localhost:6460/Home/Index”,只是省略了而已。在登录逻辑代码中,转向了 Home/Index,表示用户登录成功了。

8.创建首大页

因为我们是用MVC项目first code,所以home的index视图是自动生成的。

我们主要是编写“_LoginPartial.cshtml“视图。用于呈现用户的登录信息。由于网站的顶部导航视图代码是编写在“Views”\“Shared”文件夹下的“_Layout.cshtml”布局视图中的。所以我们也将“_LoginPartial.cshtml”放在“Views”/“Shared”文件夹下。

@using Microsoft.AspNet.Identity
@if (Request.IsAuthenticated)
{
    using (Html.BeginForm("LogOff", "Account", FormMethod.Post, new { id = "logoutForm", @class = "navbar-right" }))
    {
    @Html.AntiForgeryToken()

    <ul class="nav navbar-nav navbar-right">
        <li>
            @Html.ActionLink("你好," + User.Identity.GetUserName() + "!", "Index", "Manage", routeValues: null, htmlAttributes: new { title = "Manage" })
        </li>
        <li><a href="javascript:document.getElementById('logoutForm').submit()">注销</a></li>
    </ul>
    }
}
else
{
    <ul class="nav navbar-nav navbar-right">
        <li>@Html.ActionLink("注册", "Register", "Account", routeValues: null, htmlAttributes: new { id = "registerLink" })</li>
        <li>@Html.ActionLink("登录", "Login", "Account", routeValues: null, htmlAttributes: new { id = "loginLink" })</li>
    </ul>
}

然后打开“_Layout.cshtml”,将“_LoginPartial.cshtml”以部分视图的形式嵌入。如图7-26 所示:

 

 

@Html.Partial("_LoginPartial")
代码,将部分视图嵌入到布局视图中。现在运行一下首页。

的顶部导航栏上,是不是已经看到在前面登录的账户名了,并且还有注销按钮,注销按钮的功能后面会讲解
5.  确认用户

确认用户是指当用户在网站中注册成功之后,会通过邮件或手机发送一个验证码,然后通过这个验证码来确认用户的正确性。这里主要讲解使用电子邮件来确认用户,使用 163 邮箱的网关来发送确认用户的
电子邮件。

5.1. 配置 EmailService

配置 EmailService 服务,需要继承 IidentityMessageService 接口,实现 SendAsync()方法来发送邮件。在“App_Start”文件夹下的“IdentityConfig.cs”文件中的“Yidosoft.Identity”名称空间
下编写如下代码:

public class EmailService : IIdentityMessageService
	{
		public Task SendAsync(IdentityMessage message)
		{
			//编写发送邮件的代码
			Common.SendMail("smtp.126.com", 25, "lbj$4561200", "yidosoft@126.com", message.Destination, message.Subject, message.Body);
			return Task.FromResult(0);
		}
	}

在 SendAsync()方法中就可以编写发送电子邮件的代码。这里使用了 Common 类中的静态方法 SendMail()来发送邮件,使用的是 smtp.126.com 来发送邮件。在“Yidosoft.Identity”的根目录下添加一个 Common 类文件,然后编写如下代码

 

using System.Net;
using System.Net.Mail;

namespace jsdhh2
{
	public class Common
	{
		public static bool SendMail(string strSmtpServer, int iSmtpPort, string Password, string strFrom, string strto, string strSubject, string strBody)
		{

			//设置发件人信箱,及显示名字 
			MailAddress mailFrom = new MailAddress(strFrom);
			//设置收件人信箱,及显示名字 
			MailAddress mailTo = new MailAddress(strto);
			//创建一个MailMessage对象 
			MailMessage oMail = new MailMessage(mailFrom, mailTo);
			oMail.Subject = strSubject;
			oMail.Body = strBody;
			oMail.IsBodyHtml = true; //指定邮件格式,支持HTML格式 
			oMail.BodyEncoding = System.Text.Encoding.GetEncoding("GB2312");//邮件采用的编码 
			oMail.SubjectEncoding = System.Text.Encoding.GetEncoding("GB2312");//邮件采用的编码 
			oMail.Priority = MailPriority.High;//设置邮件的优先级为高 
											   //发送邮件服务器 
			SmtpClient client = new SmtpClient();
			//发送邮件服务器的smtp
			//每种邮箱都不一致
			client.Host = strSmtpServer; //指定邮件服务器 
										 //发送邮件服务器端口
			client.Port = iSmtpPort;
			//设置超时时间 
			client.Timeout = 9999;
			//设置为发送认证消息
			client.UseDefaultCredentials = true;
			//指定服务器邮件,及密码 
			//发邮件人的邮箱地址和密码
			client.Credentials = new NetworkCredential(strFrom, Password);
			client.Send(oMail); //发送邮件 
								//释放资源
			mailFrom = null;
			mailTo = null;
			client.Dispose();//释放资源
			oMail.Dispose(); //释放资源 
			return true;
		}
	}
}

然后在IdentityConfig.CS中的“UserManager”类中的“Create()”方法中添加如下代码:

  /// <summary>
    /// 配置用户管理器
    /// </summary>
    public class UserManager : UserManager<User>
    {
        public UserManager(IUserStore<User> store) : base(store) { }

        public static UserManager Create(IdentityFactoryOptions<UserManager> options, IOwinContext context)
        {
            var manager = new UserManager(new UserStore<User>(context.Get<Models.IdentityDbContext>()));

            manager.EmailService = new EmailService();
            var dataProtectionProvider = options.DataProtectionProvider;
            if (dataProtectionProvider != null)
            {
                manager.UserTokenProvider =
                    new DataProtectorTokenProvider<User>(dataProtectionProvider.Create("EmailConfirmation"));
            }

            return manager;
        }
    }

.5.2. 发送确认用户邮件

带 HttpPost 的 Add()方法中添加发送确认用户的邮件,表示在用户注册后需要验证一下。

打开“UserController.cs”文件,找到带有 HttpPost 特性的 Add()方法。原来的代码如下:

HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Add(AddUserViewModel addUserModel)
{
if (ModelState.IsValid)
{
var user = new User { UserName = addUserModel.Email, Email = addUserModel.Email,
QQNumber = addUserModel.QQNumber, WechatNumber = addUserModel.WechatNumber };
var result = await UserManager.CreateAsync(user, addUserModel.Password);
if (result.Succeeded)
{
return RedirectToAction("List", "User");
}
AddErrors(result);
}
return View(addUserModel);
}

现在修改一下,添加用户登录和发送确认用户电子邮件的代码,如下:

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Add(AddUserViewModel addUserModel)
{
if (ModelState.IsValid)
{
var user = new User { UserName = addUserModel.Email, Email = addUserModel.Email,
QQNumber = addUserModel.QQNumber, WechatNumber = addUserModel.WechatNumber };
var result = await UserManager.CreateAsync(user, addUserModel.Password);
if (result.Succeeded)
{
//登录
await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser:
false);
// 发送包含此链接的电子邮件
string code = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id);
var callbackUrl = Url.Action("ConfirmEmail", "User", new { userId = user.Id,
code = code }, protocol: Request.Url.Scheme);
await UserManager.SendEmailAsync(user.Id, "确认你的帐户", "请通过单击 <a
href=\"" + callbackUrl + "\">这里</a>来确认你的帐户");
//return RedirectToAction("List", "User");
return RedirectToAction("Index", "Home");
}
AddErrors(result);
}
return View(addUserModel);
}

在此代码中,使用 UserManager.GenerateEmailConfirmationTokenAsync()方法,根据 User.Id 一成一个 Code,然后附加到验证 Url 中。最后使用 UserManager.SendEmailAsync()来发送电子邮件。

5.3. 接收电子邮件:在这要说明,我这没有成功,是因为一是我使用的USERNAME,而非Email,同时我未正确配置邮箱端口。

现在新注册一个用户,使用真实的电子邮件,然后接收该电子邮件,并完成邮件验证。 

先按真实的EMAIL地址注册,并登录。再到邮箱中去查看是否有邮箱。

 

然后登录邮箱

正确转到首页了,并且显示了当前注册的用户信息,表示登录成功了。现在登录注册的邮件,看是否能接收到邮件。如图 7-30 所示:

 

中已经接收到邮件了,点击“这里”链接去确认账户。如图 7-31 所示

由于我们还没有编写邮件验证的代码,所以图 7-31 的页面是不存在的

.5.4. 邮箱验证

在“UserController”控制器中添加一个名称为“ConfirmEmail()”的方法。代码如下:

		/// <summary>
		/// 邮箱验证
		/// </summary>
		/// <param name="userId"></param>
		/// <param name="code"></param>
		/// <returns></returns>
		[HttpGet]
		[AllowAnonymous]
		public async Task<ActionResult> ConfirmEmail(string userId, string code)
		{
			if (userId == null || code == null)
			{
				return View("Error");
			}
			var result = await UserManager.ConfirmEmailAsync(userId, code);
			return View(result.Succeeded ? "ConfirmEmail" : "Error");
		}

ConfirmEmail()方法需要接收到 2 个参数,1 个是 userId,1 个是 code,与图 7-31浏览器中的 Url 参数是相对应的。
在数据库中存储的用户信息表是 AspNetUsers,有一个字段是“EmailConfirmed”,是 bit 类型,未验证时,该字段的值为 false,验证通过后,修改后 true。如图 7-32

 

在 ConfirmEmail()方法中用到了“Error”和“ConfirmEmail”这两个视图,在“Views”“User”中添加“ConfirmEmail.cshtml”视图,并编写如下代码:

@{
ViewBag.Title = "确认电子邮件";
}

<h2>@ViewBag.Title。</h2>
<div>
<p>
感谢你确认电子邮件。请
@Html.ActionLink("单击此处登录", "Login", "Account", routeValues: null, htmlAttributes: new { id = "loginLink" })
</p>
</div>

在“Shared”文件夹中添加“Error.cshtml”视图,并编写如下代码:这个基本是自带的

@model System.Web.Mvc.HandleErrorInfo

@{
    ViewBag.Title = "Error";
}

<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>

好了,现在重新运行图 7-31 的页面,如图 7-33 所示

 

图 7-33 提示已经确认了电子邮件,可以正常登录了。那么我们回到数据库看一下,如图 7-34 所示:

 

 

在图 7-34 中,“EmailConfirmed”字段的值已经变为“True”了,表示该用户的电子邮
件已经验证过了

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

推荐阅读