WebApi接口安全性 接口权限调用、参数防篡改防止恶意调用
<h1>背景先容</h1> <p>迩来使用WebApi开辟一套对外接口,重要是数据的外送以及效果回传,接口没什么难度,接纳WebApi+EF的架构简朴创建一个模板工程,使用template天生一套WebApi接口,去掉put、delete等操纵,修改一下就可以上线。这些都不在话下,反正网上一大堆教程,随便找谁人step by step做下来就可以了。</p> <p> </p> <p>然后发布上线后,接口是放在外网,面对两个题目:</p> <ol> <li>怎样包管接口的调用的正当性<li><strong>怎样包管接口及数据的安全性</strong></li></ol> <p>其实这两个题目是相互团结的,先包管正当,然后在正当底子上包管哀求的唯一性,制止参数被篡改。</p> <p>鉴于接口上线限期紧迫,团结浩繁案例,先管理掉接口调用数据的安全性题目,这里接纳了RSA报文加解密的方案,包管数据安全和防止接口被恶意调用以及参数篡改的题目。</p> <p>本文参考博客园多篇博文,内容多有引用,文末附有参照博文的地址。</p> <p>以下为正文!</p> <h1>正文</h1> <h2>起首,接口面对的题目:</h2> <ol> <li><strike>哀求泉源(身份)是否正当(部门管理,后续在处置惩罚)</strike>?<li>哀求参数被篡改?<li>哀求的唯一性(不可复制),防止哀求被恶意攻击</li></ol> <p> </p> <h2>管理方案:</h2> <p> </p> <ol> <li>参数加密: 客户端和服务端参数接纳RSA加密后通报,原则上只有<strong>持有私钥的服务端</strong>才气解密<strong>客户端公钥加密</strong>的参数,制止了参数篡改的题目<li>哀求署名:接纳一套署名算法,对哀求举行署名验证,包管哀求的唯一性</li></ol> <p> </p> <p>这里参照了<a href="https://www.cnblogs.com/clly/p/7384008.html">WebAPi使用公钥私钥加密先容和使用</a> 一文,举行公钥私钥加解密的处置惩罚</p> <p><strong>先说服务端:</strong></p> <h3>扩展 MessageProcessingHandler</h3> <p>先看一下<font style="background-color: #ffff00">MessageProcessingHandler</font>的先容:</p> #region 程序集 System.Net.Http, Version=4.2.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a// C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.7.2\System.Net.Http.dll
#endregion
using System.Threading;
using System.Threading.Tasks;
namespace System.Net.Http
{
//
// 择要:
// 仅对哀求和/或相应消息举行一些小型处置惩罚的处置惩罚程序的基类。
public abstract class MessageProcessingHandler : DelegatingHandler
{
//
// 择要:
// 创建的一个实例 System.Net.Http.MessageProcessingHandler 类。
protected MessageProcessingHandler();
//
// 择要:
// 创建的一个实例 System.Net.Http.MessageProcessingHandler 具有特定的内部处置惩罚程序类。
//
// 参数:
// innerHandler:
// 内部处置惩罚程序负责处置惩罚 HTTP 相应消息。
protected MessageProcessingHandler(HttpMessageHandler innerHandler);
//
// 择要:
// 处置惩罚每个发送到服务器的哀求。
//
// 参数:
// request:
// 要处置惩罚的 HTTP 哀求消息。
//
// cancellationToken:
// 可由其他对象或线程用以吸收取消关照的取消标志。
//
// 返回效果:
// 已处置惩罚的 HTTP 哀求消息。
protected abstract HttpRequestMessage <strong><font style="background-color: #ff0000">ProcessRequest</font></strong>(HttpRequestMessage request, CancellationToken cancellationToken);
//
// 择要:
// 处置惩罚来自服务器的每个相应。
//
// 参数:
// response:
// 要处置惩罚的 HTTP 相应消息。
//
// cancellationToken:
// 可由其他对象或线程用以吸收取消关照的取消标志。
//
// 返回效果:
// 已处置惩罚的 HTTP 相应消息。
protected abstract HttpResponseMessage <font style="background-color: #ff0000">ProcessResponse</font>(HttpResponseMessage response, CancellationToken cancellationToken);
//
// 择要:
// 异步发送 HTTP 哀求到要发送到服务器的内部处置惩罚程序。
//
// 参数:
// request:
// 要发送到服务器的 HTTP 哀求消息。
//
// cancellationToken:
// 可由其他对象或线程用以吸收取消关照的取消标志。
//
// 返回效果:
// 体现异步操纵的使命对象。
//
// 异常:
// T:System.ArgumentNullException:
// request 是 null。
protected internal sealed override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken);
}
}
<p> </p>
<p>扩展这个类的目标是<strong>解密参数</strong>,其实也可以推迟到Action过滤器中做,但是照旧以为机遇上在这里处置惩罚比力符合。具体的发起相识一下WebApi消息管道以及扩展过滤器的干系文章,本文不再延伸。</p>
<p>下面是扩展的实今世码:</p>
<p> </p>
/// <summary>
/// 哀求预处置惩罚,报文解密
/// </summary>
/// <seealso cref="System.Net.Http.MessageProcessingHandler"/>
public class ArgDecryptMessageProcesssingHandler : MessageProcessingHandler
{
/// <summary>
/// 处置惩罚每个发送到服务器的哀求。
/// </summary>
/// <param name="request"> 要处置惩罚的 HTTP 哀求消息。</param>
/// <param name="cancellationToken">可由其他对象或线程用以吸收取消关照的取消标志。</param>
/// <returns>已处置惩罚的 HTTP 哀求消息。</returns>
protected override HttpRequestMessage ProcessRequest(HttpRequestMessage request, CancellationToken cancellationToken)
{
var contentType = request.Content.Headers.ContentType;
//swagger哀求直接跳过不予处置惩罚
if (request.RequestUri.AbsolutePath.Contains("/swagger"))
{
return request;
}
//得到平台私钥
string privateKey = Common.GetRsaPrivateKey();
//获取Get中的Query信息,解密后重置哀求上下文
if (request.Method == HttpMethod.Get)
{
string baseQuery = request.RequestUri.Query;
if (!string.IsNullOrEmpty(baseQuery))
{
baseQuery = baseQuery.Substring(1);
baseQuery = Regex.Match(baseQuery, "(sign=)*(?<sign>[\\S]+)").Groups.Value;
baseQuery = RsaHelper.RSADecrypt(privateKey, baseQuery);
var requestUrl = $"{request.RequestUri.AbsoluteUri.Split('?')}?{baseQuery}";
request.RequestUri = new Uri(requestUrl);
}
}
//获取Post哀求中body中的报文信息,解密后重置哀求上下文
if (request.Method == HttpMethod.Post)
{
string baseContent = request.Content.ReadAsStringAsync().Result;
baseContent = Regex.Match(baseContent, "(sign=)*(?<sign>[\\S]+)").Groups.Value;
baseContent = RsaHelper.RSADecrypt(privateKey, baseContent);
request.Content = new StringContent(baseContent);
//此contentType必须末了设置 否则会变成默认值
request.Content.Headers.ContentType = contentType;
}
return request;
}
/// <summary>
/// 处置惩罚来自服务器的每个相应。
/// </summary>
/// <param name="response"> 要处置惩罚的 HTTP 相应消息。</param>
/// <param name="cancellationToken">可由其他对象或线程用以吸收取消关照的取消标志。</param>
/// <returns>已处置惩罚的 HTTP 相应消息。</returns>
protected override HttpResponseMessage ProcessResponse(HttpResponseMessage response, CancellationToken cancellationToken)
{
return response;
}
}
<p>获取平台私钥那里,现实上可以针对差别的接口调用方单独一个,另起一篇在先容。</p>
<p> </p>
<p>然后找到管理方案【App_Start】目次下的<font style="background-color: #ffff00">WebApiConfig</font>类,在内里添加如下代码,启用消息处置惩罚扩展类:</p>
public static void Register(HttpConfiguration config)
{
// Web API 路由
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
<strong><font style="background-color: #ff0000">config.MessageHandlers.Add(</font></strong><strong><font style="background-color: #ff0000">new</font></strong><strong><font style="background-color: #ff0000"> ArgDecryptMessageProcesssingHandler());</font></strong>
}
<h3>扩展 ActionFilterAttribute</h3>
<blockquote>
<p><font size="3">注意!注意!注意!</font></p>
<p><font size="3">原博文中是扩展的 </font><font style="background-color: #ffff00">AuthorizeAttribute</font>,即认证和授权过滤器,代码实现上是没有多大差别的;在机遇上<strong>认证和授权过滤器</strong>要比方法过滤器实行的要早,更得当做认证和授权的操纵。而我们扩展这个过滤器的目标是对报文举行<strong>署名验证</strong>以及<strong>超时验证</strong>,以是使用<strong>方法过滤器</strong>更适当些。</p></blockquote><font size="3"></font>
<p>下面是扩展过滤器的代码:</p>
<p>
/// <summary>
/// 扩展方法过滤器,进入方法前验证署名
/// </summary>
public class ApiVerifyFilter : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
base.OnActionExecuting(actionContext);
//获取平台私钥
string privateKey = Common.GetRsaPrivateKey();
//获取哀求的超时时间,为了测试设置为100秒,即两次调用隔断不能高出100秒
string expireyTime = ConfigurationManager.AppSettings["UrlExpireTime"];
var request = actionContext.Request;
//验证署名所需header内容
if (!request.Headers.Contains("signature") || !request.Headers.Contains("timestamp") || !request.Headers.Contains("nonce"))
{
SetSpecialResponseMessage(actionContext, 40301);
return;
}
var token = string.Empty;
var signature = request.Headers.GetValues("signature").FirstOrDefault();
var timeStamp = request.Headers.GetValues("timestamp").FirstOrDefault();
var nonce = request.Headers.GetValues("nonce").FirstOrDefault();
//验证署名
if (!Common.SignValidate(privateKey, nonce, timeStamp, signature, token))
{
SetSpecialResponseMessage(actionContext, 40302);
return;
}
//查抄接口调用是否超时
var ts = Common.DateTime2TimeStamp(DateTime.UtcNow) - Convert.ToDouble(timeStamp);
if (ts > int.Parse(expireyTime) * 1000)
{
SetSpecialResponseMessage(actionContext, 40303);
return;
}
}
/// <summary>
/// 设置署名验证异常返回状态
/// </summary>
/// <param name="actionContext">当前哀求上下文</param>
/// <param name="statusCode">异常状态码</param>
private static void SetSpecialResponseMessage(HttpActionContext actionContext, int statusCode)
{
BizResponseModel model = new BizResponseModel
{
Status = statusCode,
Date = DateTime.Now.ToString("yyyyMMddhhmmssfff"),
Message = "服务端拒绝访问"
};
switch (statusCode)
{
case 40301:
model.Message = "没有设置署名、时间戳、随机字符串";
break;
case 40302:
model.Message = "署名无效";
break;
case 40303:
model.Message = "无效的哀求";
break;
default:
break;
}
actionContext.Response = new HttpResponseMessage
{
Content = new StringContent(JsonConvert.SerializeObject(model))
};
}
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
base.OnActionExecuted(actionExecutedContext);
}
}<br>这里为了方便写了个ResponseModel,代码如下:</p>
/// <summary>
/// 特殊状态
/// </summary>
public class BizResponseModel
{
public int Status { get; set; }
public string Message { get; set; }
public string Date { get; set; }
}
<p>然后下面是用的公共方法:</p>
<p>
/// <summary>
/// 获取时间戳毫秒数
/// </summary>
/// <param name="dateTime"></param>
/// <returns></returns>
public static long DateTime2TimeStamp(DateTime dateTime)
{
TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
return Convert.ToInt64(ts.TotalMilliseconds);
}
public static bool SignValidate(string privateKey, string nonce, string timestamp, string signature, string token)
{
bool isValidate = false;
var tempSign = RsaHelper.RSADecrypt(privateKey, signature);
string[] arr = new[] { token, timestamp, nonce }.OrderBy(z => z).ToArray();
string arrString = string.Join("", arr);
var sha256Result = arrString.EncryptSha256();
if (sha256Result == tempSign)
{
isValidate = true;
}
return isValidate;
}</p>
<p>署名验证的过程如下:</p>
<ol>
<li>获取到报文Header中的 nonce、timestamp、signature、token信息</li>
<li>将token、timestamp、nonce 三者归并数组中,然后举行序次排序(排序为了包管后续三个字符串拼接后一致)</li>
<li>将数组拼接成字符串,然后举行sha256 哈希运算(这里随便什么运算都行,重要为了防止超长加密贫苦)</li>
<li>将上一步的哈希效果与 RSA解密效果举行比对,一致则署名验证通过,否则则署名不一致,哀求为伪造</li></ol>
<p> </p>
<p><br>然后,如今需要启用刚添加的方法过滤器,由于是继承与属性,可以全局启用,大概单个Controller中启用、大概为某个Action启用。全局启用代码如下:</p>
<p>下的<font style="background-color: #ffff00">WebApiConfig</font>类添加如下代码:</p>
config.Filters.Add(new ApiVerifyFilter());
<p><font size="5"></font> </p>
<p><font size="5">OK,全部完成,末了附上两个前后的效果对比!</font></p>
<p><a href="https://img2018.cnblogs.com/blog/171569/201910/171569-20191031190516572-270810520.jpg"></a></p>
<p> </p>
<p>参考博文:</p>
<p><a href="https://www.cnblogs.com/MR-YY/p/5972380.html">WebApi安全性 使用TOKEN+署名验证</a></p>
<p><a href="https://www.cnblogs.com/clly/p/7384008.html">WebAPi接口安全之公钥私钥加密</a></p>
<p><a href="https://www.cnblogs.com/richieyang/p/4918819.html">使用OAuth打造webapi认证服务供自己的客户端使用</a></p>
<p><a href="https://www.cnblogs.com/lijingran/p/6420397.html">Asp.Net WebAPI中Filter过滤器的使用以及实行序次</a></p>
<p><a href="https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Access_Overview.html">微信 公众号开辟文档</a></p>
<p> </p>
<p>写博文太累了,回家吃螃蟹补补~</p>
页:
[1]