在普通的MVC项目中 咱们广泛的使用Cookie来做为认证受权方式,使用简单。登陆成功后将用户信息写入Cookie;但当咱们作WebApi的时候显然Cookie这种方式就有点不适用了。ios
在dotnet core 中 WebApi中目前比较流行的认证受权方式是Jwt (Json Web Token) 技术。Jwt 是一种无状态的分布式身份验证方式,Jwt 是将用户登陆信息加密后存放到返回的Token中 ,至关于用户信息是存储在客户端。Jwt的加密方式有两种 :对称加密与非对称加密,非对称加密即 RSA 加密的方式。
git
本身手写认证受权代码和Jwt的思路是同样的;不一样之处在于:github
一、加密方式仅仅是采用的对称加密方式 简单高效。哈哈!(弊端就是没有非对称加密更安全);redis
二、用户登陆信息主要保存在Redis中,即服务端。安全
本身写的好处:mvc
一、扩展性强,可根据本身的须要进行各类扩展,好比在验证受权时可很方便的添加多设备登陆挤下线功能等。dom
二、可随时调整用户的Token失效时间。分布式
认证及受权流程post
一、先请求登陆接口,登陆成功,为用户产生一个Token,ui
登陆获取Token 图片中ticket字段。
二、 客户端拿到Token在其余请求中将Token信息添加到请求头中传递到服务端。
开发思路
一、添加一个过滤器。在Startup 中ConfigureServices方法里添加一个Filters 即咱们本身受权代码类。
public
void
ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddMvc(mvc =>
{
//添加本身的受权验证
mvc.Filters.Add(
typeof
(AuthorizeFilter));
});
}
|
添加过滤器以后咱们的每次请求都会优先执行过滤器的代码。在这里咱们就能够判断用户是否已经登陆,从而进行拦截没有受权的的请求。
/// <summary> /// 安全认证过滤器 /// </summary> public class AuthorizeFilter : IActionFilter, IAuthorizationFilter { public void OnAuthorization(AuthorizationFilterContext context) { //容许匿名访问 if (context.HttpContext.User.Identity.IsAuthenticated || context.Filters.Any(item => item is IAllowAnonymousFilter)) return; var httpContext = context.HttpContext; var claimsIdentity = httpContext.User.Identity as ClaimsIdentity; var request = context.HttpContext.Request; var authorization = request.Headers["Authorization"].ToString(); if (authorization != null && authorization.Contains("BasicAuth")) { //获取请求头中传递的ticket var current_ticket = authorization.Split(" ")[1]; //校验ticket并获取用户信息 var userInfo = TicketEncryption.VerifyTicket(current_ticket, out string dec_client); if (userInfo != null) { //同一个终端屡次登陆挤下线功能 返回403 if (userInfo.ticket != current_ticket && userInfo.client.ToString() == dec_client) { #region 多设备挤下线代码 var response = new HttpResponseMessage(); context.HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; context.Result = new JsonResult("Forbidden:The current authorization has expired"); #endregion return; } else { return; } } } // 401 未受权 context.HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized; context.Result = new JsonResult("Forbidden:Tiket Invalid"); } }
二、登陆并获取Token
因为添加了IAuthorizationFilter类型的过滤器,因此每一个请求都会被拦截。因此登陆接口咱们须要容许匿名访问。
三、加解密Token
加密:登陆成功后就要产生个Token了,产生也简单。将用户的惟一信息好比uid或者guid进行对称式加密。固然若是须要对登陆设备作区分或者多设备登陆挤下线功能时最好也将登陆设备一块儿加密进去。
咱们都知道 在加密中通常状况下只要加密的数据及加密key不变;那么加密后的内容也会一直保持不变。若是咱们每次登陆产生的Token一直没有任何变化只要这个Token被泄露了那将很危险的。居然咱们但愿每次登陆产生的Token都有变化。那就要改变加密数据或者加密key了。加密数据是用户惟一信息这个显然不可能产生变化。因此咱们能改变的地方只能是加密key了;咱们采用固定key+随机key的方式。
由于加密key在咱们解密时也是须要一一对应的。因此咱们得想办法将咱们的随机key告诉咱们解密的代码中。办法就是 咱们将加密后的内容(通常状况进行base64编码)再加上随机key。(随机key必定是固定长度 否则后面没法解析拆分)
好比加密内容是guid=73e01eab-210d-4d19-a72a-d0d64e053ec0+client=ios 固定key=123654+随机key=FEZaaWbyimaWiJHah
即加密过程:
加密(73e01eab-210d-4d19-a72a-d0d64e053ec0&ios,123654FEZaaWbyimaWiJHah)=M0EzM0ZGRjk2QzgwRDY2RDJDMTdFOEJGRUE0NDI3NEE1RDlFNkU4NDQ0MERFNEIyMkQ5QjM4MjAxODcwj加随机keyFEZaaWbyimaWiJHah
因此咱们返回给用户的Token其实是包含了随机key的。固然这个随机key只有咱们本身知道。由于随机key的长度以及位置只有咱们本身知道。这种方式即便咱们固定key被泄露了 只要别人不知道咱们随机key处理方法也无济于事。
解密:知道加密过程后就好解密了。拿到用户提交的Token后首先按照随机key的固定位置进行截取。将加密内容和随机key拆开。而后将固定key和随机key组合一块儿解密加密的内容,取得用户guid和登陆的客户端类型。
完整加解密代码
代码中的ticket表明本文中的Token。代码中使用的是DES加解密
public class TicketEncryption { //加密key 实际中请用配置文件配置 private static readonly string key = "yvDlky7GXGtlPCGr"; /// <summary> /// 获取一个新的ticket /// </summary> /// <param name="guid">用户的guid</param> /// <param name="client">客户端</param> /// <returns></returns> public static string GenerateTicket(string guid, string client) { //随机key string randomKey = Randoms.GetRandomString(15); var keys = key + randomKey; var desStr = Encryption.DesEncrypt(guid + "&" + client, keys); var base64Str = Encryption.Base64Encrypt(desStr) + randomKey; return base64Str; } /// <summary> /// 校验ticket /// </summary> /// <param name="encryptStr"></param> /// <returns></returns> public static UserInfo VerifyTicket(string encryptStr,out string client) { try { RedisHelper redisHelper = new RedisHelper("127.0.0.1:6379"); //加密原型:guid&client; 如:08e80f78-95ad-427c-b506-a5f1504e29ac&ios string randomKey = encryptStr.Substring(encryptStr.Length - 15); var base64 = encryptStr.Substring(0, encryptStr.Length - 15); var deBase64 = Encryption.Base64Decrypt(base64); var keys = key + randomKey; string ticketInfo = Encryption.DesDecrypt(deBase64, keys); var guid = ticketInfo.Split("&")[0]; client = ticketInfo.Split("&")[1]; string redisKey = "ticket_" + guid; var obj = redisHelper.Get<UserInfo>(redisKey); return obj; } catch (Exception ex) { throw ex; } } }
完整demo代码请看github