MVC5默认项目解析

前言

本文简单介绍Identity的使用,使用的案例是基于默认的Mvc5项目(只勾选MVC,不勾选WebApi).读者可以拿着项目源码对照地看.

ASP.NET Identity特性

  • One ASP.NET Identity 系统
  • 更容易加入用户的个人资料信息
  • 持久化控制
  • 单元测试能力
  • 角色提供程序
  • 基于声明的 (Claims Based)
  • 社交账号登录提供程序 (Social Login Providers)
  • Windows Azure Active Directory
  • OWIN 集成
  • NuGet 包

Identity包

Identity是依赖于EF的Code First 和Owin的,当然你可以自己拿着Micsoft.AspNet.Identity.Core重写一份不依赖EF的Identity.

用户数据库由EF Code First创建,账号等功能通过Owin的中间件形式加入到程序中(Owin中间件相当于Asp.Net 的Module)

  • Microsoft.AspNet.Identity.EntityFramework

    这个包容纳了 ASP.NET Identity 基于 Entity Framework 的实现。它将 ASP.NET Identity 的数据和架构存入 SQL Server。

  • Microsoft.AspNet.Identity.Core

    这个包容纳了 ASP.NET Identity 的核心接口。它可以用来编写 ASP.NET Identity 的其他实现,用以支持其他持久化存储系统,如 Windows Azure 表存储, NoSQL 数据库等等。

  • Microsoft.AspNet.Identity.OWIN

    这个包为 ASP.NET 应用程序提供了将 ASP.NET Identity 引入到 OWIN 身份验证的功能。当你在为应用程序加入登录功能,调用 OWIN Cookie 身份验证中间件来生成 cookie 时,会用到这个包。

技术分享

如上图所示Identity依赖了很多东西每个都是大框架,因此本文要求读者有一定的EF Code First和Owin知识

基本

Identity采用EF Code First,他内置了一些类用户创建数据库,因此 
在默认情况下Identity会创建下列表格 
技术分享

Identity用的数据库上下文有点特别,是继承IdentityDbContext,正是继承了这个特殊的上下文,才会有那么多默认表

    1. public class MyIdentityDbContext : IdentityDbContext<MyIdentityUser>
    2. {
    3. //可以在这里扩展自己的表,配置数据表
    4. }

MyIdentityUser我自定义的,是实现IdentityUser接口的类

默认情况下是没有数据库的,直到创建一个新用户,EF才会去创建数据库 
这个数据库会创建在App_Data下 
技术分享 
因为在Web.config配置了数据库生成位置

    1. <connectionStrings>
    2. <add name="DefaultConnection" connectionString="Data Source=(LocalDb)\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\aspnet-DefaultMVC5-20160806094030.mdf;Initial Catalog=aspnet-DefaultMVC5-20160806094030;Integrated Security=True"
    3. providerName="System.Data.SqlClient" />
    4. </connectionStrings>

IdentityUser

对应数据表中AspNetUsers 
该类描述用户数据.我们先只注意用户部分忽略登入记录,角色,申明的部分

IdentityUser默认成员

名称描述
Claims返回用户的声明集合
Email返回用户的E-mail地址
Id返回用户的唯一ID
Logins返回用户的登录集合
PasswordHash返回哈希格式的用户口令,在“实现Edit特性”中会用到它
Roles返回用户所属的角色集合
PhoneNumber返回用户的电话号码
SecurityStamp返回变更用户标识时被修改的值,例如被口令修改的值
UserName返回用户名

AspNetUser表结构 
技术分享

可以使用EF Code First相关的知识对默认表进行配置

    1. //改表名
    2. protected override void OnModelCreating(DbModelBuilder modelBuilder)
    3. {
    4. modelBuilder.Entity<IdentityUserRole>().ToTable("MyUserRoles");
    5. }
    6. //忽略列,注意不是所有列都能忽略
    7. modelBuilder.Entity<MyIdentityUser>().Ignore(x => x.PhoneNumberConfirmed);

UserManager

用户管理类,其职责相当于业务逻辑层

名称描述
ChangePasswordAsync(id, old, new)为指定用户修改口令
CreateAsync(user)创建一个不带口令的新用户
CreateAsync(user, pass)创建一个带有指定口令的新用户
DeleteAsync(user)删除指定用户
FindAsync(user, pass)查找代表该用户的对象,并认证其口令
FindByIdAsync(id)查找与指定ID相关联的用户对象
FindByNameAsync(name)查找与指定名称相关联的用户对象,第14章“种植数据库”时会用到这个方法
UpdateAsync(user)将用户对象的修改送入数据库
Users返回用户枚举

同样的可以用继承的方式扩展自己的用户管理类

准备工作

在使用Identity前先要做一些配置 
首先是Owin 
默认的项目会创建一个Startup.cs,该类上的OwinStartupAttribute特性标注了该类为启动类 
这个类含有一个名称为Configuration的方法,该方法由OWIN基础架构进行调用,并为该方法传递一个Owin.IAppBuilder接口的实现,由它支持应用程序所需中间件的设置

    1. [assembly: OwinStartupAttribute(typeof(DefaultMVC5.Startup))]
    2. namespace DefaultMVC5
    3. {
    4. public partial class Startup
    5. {
    6. public void Configuration(IAppBuilder app)
    7. {
    8. ConfigureAuth(app);
    9. }
    10. }
    11. }

同时这个类是个部分类,在App_start中能找到另一部分ConfigureAuth就是用于配置Identity

    1. public void ConfigureAuth(IAppBuilder app)
    2. {
    3. app.CreatePerOwinContext(ApplicationDbContext.Create);
    4. app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
    5. //省略,先不解释
    6. }

Startup只在网站启动的时候执行,上面这段代码的CreatePerOwinContext需要传入一个委托,这个委托能返回一个对象,而这个对象在一次请求中是唯一的.所以非常时候放置类似数据库上下文之类的类. 
本质是每当用户请求时候Owin讲调用这些委托来创建对象,并把对象保存到OwinContext中.然后可以在应用程序的任何位置使用

    1. HttpContext.GetOwinContext().Get<ApplicationSignInManager>()
    2. //你可能会看到
    3. HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
    4. //这个泛型扩展方法内部调用了context.Get<TManager>();,感觉这个扩展方法只是用来打酱油的

来获得这个对象.

GetOwinContext是扩展方法,他会从HttpContext.Items中获得Owin之前保存在里面的信息,再生成OwinContext

总之使用CreatePerOwinContext可以保存一个对象在Owin上下文,使得一次请求中用到同一个对象.

ApplicationDbContext

/Models/IdentityModels.cs

数据库上下文类和用户类都是继承Identity内置类的,为了能扩展自己想要的表或表的字段

    1. public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
    2. {
    3. public ApplicationDbContext()
    4. : base("DefaultConnection", throwIfV1Schema: false)
    5. {
    6. }
    7. //给Owin用的
    8. public static ApplicationDbContext Create()
    9. {
    10. return new ApplicationDbContext();
    11. }
    12. }
ApplicationUserManager

/App_Start/IdentityConfig.cs

    1. public class ApplicationUserManager : UserManager<ApplicationUser>
    2. {
    3. public ApplicationUserManager(IUserStore<ApplicationUser> store)
    4. : base(store)
    5. {
    6. }
    7. public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
    8. {
    9. var manager = new ApplicationUserManager(new UserStore<ApplicationUser>(context.Get<ApplicationDbContext>()));
    10. /*
    11. manager配置代码
    12. */
    13. return manager;
    14. }
    15. }

值得注意的是Manager的创建需要用到UserStore,如果ApplicationUserManager相等于业务层,那么他的职责相当于数据层. 
还有一点是这个Create方法的参数与ApplicationDbContext的Create不同

    1. IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context

这个Create也也是能被Owin的CreatePerOwinContext使用.参数context就是Owin上下文,Create中使用context.Get<ApplicationDbContext>获得保存在context中的ApplicationDbContext对象而不用再次手动创建

注册案例

Controllers/AccountController.cs

账号管理的相关代码在这个控制器中,你会常看到这类代码,从Owin上下文获得ApplicationUserManager对象,以便管理用户

    1. private ApplicationUserManager _userManager;
    2. public ApplicationUserManager UserManager
    3. {
    4. get
    5. {
    6. return _userManager ?? HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
    7. }
    8. private set
    9. {
    10. _userManager = value;
    11. }
    12. }
    1. [HttpPost]
    2. [AllowAnonymous]
    3. [ValidateAntiForgeryToken]
    4. public async Task<ActionResult> Register(RegisterViewModel model)
    5. {
    6. if (ModelState.IsValid)
    7. {
    8. //创建新用户
    9. var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
    10. var result = await UserManager.CreateAsync(user, model.Password);
    11. if (result.Succeeded)
    12. {
    13. //如果注册成功同时登入,SignInManager后面解释
    14. await SignInManager.SignInAsync(user, isPersistent:false, rememberBrowser:false);
    15. return RedirectToAction("Index", "Home");
    16. }
    17. AddErrors(result);
    18. }
    19. // 如果我们进行到这一步时某个地方出错,则重新显示表单
    20. return View(model);
    21. }

登入案例

用户登入后有一个比较重要的步骤让网站记住这个用户登入了(可以说是授权),传统做法会把用户数据类保存到Session中用户再请求使用查看他是否在Session保存了用户数据.而Session这种做法是利用Cookie来标识用户. 
在Identity中并不是用Session,但还是借用了Cookie 
为了开启Cookie授权在Startup类中使用这个中间件(Middleware)

    1. app.UseCookieAuthentication(new CookieAuthenticationOptions
    2. {
    3. AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
    4. LoginPath = new PathString("/Account/Login"),//当访问未授权页面时将自定跳转到这个位置
    5. CookieName = "MyCookieName",//自定义Cookie名称
    6. Provider = new CookieAuthenticationProvider
    7. {
    8. // 当用户登录时使应用程序可以验证安全戳。
    9. // 这是一项安全功能,当你更改密码或者向帐户添加外部登录名时,将使用此功能。
    10. OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
    11. validateInterval: TimeSpan.FromMinutes(30),
    12. regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
    13. }
    14. });

在亮出MVC5默认项目代码前,我想先展示下<<Pro Asp.Net MVC5 Platform>>的代码,因为他更加的直观.

    1. [HttpPost]
    2. [AllowAnonymous]
    3. [ValidateAntiForgeryToken]
    4. public async Task<ActionResult> Login(LoginModel details, string returnUrl) {
    5. if (ModelState.IsValid) {
    6. AppUser user = await UserManager.FindAsync(details.Name,details.Password);
    7. if (user == null) {
    8. ModelState.AddModelError("", "Invalid name or password.");
    9. } else {
    10. //获得用户的标识,所有的标识都实现IIdentity接口,这个是基于声明的标识,声明下文再讲,只要知道他与授权有关
    11. ClaimsIdentity ident = await UserManager.CreateIdentityAsync(user,DefaultAuthenticationTypes.ApplicationCookie);
    12. AuthManager.SignOut();
    13. AuthManager.SignIn(new AuthenticationProperties {
    14. IsPersistent = false}, ident);
    15. return Redirect(returnUrl);
    16. }
    17. }
    18. ViewBag.returnUrl = returnUrl;
    19. return View(details);
    20. }

这块代码很直观,获得用户账号密码,去数据库查是否存在,如果存在就登入,顺带获得用户的声明信息.

下面是MVC5默认项目中的代码

    1. [HttpPost]
    2. [AllowAnonymous]
    3. [ValidateAntiForgeryToken]
    4. public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
    5. {
    6. if (!ModelState.IsValid)
    7. {
    8. return View(model);
    9. }
    10. // 这不会计入到为执行帐户锁定而统计的登录失败次数中
    11. // 若要在多次输入错误密码的情况下触发帐户锁定,请更改为 shouldLockout: true
    12. var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false);
    13. switch (result)
    14. {
    15. case SignInStatus.Success:
    16. return RedirectToLocal(returnUrl);
    17. case SignInStatus.LockedOut:
    18. return View("Lockout");
    19. case SignInStatus.RequiresVerification:
    20. return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = model.RememberMe });
    21. case SignInStatus.Failure:
    22. default:
    23. ModelState.AddModelError("", "无效的登录尝试。");
    24. return View(model);
    25. }
    26. }

这份代码中并没有上面那样直观,它用了SignInManager,这个是个ApplicationSignInManager类,很容易猜到他是自定义的类,继承自SignInManager(Identity内置的).该类是利用UserManager执行一系列登入操作 
其实内部实现大致就与上上段代码一样,也是查找用户判断是否存在….

但它做的更多,单PasswordSignInAsync这个方法它不仅负责查询用户,登入用户,还负责记录用户登入记录(登入失败几次,对于被锁定用户的处理…).

用户信息验证

任何用户输入都需要验证,用户信息更是如此. 
在默认项目中不仅利用了MVC内置的模型验证,还利用了Identity的验证器. 
就拿注册来说,首先自定义了ViewModel,并打上验证特性

    1. public class RegisterViewModel
    2. {
    3. [Required]
    4. [EmailAddress]
    5. [Display(Name = "电子邮件")]
    6. public string Email { get; set; }
    7. [Required]
    8. [StringLength(100, ErrorMessage = "{0} 必须至少包含 {2} 个字符。", MinimumLength = 6)]
    9. [DataType(DataType.Password)]
    10. [Display(Name = "密码")]
    11. public string Password { get; set; }
    12. [DataType(DataType.Password)]
    13. [Display(Name = "确认密码")]
    14. [Compare("Password", ErrorMessage = "密码和确认密码不匹配。")]
    15. public string ConfirmPassword { get; set; }
    16. }

这里的验证能配合HtmlHelper实现客户端校验. 
其次利用Identity的验证器,关键点在下面代码第一行,尝试登入,如果失败的话把result中的错误信息返回给前端,AddErrors方法添加的是模型级错误,通过@Html.ValidationSummary()能显示出来

    1. var result = await UserManager.CreateAsync(user, model.Password);
    2. if (result.Succeeded)
    3. {
    4. await SignInManager.SignInAsync(user, isPersistent:false, rememberBrowser:false);
    5. return RedirectToAction("Index", "Home");
    6. }
    7. AddErrors(result);
    8. ......
    9. private void AddErrors(IdentityResult result)
    10. {
    11. foreach (var error in result.Errors)
    12. {
    13. ModelState.AddModelError("", error);
    14. }
    15. }
用户名密码验证器

App_Start/ApplicationUserManager/Create

    1. // 配置用户名的验证逻辑
    2. manager.UserValidator = new UserValidator<ApplicationUser>(manager)
    3. {
    4. AllowOnlyAlphanumericUserNames = false,
    5. RequireUniqueEmail = true
    6. };
    7. // 配置密码的验证逻辑
    8. manager.PasswordValidator = new PasswordValidator
    9. {
    10. RequiredLength = 6,
    11. RequireNonLetterOrDigit = true,
    12. RequireDigit = true,
    13. RequireLowercase = true,
    14. RequireUppercase = true,
    15. };

PasswordValidator属性定义

名称描述
RequiredLength指定合法口令的最小长度
RequireNonLetterOrDigit当设置为true时,合法口令必须含有非字母和数字的字符
RequireDigit当设置为true时,合法口令必须含有数字
RequireLowercase当设置为true时,合法口令必须含有小写字母
RequireUppercase当设置为true时,合法口令必须含有大写字母

UserValidator属性定义

名称描述
AllowOnlyAlphanumericUserNames当为true时,用户名只能含有字母数字字符
RequireUniqueEmail当为true时,邮件地址必须唯一

配置验证器后就能在有UserManager的地方使用它UserManager.PasswordValidator.ValidateAsync
通常SignInAsync这些方法内部都会调用他们的.

自定义验证器

自定义用户验证器

    1. public class CustomUserValidator : UserValidator<AppUser> {
    2. public CustomUserValidator(AppUserManager mgr) : base(mgr) {
    3. }
    4. public override async Task<IdentityResult> ValidateAsync(AppUser user) {
    5. //使用内建验证策略
    6. IdentityResult result = await base.ValidateAsync(user);
    7. //在此基础上添加自己的验证策略
    8. if (!user.Email.ToLower().EndsWith("@example.com")) {
    9. var errors = result.Errors.ToList();
    10. errors.Add("Only example.com email addresses are allowed");
    11. result = new IdentityResult(errors);
    12. }
    13. return result;
    14. }
    15. }

自定义口令验证器

    1. public class CustomPasswordValidator : PasswordValidator {
    2. public override async Task<IdentityResult> ValidateAsync(string pass) {
    3. IdentityResult result = await base.ValidateAsync(pass);
    4. if (pass.Contains("12345")) {
    5. var errors = result.Errors.ToList();
    6. errors.Add("Passwords cannot contain numeric sequences");
    7. result = new IdentityResult(errors);
    8. }
    9. return result;
    10. }
    11. }



 

转载于:https://www.cnblogs.com/jetdl/p/8512595.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值