WebAPi使用公钥私钥加密介绍和使用

随着各种设备的兴起,WebApi作为服务也越来越流行。而在无任何保护措施的情况下接口完全暴露在外面,将导致被恶意请求。最近项目的项目中由于提供给APP的接口未对接口进行时间防范导致短信接口被怒对造成一定的损失,临时的措施导致PC和app的防止措施不一样导致后来前端调用相当痛苦,选型过oauth,https,当然都被上级未通过,那就只能自己写了,就很,,ԾㅂԾ,,。下面就此次的方式做一次记录。最终的效果:传输过程中都是密文,别人拿到请求串不能更改请求参数,通过接口过期时间防止同一请求串一直被调用。

第一步重写MessageProcessingHandler中的ProcessRequest和ProcessResponse


无论是APi还是Mvc请求管道都提供了我们很好的去扩展,本次说的是api,其实mvc大概意思也是差不多的。我们现在主要写出大致流程

从图中可以看出我们需要在MessageProcessingHandlder上做处理。我们继承MessageProcessingHandlder重写ProcessRequest和ProcessResponse方法,从方法名可以看出一个是针对请求值处理,一个是针对返回值处理代码如下:

  public class CustomerMessageProcesssingHandler : MessageProcessingHandler
{
protected override HttpRequestMessage ProcessRequest(HttpRequestMessage request, CancellationToken cancellationToken)
{
var contentType = request.Content.Headers.ContentType; if (!request.Headers.Contains("platformtype"))
{
return request;
}
//根据平台编号获得对应私钥
string privateKey = Encoding.UTF8.GetString(Convert.FromBase64String(ConfigurationManager.AppSettings["PlatformPrivateKey_" + request.Headers.GetValues("platformtype").FirstOrDefault()]));
if (request.Method == HttpMethod.Post)
{
// 读取请求body中的数据
string baseContent = request.Content.ReadAsStringAsync().Result;
// 获取加密的信息
// 兼容 body: 加密数据 和 body: sign=加密数据
baseContent = Regex.Match(baseContent, "(sign=)*(?<sign>[\\S]+)").Groups[].Value;
// 用加密对象解密数据
baseContent = CommonHelper.RSADecrypt(privateKey, baseContent);
// 将解密后的BODY数据 重置
request.Content = new StringContent(baseContent);
//此contentType必须最后设置 否则会变成默认值
request.Content.Headers.ContentType = contentType;
}
if (request.Method == HttpMethod.Get)
{
string baseQuery = request.RequestUri.Query;
// 读取请求 url query数据
baseQuery = baseQuery.Substring();
baseQuery = Regex.Match(baseQuery, "(sign=)*(?<sign>[\\S]+)").Groups[].Value;
baseQuery = CommonHelper.RSADecrypt(privateKey, baseQuery);
// 将解密后的 URL 重置URL请求
request.RequestUri = new Uri($"{request.RequestUri.AbsoluteUri.Split('?')[0]}?{baseQuery}");
}
return request;
}
protected override HttpResponseMessage ProcessResponse(HttpResponseMessage response, CancellationToken cancellationToken)
{
return response;
}
}
上面的代码大部分已经有注释了,但这里说明三点第一:platformtype用来针对不同的平台设置不同的公钥和私钥;第二:在post方法中ContentType一定要最后设置,否则会成为默认值,这个问题会导致webapi不能进行正确的参数绑定;第三:有人可能会问这里ProcessResponse是不是可以不用重写?答案是必须重写如果不想对结果操作直接返回就如上当然你也可以在此对返回值进行加密,但是个人认为意义不大,看具体情况因为大部分数据加密后前端还是需要解密然后展示所以此处不做任何处理。在这一步我们已经对前端请求的加密串在handler中处理成明文重新赋值给HttpRequestMessage。
 

第二步重写AuthorizeAttribute中OnAuthorization和HandleUnauthorizedRequest


上图是Api中Filter的请求顺序,我们在第一个Filter上处理,代码如下:
    public class CustomRequestAuthorizeAttribute : AuthorizeAttribute
{ public override void OnAuthorization(HttpActionContext actionContext)
{
//action具有[AllowAnonymous]特性不参与验证
if (actionContext.ActionDescriptor.GetCustomAttributes<AllowAnonymousAttribute>().OfType<AllowAnonymousAttribute>().Any(x => x is AllowAnonymousAttribute))
{
base.OnAuthorization(actionContext);
return;
}
var request = actionContext.Request;
string method = request.Method.Method, timeStamp = string.Empty, expireyTime = ConfigurationManager.AppSettings["UrlExpireTime"], timeSign = string.Empty, platformType = string.Empty;
if (!request.Headers.Contains("timesign") || !request.Headers.Contains("platformtype") || !request.Headers.Contains("timestamp") || !request.Headers.Contains("expiretime"))
{
HandleUnauthorizedRequest(actionContext);
return;
}
platformType = request.Headers.GetValues("platformtype").FirstOrDefault();
timeSign = request.Headers.GetValues("timesign").FirstOrDefault();
timeStamp = request.Headers.GetValues("timestamp").FirstOrDefault();
var tempExpireyTime = request.Headers.GetValues("expiretime").FirstOrDefault();
string privateKey = Encoding.UTF8.GetString(Convert.FromBase64String(ConfigurationManager.AppSettings[$"PlatformPrivateKey_{platformType}"]));
if (!SignValidate(tempExpireyTime, privateKey, timeStamp, timeSign))
{
HandleUnauthorizedRequest(actionContext);
return;
}
if (tempExpireyTime != "")
{
expireyTime = tempExpireyTime;
}
//判断timespan是否有效
double ts2 = ConvertHelper.ToDouble((DateTime.UtcNow - new DateTime(, , , , , , )).TotalMilliseconds, ), ts = ts2 - ConvertHelper.ToDouble(timeStamp);
bool falg = ts > int.Parse(expireyTime) * ;
if (falg)
{
HandleUnauthorizedRequest(actionContext);
return;
}
base.IsAuthorized(actionContext);
}
protected override void HandleUnauthorizedRequest(HttpActionContext filterContext)
{
base.HandleUnauthorizedRequest(filterContext); var response = filterContext.Response = filterContext.Response ?? new HttpResponseMessage();
response.StatusCode = HttpStatusCode.Forbidden;
var content = new
{
BusinessStatus = -,
StatusMessage = "服务端拒绝访问"
};
response.Content = new StringContent(JsonConvert.SerializeObject(content), Encoding.UTF8, "application/json");
}
private bool SignValidate(string expiryTime, string privateKey, string timestamp, string sign)
{
bool isValidate = false;
var tempSign = CommonHelper.RSADecrypt(privateKey, sign);
if (CommonHelper.EncryptSHA256($"expiretime{expiryTime}" + $"timestamp{timestamp}") == tempSign)
{
isValidate = true;
}
return isValidate;
}
}

请求头部增加参数expiretime使用此参数作为本次接口的过期时间如果没有则表示使用平台默认的接口时间,是我们可以针对不同的接口设置不同的过期时间;timestamp请求时间戳来防止别人拿到接口后一直调用timesign是过期时间和时间戳通过hash然后在通过公钥加密的串来防止别人修改前两个参数。重写HandleUnauthorizedRequest来设置返回内容。


至此整个验证过程就结束了,我们在使用过程中可以建立BaseApi将特性标记上让其他APi继承,当然我们的接口中可能有的action不需要验证看OnAuthorization第一行代码 增加相应的特性跳过此验证。在整个过程中其实我们已经使用了两种加密方式。一是本文中的CustomerMessageProcesssingHandler;另外一种就是timestamp+QueryString然后hash 在公钥加密 这样就不需要CustomerMessageProcesssingHandler其实就是本文中的头部加密方式。

补充:园友建议增加请求端实例,确实是昨天有所遗漏。趁不忙补充上:

本次以HttpClient调用方式为例,展示Get,Post请求加密到执行的相应的action的过程;首先看一下Get请求如下:

可以看到我们的请求串url已经是密文,头部时间sign也是密文,除非别人拿到我们的私钥不然是不能修改其参数的。然后请求到达我们的CustomerMessageProcesssingHandler中我们看下Get中得到的参数是:

这是我们得到的前端传过来的querystring的参数他的值就是我们前端加密后传过来的下一步我们解密应该要得到未加密之前的参数也就是客户端中id=1同时重新给requesturi赋值;

结果中我们可以看到id=1已被正确解密得到。接下来进入我们的CustomRequestAuthorizeAttribute

在这一步我们进行对timeSign的解密对请求只进行hash对比然后验证时间戳是否在过期时间内最终我们到达相应的action:

这样整个请求也就完成了Post跟Get区别不大重要的在于拿到传递参数的地方不一样这里我只贴一下调用的代码过程同上:

   public static void PostTestByModel() {

             HttpClient http = new HttpClient();
var timestamp = (DateTime.UtcNow - new DateTime(, , , , , , )).TotalMilliseconds;
var expiretime = "";
var timesign = RSAEncrypt(publicKey, EncryptSHA256($"expiretime{expiretime}timestamp{timestamp}"));
var codeValue = RSAEncrypt(publicKey, JsonConvert.SerializeObject(new Tenmp { Id = , Name = "cl" }));
http.DefaultRequestHeaders.Add("platformtype", "Web");
http.DefaultRequestHeaders.Add("timesign", $"{timesign}");
http.DefaultRequestHeaders.Add("timestamp", $"{string.Format("{:N2}", timestamp.ToString()) }");
http.DefaultRequestHeaders.Add("expiretime", expiretime);
var url1 = string.Format($"{host}api/Values/PostTestByModel");
HttpContent content = new StringContent(codeValue);
MediaTypeHeaderValue typeHeader = new MediaTypeHeaderValue("application/json");
typeHeader.CharSet = "UTF-8";
content.Headers.ContentType = typeHeader;
var response1 = http.PostAsync(url1, content).Result;
}

最后当验证不通过得到的返回值:

这也就是重写HandleUnauthorizedRequest的目的 当然你也可以不重写此方法那么返回的就是401 英文的未通过验证。

WebAPi接口安全之公钥私钥加密的更多相关文章

  1. 使用公钥私钥加密实现单点登录(SSO)

    Oauth2+Gateway+springcloud+springcloud-alibaba-nacos+jwt ,使用公钥私钥加密实现单点登录(OSS) github地址点这里 注意事项 GET: ...

  2. 支付宝网站支付接口配置 RSA 公钥 私钥

    个人博客 地址:http://www.wenhaofan.com/article/20190419143333 下载签名工具 访问:https://docs.open.alipay.com/291/1 ...

  3. 【Azure API 管理】APIM 配置Validate-JWT策略,验证RS256非对称(公钥/私钥)加密的Token

    问题描述 在APIM中配置对传入的Token进行预验证,确保传入后端被保护的API的Authorization信息正确有效,可以使用validate-jwt策略.validate-jwt 策略强制要求 ...

  4. WebApi接口安全性 接口权限调用、参数防篡改防止恶意调用

    背景介绍 最近使用WebApi开发一套对外接口,主要是数据的外送以及结果回传,接口没什么难度,采用WebApi+EF的架构简单创建一个模板工程,使用template生成一套WebApi接口,去掉put ...

  5. RSA不对称加密和公钥 私钥

    理论上只要有加密的规则 基本都是可以解密的 但是如果解密需要消耗的时间过长 比如1000年 解密过后已经没什么意义了 此时可认为这种算法不能被破解 也就是说此加密可信 MD5 是一种单向操作 加密后不 ...

  6. 关于URLEnCode,URLDeCode,Base64,公钥私钥

    1.Base64非常适合http.mime协议,所以在一些类似webservice中可以用Base64. 用法如下:传出去之前先 Convert.ToBase64String(encryptedByt ...

  7. C# 与JAVA 的RSA 加密解密交互,互通,C#使用BouncyCastle来实现私钥加密,公钥解密的方法

    因为C#的RSA加密解密只有公钥加密,私钥解密,没有私钥加密,公钥解密.在网上查了很久也没有很好的实现.BouncyCastle的文档少之又少.很多人可能会说,C#也是可以的,通过Biginteger ...

  8. 基于私钥加密公钥解密的RSA算法C#实现

    RSA算法是第一个能同时用于加密和数字签名的算法,也易于理解和操作. RSA是被研究得最广泛的公钥算法,从提出到现在已近二十年,经历了各种攻击的考验,逐渐为人们接受,普遍认为是目前最优秀的公钥方案之一 ...

  9. 对加密方式(公钥私钥)的形象理解(以http和https为例)

    https其实就是建构在SSL/TLS之上的 http协议,所以要比较https比http多用多少服务器资源,主要看SSL/TLS本身消耗多少服务器资源. http使用TCP 三次握手建立连接,客户端 ...

随机推荐

  1. centos生成公钥私钥 securecrt通过公钥访问服务器 winscp通过公钥访问服务器

    忙碌了一下午,一直到写博客现在.都在纠结阿里云服务器上配置公钥私钥,网上的说辞总是参差不齐,需要各个去综合,合理取舍.今天终于配置好了. 我就不说这种方式的重要性了,往往黑客都不需要你的登陆账户密码就 ...

  2. 微信小程序的开发环境搭建(Windows版本)

    前言: 小程序是指微信公众平台小程序,小程序可以帮助开发者快速的开发小程序,小程序可以在微信内被便捷地获取和传播:是一种不需要下载安装即可使用的应用小程序,和原有的三种公众号是并行的体系.2017年1 ...

  3. npm 的用法

    当用npm 安装依赖时如果加上  --save  就会自动把依赖模块添加到package.json中  别人下载时直接npm install 加载后就可以了

  4. JavaScript 语言中的 this

    JavaScript 语言中的 this 由于其运行期绑定的特性,JavaScript 中的 this 含义要丰富得多,它可以是全局对象.当前对象或者任意对象,这完全取决于函数的调用方式.JavaSc ...

  5. FZU 1015 土地划分

        Description 在Dukeswood这块土地上生活着一个富有的农庄主和他的几个孩子.在他临终时,他想把他的土地分给他的孩子.他有许多农场,每个农场都是一块矩形土地.他在农场地图上划上一 ...

  6. vue+webpack项目实际工作中需要生成一个配置文件供生产环境使用

    大家都知道webpack打包十分方便,但是在工作中,前端写好的项目需要后端进行部署,就需要有一个配置文件. 使用插件 :  GenerateAssetPlugin , 使用方法 : 1  在项目中安装 ...

  7. AndroidDemo - FloatWindowDemo

    安卓悬浮窗Demo 在桌面上创建一个小的悬浮窗.点击小悬浮窗后会弹出一个大的窗口.大窗口上有2个按键,分别为返回与关闭.点击大窗口外的部分能返回小窗口. 小窗口可以自由拖动.小窗口上显示的是当前内存使 ...

  8. sql操作一般函数

    sql操作一般函数 函数一般语法:SELECT function(列) FROM 表 函数的基本类型是: Aggregate 合计函数:函数的操作面向一系列的值,并返回一个单一的值. Scalar 函 ...

  9. 2017年7月Web服务器调查报告

    在2017年7月的调查中,我们收到了来自1,767,964,429个网站和6,593,508个面向web的计算机的反馈.这是一个小小的进步,网站的数量增加了100万个(+0.06%),面向web的计算 ...

  10. 【Django】request 处理流程(转)

    Django 和其他 Web 框架的 HTTP 处理的流程大致相同,Django 处理一个 Request 的过程是首先通过中间件,然后再通过默认的 URL 方式进行的.我们可以在 Middlewar ...