注意,Microsoft.AspNet.Identity.Core.1.0.0和Microsoft.AspNet.Identity.Core.2.2.1差别太大,需考虑实际项目中用的是哪种,本文是基于2.2.1来写的!!

两个概念:1、OWIN的英文全称是Open Web Interface for .NET。2、Microsoft.AspNet.Identity是微软在MVC 5.0中新引入的一种membership框架,和之前ASP.NET传统的membership以及WebPage所带来的SimpleMembership(在MVC 4中使用)都有所不同。本文以自定义数据库表来实现用户的登录授权。不依赖MemberShip和EF框架。

要实现自定义,有三处需重写:UserStore、PasswordHasher、IUser

自定义UserStore:

  继承微软的UserStore并重新实现它的FindByIdAsync、FindByNameAsync等方法。

  为什么要这么做?

  1、替换EF并换成我们自己的底层ORM(我在另一篇文章中用EF写了一套)

  2、多个Area可以自定义多个UserStore,不同的Area可以访问不同的UserStore,当然多个Area访问同一个UserStore也是可以。

  它是如何工作的?

    public class UserManager<TUser> : IDisposable where TUser: IUser
{
private ClaimsIdentityFactory<TUser> _claimsFactory;
private bool _disposed;
private IPasswordHasher _passwordHasher;
private IIdentityValidator<string> _passwordValidator;
private IIdentityValidator<TUser> _userValidator; public UserManager(IUserStore<TUser> store)
{
if (store == null)
{
throw new ArgumentNullException("store");
}
this.Store = store;
this.UserValidator = new UserValidator<TUser>((UserManager<TUser>) this);
this.PasswordValidator = new MinimumLengthValidator();
this.PasswordHasher = new Microsoft.AspNet.Identity.PasswordHasher();
this.ClaimsIdentityFactory = new ClaimsIdentityFactory<TUser>();
}

UserManager代码

  通过UserManager的源代码可以看到,UserManager运用了桥接模式,查找用户的方法来自UserStore,返回的是泛型

自定义IUserIdentity:

  1、需注意,此处可不是继承IUserIdentity,IUserIdentity需和IdentityDbContext配合,我们既然用自己的ORM,就等于放弃IdentityDbContext。

namespace Biz.Framework.AspNet.Identity
{
/// <summary>
/// IdentityLoginUser,重写Identity.IUser,这个跟数据库中的用户表是两码事,专门做登录、权限判断用的
/// 但是,我们可以把它跟数据库的用户表结合起来用,上面部分为数据的用户表,下面为继承成IUserIdentity(可做公司产品)
/// </summary>
[Serializable]
public class IdentityLoginUser : ISerializable, IUserIdentity, Microsoft.AspNet.Identity.IUser
{
#region 数据库字段
public int UserId { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
#endregion #region 公司自己的产品IUserIdentity接口
bool? IUserIdentity.IsRootAdmin
{
get { return null; }
}
#endregion #region 微软IUser接口
string Microsoft.AspNet.Identity.IUser<string>.Id
{
get { return null; }
}
#endregion #region 必须实现,否则无法使用
[SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)]
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("UserId", this.UserId);
info.AddValue("Password", string.Empty);
} /// <summary>
/// 反序列化的构造函数
/// </summary>
/// <param name="info"></param>
/// <param name="context"></param>
public IdentityLoginUser(SerializationInfo info, StreamingContext context)
{
this.UserId = info.GetInt32("UserId");
this.Password = info.GetString("Password");
}
#endregion public string ToJson() { return JsonConvert.SerializeObject(this); }
}
}

自定义IUserIdentity

  2、微软自带的User字段肯定是不符合实际环境的字段

自定义UserStore,整体代码如下

using Biz.DBUtility;//这个是自己的底层ORM,可以是EF的edmx或者其他
using Microsoft.AspNet.Identity;
using System;
using System.Data.Entity;
using System.Threading.Tasks; namespace Biz.Framework.AspNet.Identity
{
/// <summary>
/// 针对不同的Area,可以定义多个UserStore。
/// TODO:可以支持多种形式的用户读取吗?比如AD,接口验证等?待研究
/// </summary>
public class BizUserStore : IUserPasswordStore<IdentityLoginUser>, IUserStore<IdentityLoginUser>, IDisposable
{
public Task CreateAsync(IdentityLoginUser user)
{
throw new NotImplementedException("不支持新增用户");
} public Task DeleteAsync(IdentityLoginUser user)
{
throw new NotImplementedException("不支持删除用户");
} public Task<IdentityLoginUser> FindByIdAsync(int userId)
{
//BizEntities b = new BizEntities();
return null;//b.Sys_User.FindAsync(new object[] { userId });
} public Task<IdentityLoginUser> FindByNameAsync(string userName)
{
if (string.IsNullOrEmpty(userName))
return null; BizEntities dbContext = new BizEntities();
Sys_User user = dbContext.Sys_User.FirstOrDefaultAsync(q => q.UserName == userName).Result; IdentityLoginUser identityLoginUser = new IdentityLoginUser
{
UserId = user.UserId,
UserName = user.UserName,
Password = user.Password
}; return Task.FromResult<IdentityLoginUser>(identityLoginUser);
} public Task UpdateAsync(IdentityLoginUser user)
{
throw new NotImplementedException("不支持删除用户");
} public Task<IdentityLoginUser> FindByIdAsync(string userId)
{
if (string.IsNullOrEmpty(userId))
return null; return this.FindByIdAsync(int.Parse(userId));
} public void Dispose() { } #region IUserPasswordStore接口
/// <summary>
     /// 这个方法用不到!!!
/// 虽然对于普通系统的明文方式这些用不到。
/// 但是!!!MVC5之后的Identity2.0后数据库保存的密码是加密盐加密过的,这里也需要一并处理,然而1.0并不需要处理,需注意!!!
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
public Task<string> GetPasswordHashAsync(IdentityLoginUser user)
{
return Task.FromResult<string>(user.Password);
} public Task<bool> HasPasswordAsync(IdentityLoginUser user)
{
return Task.FromResult<bool>(!string.IsNullOrWhiteSpace(user.Password));
} public Task SetPasswordHashAsync(IdentityLoginUser user, string passwordHash)
{
user.Password = passwordHash;
return Task.FromResult<int>();
} #endregion
}
}

自定义UserStore

但是,此时运行代码会报错:Base-64 字符数组或字符串的长度无效。这个是由于微软的UserManager下的IPasswordHasher是通过MD5+随机盐加密出来的。所以,我们还要将密码加密,对比这一套换成我们自己的!你也可以自己去研究一下加密盐,我看微软的源代码是没看懂啦,我以为盐既然是随机生成的,应该是放在数据库里啊,但是数据库里没有,不懂!

自定义PasswordHasher,用自己的密码

using Microsoft.AspNet.Identity;

namespace Biz.Framework.AspNet.Identity
{
public class BizPasswordHasher : PasswordHasher
{
/// <summary>
/// 加密密码,可以采用简单的MD5加密,也可以通过MD5+随机盐加密
/// </summary>
/// <param name="password"></param>
/// <returns></returns>
public override string HashPassword(string password)
{
return password;
} public override PasswordVerificationResult VerifyHashedPassword(string hashedPassword, string providedPassword)
{
//此处什么加密都不做!
if (hashedPassword == providedPassword)
//if (Crypto.VerifyHashedPassword(hashedPassword, providedPassword))
{
return PasswordVerificationResult.Success;
}
return PasswordVerificationResult.Failed;
}
}
}

自定义PasswordHasher

namespace Biz.Web.Controllers
{
[Authorize]
public class AccountController : Controller
{
public AccountController()
: this(new UserManager<IdentityLoginUser>(new BizUserStore()))
{
} public AccountController(UserManager<IdentityLoginUser> userManager)
{
UserManager = userManager;
userManager.PasswordHasher = new BizPasswordHasher();
}

UserManager中的PasswordHasher也要一并重赋

但是,判断用户通过后,登录代码又报错:System.ArgumentNullException: 值不能为 null。重写ClaimsIdentityFactory后发现是User.Id为null,有次IdentityLogin中继承自IUser的Id必须赋值,可以用用户表中的UserId。

using Microsoft.AspNet.Identity;
using System;
using System.Collections.Generic;
using System.Security.Claims;
using System.Threading.Tasks; namespace Biz.Framework.AspNet.Identity
{
//public class BizClaimsIdentityFactory<TUser> : IClaimsIdentityFactory<TUser> where TUser : class, IUser
public class BizClaimsIdentityFactory<TUser> : ClaimsIdentityFactory<TUser, string> where TUser : class, IUser<string>
{
internal const string DefaultIdentityProviderClaimValue = "ASP.NET Identity";
internal const string IdentityProviderClaimType = "http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider"; public string RoleClaimType { get; set; } public string UserIdClaimType { get; set; } public string UserNameClaimType { get; set; } public BizClaimsIdentityFactory()
{
this.RoleClaimType = "http://schemas.microsoft.com/ws/2008/06/identity/claims/role";
this.UserIdClaimType = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier";
this.UserNameClaimType = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name";
} public async override Task<ClaimsIdentity> CreateAsync(UserManager<TUser, string> manager, TUser user, string authenticationType)
{
if (manager == null)
{
throw new ArgumentNullException("manager");
}
if (user == null)
{
throw new ArgumentNullException("user");
}
ClaimsIdentity id = new ClaimsIdentity(authenticationType, ((BizClaimsIdentityFactory<TUser>)this).UserNameClaimType, ((BizClaimsIdentityFactory<TUser>)this).RoleClaimType);
id.AddClaim(new Claim(((BizClaimsIdentityFactory<TUser>)this).UserIdClaimType, user.Id, "http://www.w3.org/2001/XMLSchema#string"));
id.AddClaim(new Claim(((BizClaimsIdentityFactory<TUser>)this).UserNameClaimType, user.UserName, "http://www.w3.org/2001/XMLSchema#string"));
id.AddClaim(new Claim("http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider", "ASP.NET Identity", "http://www.w3.org/2001/XMLSchema#string"));
if (manager.SupportsUserRole)
{
IList<string> roles = await manager.GetRolesAsync(user.Id);
using (IEnumerator<string> enumerator = roles.GetEnumerator())
{
while (enumerator.MoveNext())
{
string current = enumerator.Current;
id.AddClaim(new Claim(((BizClaimsIdentityFactory<TUser>)this).RoleClaimType, current, "http://www.w3.org/2001/XMLSchema#string"));
}
}
}
if (manager.SupportsUserClaim)
{
//ClaimsIdentity identity3;
//ClaimsIdentity identity2 = id;
//IList<Claim> claims = await manager.GetClaimsAsync(user.Id);
//identity3.AddClaims(claims);
}
return id;
} //public async override Task<ClaimsIdentity> CreateAsync(UserManager<TUser> manager, TUser user, string authenticationType)
//{
// if (manager == null)
// {
// throw new ArgumentNullException("manager");
// }
// if (user == null)
// {
// throw new ArgumentNullException("user");
// }
// ClaimsIdentity id = new ClaimsIdentity(authenticationType, ((BizClaimsIdentityFactory<TUser>)this).UserNameClaimType, ((BizClaimsIdentityFactory<TUser>)this).RoleClaimType);
// id.AddClaim(new Claim(((BizClaimsIdentityFactory<TUser>)this).UserIdClaimType, user.Id, "http://www.w3.org/2001/XMLSchema#string"));
// id.AddClaim(new Claim(((BizClaimsIdentityFactory<TUser>)this).UserNameClaimType, user.UserName, "http://www.w3.org/2001/XMLSchema#string"));
// id.AddClaim(new Claim("http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider", "ASP.NET Identity", "http://www.w3.org/2001/XMLSchema#string"));
// if (manager.SupportsUserRole)
// {
// IList<string> roles = await manager.GetRolesAsync(user.Id);
// using (IEnumerator<string> enumerator = roles.GetEnumerator())
// {
// while (enumerator.MoveNext())
// {
// string current = enumerator.Current;
// id.AddClaim(new Claim(((BizClaimsIdentityFactory<TUser>)this).RoleClaimType, current, "http://www.w3.org/2001/XMLSchema#string"));
// }
// }
// }
// if (manager.SupportsUserClaim)
// {
// //ClaimsIdentity identity3;
// //ClaimsIdentity identity2 = id;
// //IList<Claim> claims = await manager.GetClaimsAsync(user.Id);
// //identity3.AddClaims(claims);
// }
// return id;
//}
}
}

重写ClaimsIdentityFactory,注意可以不重写,此处是为了验证问题

自此,本文结束!需要代码的可留言

其他的一些可借鉴的文章:

重写UserManager

加密盐

自定义表并实现Identity登录(一)的更多相关文章

  1. SpringBoot集成Spring Security(4)——自定义表单登录

    通过前面三篇文章,你应该大致了解了 Spring Security 的流程.你应该发现了,真正的 login 请求是由 Spring Security 帮我们处理的,那么我们如何实现自定义表单登录呢, ...

  2. SpringSecurity 自定义表单登录

    SpringSecurity 自定义表单登录 本篇主要讲解 在SpringSecurity中 如何 自定义表单登录 , SpringSecurity默认提供了一个表单登录,但是实际项目里肯定无法使用的 ...

  3. spring security 之自定义表单登录源码跟踪

    ​ 上一节我们跟踪了security的默认登录页的源码,可以参考这里:https://www.cnblogs.com/process-h/p/15522267.html 这节我们来看看如何自定义单表认 ...

  4. Spring Security教程(三):自定义表结构

    在上一篇博客中讲解了用Spring Security自带的默认数据库存储用户和权限的数据,但是Spring Security默认提供的表结构太过简单了,其实就算默认提供的表结构很复杂,也不一定能满足项 ...

  5. MVC5 - ASP.NET Identity登录原理 - Claims-based认证和OWIN

    在Membership系列的最后一篇引入了ASP.NET Identity,看到大家对它还是挺感兴趣的,于是来一篇详解登录原理的文章.本文会涉及到Claims-based(基于声明)的认证,我们会详细 ...

  6. [转]MVC5 - ASP.NET Identity登录原理 - Claims-based认证和OWIN

    本文转自:http://www.cnblogs.com/jesse2013/p/aspnet-identity-claims-based-authentication-and-owin.html 在M ...

  7. 【.net+jquery】绘制自定义表单(含源码)

    前言 两年前在力控的时候就想做一个类似的功能,当时思路大家都讨论好了,诸多原因最终还是夭折了.没想到两年多后再这有重新提出要写一个绘制表单的功能.对此也是有点小激动呢?总共用时8.5天的时间基本功能也 ...

  8. MVC5 - ASP.NET Identity登录原理 - Claims-based认证和OWIN -摘自网络

    在Membership系列的最后一篇引入了ASP.NET Identity,看到大家对它还是挺感兴趣的,于是来一篇详解登录原理的文章.本文会涉及到Claims-based(基于声明)的认证,我们会详细 ...

  9. (转)ASP.NET Identity登录原理 - Claims-based认证和OWIN

    在Membership系列的最后一篇引入了ASP.NET  Identity,看到大家对它还是挺感兴趣的,于是来一篇详解登录原理的文章.本文会涉及到Claims-based(基于声明)的认证,我们会详 ...

随机推荐

  1. 省常中模拟 day2

    第一题: 题目大意: 有mn颗糖,要装进k个盒子里,使得既可以平均分给n个人,也可以平均分给m个人. 求k的最小值. 解题过程: 1.先看一组小数据(13,21).那么根据贪心的原则很容易想到先拿13 ...

  2. mysql 中execute、executeQuery和executeUpdate之间的区别

    在用纯JSP做一个页面报警功能的时候习惯性的用executeQuery来执行SQL语句,结果执行update时就遇到问题,语句能执行,但返回结果出现问题,另外还忽略了executeUpdate的返回值 ...

  3. Python开发入门与实战6-表单

    6. 表单 从简朴的单个搜索框,到常见的Blog评论提交表单,再到复杂的自定义数据输入接口,HTML表单一直是交互性网站的重要交互手段.本章介绍如何用Django如何对用户通过表单提交的数据进行访问. ...

  4. Jmeter—7 测试中使用到的定时器和逻辑控制器

    1 测试中提交数据有延时1min,所以查询数据是否提交成功要设置定时器. 固定定时器页面:单位是毫秒 [dinghanhua] 2 集合点.Synchronizing Timer 集合点编辑:集合用户 ...

  5. 探索javascript----获得节点计算后样式

    节点计算后样式是一个属性与属性值的值对对象: IE:    node.currentStyle; 非IE: window.getComputedStyle(node,null); 兼容方式: func ...

  6. HDU 4822----其实不会这个题

    题目:http://acm.hdu.edu.cn/showproblem.php?pid=4822 并不会做这个题,题解说是LCA(最近公共祖先),并不懂,说一下我自己的思路吧,虽然没能实现出来. 题 ...

  7. VC++ 文件系统

    using namespace System; using namespace System::IO; void ShowHelpMsg(){ Console::WriteLine(L"本程 ...

  8. 创建一个maven web project

    几经周折总算是找到了和高杨学长一样的web  project的方法.感谢学长的一语点醒.我之前以为,既是maven又是web project的项目得要是通过dynamic web project转换到 ...

  9. [转]SQLServer2008日志文件无法收缩处理方法

    问题描述     发现有的数据库日志文件太大,无论如何收缩执行几次SQL语句都不行.事务日志达30+G,而且使用常规的截断.收缩方法均无法减小日志物理文件的尺寸,经过一番寻找,终于找到了解决方法. 查 ...

  10. 用JS编写日历的简单思路

    提要:本文以写当前时间环境下当月的日历表为例,用最简单的方法实现JavaScript日历,旨在展示JS世界中实用为本.简单为上的解决问题的思路. Web页中的日历一般离不开表格,通常都使用表格装载指定 ...