在普通的MVC项目中 我们普遍的使用Cookie来作为认证授权方式,使用简单。登录成功后将用户信息写入Cookie;但当我们做WebApi的时候显然Cookie这种方式就有点不适用了。

在.NET Core 中 WebApi中目前比较流行的认证授权方式是Jwt  (Json Web Token) 技术。Jwt 是一种无状态的分布式身份验证方式,Jwt 是将用户登录信息加密后存放到返回的Token中 ,相当于用户信息是存储在客户端。Jwt的加密方式有两种 :对称加密与非对称加密,非对称加密即 RSA 加密的方式。

自己手写认证授权代码和Jwt的思路是一样的;不同之处在于:

1、加密方式仅仅是采用的对称加密方式 简单高效。哈哈!(弊端就是没有非对称加密更安全);

2、用户登录信息主要保存在Redis中,即服务端。

自己写的好处:

1、扩展性强,可根据自己的需要进行各种扩展,比如在验证授权时可很方便的添加多设备登录挤下线功能等。

2、可随时调整用户的Token失效时间。

认证及授权流程

1、先请求登录接口,登录成功,为用户产生一个Token,

登录获取Token 图片中ticket字段。

2、 客户端拿到Token在其他请求中将Token信息添加到请求头中传递到服务端。

开发思路

1、添加一个过滤器。在Startup 中ConfigureServices方法里添加一个Filters 即我们自己授权代码类。

public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddMvc(mvc =>
{
//添加自己的授权验证
mvc.Filters.Add(typeof(AuthorizeFilter));
});
}

具体详细代码,请看文章结尾github地址。

  

添加过滤器之后我们的每次请求都会优先执行过滤器的代码。在这里我们就可以判断用户是否已经登录,从而进行拦截没有授权的的请求。

    /// <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(" ")[];
//校验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"); }
}

具体详细代码,请看文章结尾github地址。

2、登录并获取Token

由于添加了IAuthorizationFilter类型的过滤器,所以每个请求都会被拦截。所以登录接口我们需要允许匿名访问。

3、加解密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加随机key:FEZaaWbyimaWiJHah

所以我们返回给用户的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();
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 - );
var base64 = encryptStr.Substring(, encryptStr.Length - );
var deBase64 = Encryption.Base64Decrypt(base64);
var keys = key + randomKey;
string ticketInfo = Encryption.DesDecrypt(deBase64, keys);
var guid = ticketInfo.Split("&")[];
client = ticketInfo.Split("&")[];
string redisKey = "ticket_" + guid;
var obj = redisHelper.Get<UserInfo>(redisKey);
return obj;
}
catch (Exception ex)
{
throw ex;
}
}
}

完整demo代码请看github

https://github.com/cfan1236/NetCoreAuthorize

手写DotNet Core 认证授权代码的更多相关文章

  1. SpringSecurity(1)---认证+授权代码实现

    认证+授权代码实现 Spring Security是 一种基于 Spring AOP 和 Servlet 过滤器的安全框架.它提供全面的安全性解决方案,同时在 Web 请求级和方法调用级处理身份确认和 ...

  2. 4.2tensorflow多层感知器MLP识别手写数字最易懂实例代码

    自己开发了一个股票智能分析软件,功能很强大,需要的点击下面的链接获取: https://www.cnblogs.com/bclshuai/p/11380657.html 1.1  多层感知器MLP(m ...

  3. 手写笔记变PDF-几行代码变命令行程序为图形化界面

    前言 最近发现了一个非常不错的Python类库----Gooey, https://github.com/chriskiehl/Gooey 在它的帮助下我们可以非常方便的将一个命令行程序升级成一个图形 ...

  4. 手写数字0-9的识别代码(SVM支持向量机)

    帮一个贴吧的朋友改的一段代码,源代码来自<机器学习实战> 原代码的功能是识别0和9两个数字 经过改动之后可以识别0~9,并且将分类器的产生和测试部分分开来写,免得每次测试数据都要重新生成分 ...

  5. .net core 认证与授权(三)

    前言 在写三上是在一的基础上写的,所以有没有看过二是没得关系的,在一中介绍了认证与授权,但是没有去介绍拿到证书后怎样去验证授权. 概念性东西:在这套机制中,把这个权限认证呢,称作为policy.这个p ...

  6. 手写RPC框架指北另送贴心注释代码一套

    Angular8正式发布了,Java13再过几个月也要发布了,技术迭代这么快,框架的复杂度越来越大,但是原理是基本不变的.所以沉下心看清代码本质很重要,这次给大家带来的是手写RPC框架. 完整代码以及 ...

  7. 常见的JS手写函数汇总(代码注释、持续更新)

    最近在复习面试中常见的JS手写函数,顺便进行代码注释和总结,方便自己回顾也加深记,内容也会陆陆续续进行补充和改善. 一.手写深拷贝 <script> const obj1 = { name ...

  8. Spring security OAuth2.0认证授权学习第三天(认证流程)

    本来之前打算把第三天写基于Session认证授权的,但是后来视屏看完后感觉意义不大,而且内容简单,就不单独写成文章了; 简单说一下吧,就是通过Servlet的SessionApi 通过实现拦截器的前置 ...

  9. springcloud-alibaba手写负载均衡的坑,采用restTemplate,不能添加@loadbalanced注解,否则采用了robbin

    采用springcloud-alibaba整合rabbion使用DiscoveryClient调用restful时遇到的一个问题,报错如下: D:\javaDevlepTool\java1.8\jdk ...

随机推荐

  1. python笔记:#004#注释

    注释 目标 注释的作用 单行注释(行注释) 多行注释(块注释) 01. 注释的作用 使用用自己熟悉的语言,在程序中对某些代码进行标注说明,增强程序的可读性 02. 单行注释(行注释) 以 # 开头,# ...

  2. JAVA之单源最短路径(Single Source Shortest Path,SSSP问题)dijkstra算法求解

    题目简介:给定一个带权有向图,再给定图中一个顶点(源点),求该点到其他所有点的最短距离,称为单源最短路径问题. 如下图,求点1到其他各点的最短距离 准备工作:以下为该题所需要用到的数据 int N; ...

  3. Spring+Redis的部署与Redis缓存使用示例

    由于项目的业务需要,这两天折腾了一下Spring-redis配置,有了前面用Spring托管hibernate的经验,这次可以说是顺风顺水,大概说一下流程. ubuntu 安装 redis sudo ...

  4. (function($){...})(jQuery) 含义 【转】

    经常用,今天总结一下,下文摘自某网友的总结: (function($){...})(jQuery)实际上是匿名函数,不懂得朋友可以继续往下看. 这里实际上是匿名函数 function(arg){... ...

  5. THINKPHP 调试------输出sql语句

    echo $model->getLastSql();//$model为实例化的模板类

  6. Nginx+uwsgi部署django

    0. 登录远程服务器并准备 ssh 用户@IP -p 端口 回车后,要求输入服务器密码,再输入密码 更新软件源 sudo apt-get update sudo apt-get upgrade 1. ...

  7. 洛谷 P1057 解题报告

    P1057 传球游戏 题目描述 上体育课的时候,小蛮的老师经常带着同学们一起做游戏.这次,老师带着同学们一起做传球游戏. 游戏规则是这样的:n个同学站成一个圆圈,其中的一个同学手里拿着一个球,当老师吹 ...

  8. python笔试题(1)

            为了充实自己,小编决定上传自己见到的笔试题和面试题.可能要写好长时间,一时半会写不了多少,只能说遇到多少写多少吧,但是只要小编有时间,会持续上传(但是答案却不能保证,所以有看到错误的及 ...

  9. Linux修改本机/etc/hosts的hostName

    1.Linux修改本机别名/etc/hosts的hostName后经常不生效解决 Linux修改本机别名/etc/hosts的hostName后经常不生效, 比如我们/etc/hosts的内容如下: ...

  10. new/delete和malloc/free的比较

    从C++角度上说,使用new分配堆空间可以调用类的构造函数,而malloc()函数仅仅是一个函数调用,它不会调用构造函数,它所接受的参数是一个unsigned long类型.同样,delete在释放堆 ...