一、 在开放的api接口中,我们通过http Post或者Get请求服务器的时候,会面临着许多的安全性问题,例如:
①请求来源(身份)是否合法?
②请求参数被篡改?
③请求的唯一性(不可复制),防止请求被恶意攻击
为了保证数据在通信时的安全性,我们可以采用TOKEN+参数签名的方式来进行相关验证。
二、使用TOKEN+签名认证 保证请求安全性
token+签名认证的主要原理是:
1.AppKey从何而来? 通过账号密码登录或者第三方认证服务器认证获取AppKey.(本文中的代码没有这一步骤.本文中的代码没有这一步骤.本文中的代码没有这一步骤.重要的事情说三遍)
如下图:(博客大佬回复的)(staffid或者appId就是此文中的AppKey)
2.1.做一个认证服务,提供一个认证的webapi,用户AppKey先访问它获取对应的token,用户Token缓存在服务器(该请求不需要进行签名认证),返回Token给用户,Token可能过期失效需要重新认证。
3.在后面的API接口中用户拿着请求头(①AppKey ②TimeStamp ③ Nonce ④Sign签名验证(AppKey +TimeStamp+Nonce+Token+请求参数名,请求参数值) ,注意:请求头中不携带Token。
4.服务器端每次接收到请求就获取对应用户的请求参数,服务器端计算Sign和客户端Sign做对比,如果验证通过则正常访问相应的api,验证失败则返回具体的失败信息
三、 参数说明
AppKey:公钥
TimeStamp:时间戳
Nonce:随机数
Sign:签名值
Token:密钥(禁止传递)
四、 直接上干货
① WebApiConfig 注册过滤器
var webapiAss = Assembly.Load("DAL");
//从webapiAss中拿到所有实现了IAuthorizationFilter接口,或者实现了IActionFilter接口的非抽象过滤器类
var filters = webapiAss.GetTypes().Where(r => !r.IsAbstract && (typeof(IAuthorizationFilter).IsAssignableFrom(r) || typeof(IActionFilter).IsAssignableFrom(r)));
foreach (var item in filters)
{
//从IOC容器中拿到过滤器类对象(因为过滤器都是实现了IFilter接口的,所有这里以IFilter接口来接收)
IFilter filter = (IFilter)GlobalConfiguration.Configuration.DependencyResolver.GetService(item);
config.Filters.Add(filter); //注册过滤器
}
②Global.asax.cs
protected void Application_Start()
{
AutofacConfig.SetAutofacWebApi();
}
③使用Autofac注入
/// <summary>
/// 使用Autofac注入
/// </summary>
public class AutofacConfig
{
/// <summary>
/// 注册接口和实现
/// </summary>
public static void SetAutofacWebApi()
{
//得到你的HttpConfiguration.
var configuration = GlobalConfiguration.Configuration;
var builder = new ContainerBuilder();
注册控制器
//builder.RegisterApiControllers(Assembly.GetExecutingAssembly()).PropertiesAutowired();
var webapiAssembly = Assembly.Load("DAL");
//注册webapi项目中实现了IAuthorizationFilter接口或者实现了IActionFilter接口的非抽象过滤器类
builder.RegisterAssemblyTypes(webapiAssembly).Where(r => !r.IsAbstract &&
(typeof(IAuthorizationFilter).IsAssignableFrom(r)) || typeof(IActionFilter).IsAssignableFrom(r)).PropertiesAutowired();
IContainer container = builder.Build();
//将依赖关系解析器设置为Autofac。
var resolver = new AutofacWebApiDependencyResolver(container);
configuration.DependencyResolver = resolver;
}
}
④服务器token+签名认证 +过滤器
using DAL;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
namespace MES_Server
{
public class MyAuthenticationAttribute : IAuthorizationFilter//也可以直接继承AuthorizationFilterAttribute
{
public bool AllowMultiple => true;
public async Task<HttpResponseMessage> ExecuteAuthorizationFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func<Task<HttpResponseMessage>> continuation)
{
//获得报文头中的AppKey,TimeStamp,Nonce,Sign (我们与客户端约定,在向服务端发起请求的时候,将AppKey,TimeStamp,Nonce,Sign放到请求报文头中)
IEnumerable<string> appKeys;
if (!actionContext.Request.Headers.TryGetValues("AppKey", out appKeys)) //从请求报文头中获取AppKey
{
return new HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized) { Content = new StringContent("报文头中的AppKey为空") };
}
IEnumerable<string> timeStamps;
if (!actionContext.Request.Headers.TryGetValues("TimeStamp", out timeStamps))
{
return new HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized) { Content = new StringContent("报文头中的TimeStamp为空") };
}
IEnumerable<string> nonces;
if (!actionContext.Request.Headers.TryGetValues("Nonce", out nonces))
{
return new HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized) { Content = new StringContent("报文头中的Nonce为空") };
}
//GetToken方法不需要进行签名验证
if (actionContext.ActionDescriptor.ActionName == "GetToken")
{
if (string.IsNullOrEmpty(appKeys.First()) || string.IsNullOrEmpty(timeStamps.First()) || string.IsNullOrEmpty(nonces.First()))
{
return new HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized) { Content = new StringContent("AppKey/TimeStamp/Nonce任一数值不能为空") };
}
else
{
return await continuation();
}
}
//数字签名
IEnumerable<string> signs;
if (!actionContext.Request.Headers.TryGetValues("Sign", out signs)) //从请求报文头中获取Sign
{
return new HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized) { Content = new StringContent("报文头中的Sign为空") };
}
//判断timespan是否有效
double ts1 = 0;
double ts2 = (DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0)).TotalMilliseconds;
bool timespanvalidate = double.TryParse(timeStamps.First(), out ts1);
double ts = ts2 - ts1;
bool flag = ts > 1000 * 30;
if (flag || (!timespanvalidate))
{
return new HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized) { Content = new StringContent("timespan认证失败") };
}
//判断AppToken是否有效
AppToken token = (AppToken)HttpRuntime.Cache.Get(appKeys.First());
string signtoken = string.Empty;
if (token == null)
{
return new HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized) { Content = new StringContent("AppToken认证失败") };
}
else
{
signtoken = token.SignToken.ToString();
}
string requestDataStr = ""; //请求参数字符串
List<KeyValuePair<string, string>> requestDataList = new List<KeyValuePair<string, string>>();//请求参数键值对
if (actionContext.Request.Method == HttpMethod.Post) //如果是Post请求
{
Stream stream = HttpContext.Current.Request.InputStream;
string responseJson = string.Empty;
StreamReader streamReader = new StreamReader(stream);
requestDataStr = streamReader.ReadToEnd();
stream.Position = 0;
}
if (actionContext.Request.Method == HttpMethod.Get) //如果是Get请求
{
//根据请求类型拼接参数
NameValueCollection form = HttpContext.Current.Request.QueryString;
//第一步:取出所有get参数
IDictionary<string, string> parameters = new Dictionary<string, string>();
for (int f = 0; f < form.Count; f++)
{
string key = form.Keys[f];
parameters.Add(key, form[key]);
}
// 第二步:把字典按Key的字母顺序排序
IDictionary<string, string> sortedParams = new SortedDictionary<string, string>(parameters);
IEnumerator<KeyValuePair<string, string>> dem = sortedParams.GetEnumerator();
// 第三步:把所有参数名和参数值串在一起
StringBuilder query = new StringBuilder();
while (dem.MoveNext())
{
string key = dem.Current.Key;
string value = dem.Current.Value;
if (!string.IsNullOrEmpty(key))
{
query.Append(key).Append(value);
}
}
requestDataStr = query.ToString();
}
//计算Sign (即:计算timeStamps+nonces+appKeys+signtoken+requestDataStr的md5值)
string computedSign = DESEncrypt.DesEncrypt(timeStamps.First() + nonces.First() + appKeys.First() + signtoken + requestDataStr);
//用户传进来md5值和计算出来的比对一下,就知道数据是否有被篡改过
if (signs.First().Equals(computedSign, StringComparison.CurrentCultureIgnoreCase))
{
return await continuation();
}
else
{
return new HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized) { Content = new StringContent("sign认证失败") };
}
}
}
}
⑤DES加密解密
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace DAL
{
/**//// <summary>
/// DESEncrypt加密解密算法。
/// </summary>
public sealed class DESEncrypt
{
private DESEncrypt()
{
//
// TODO: 在此处添加构造函数逻辑
//
}
private static string key = "Glodonxx";
/**//// <summary>
/// 对称加密解密的密钥
/// </summary>
public static string Key
{
get
{
return key;
}
set
{
key = value;
}
}
/**//// <summary>
/// DES加密
/// </summary>
/// <param name="encryptString"></param>
/// <returns></returns>
public static string DesEncrypt(string encryptString)
{
byte[] keyBytes = Encoding.UTF8.GetBytes(key.Substring(0, 8));
byte[] keyIV = keyBytes;
byte[] inputByteArray = Encoding.UTF8.GetBytes(encryptString);
DESCryptoServiceProvider provider = new DESCryptoServiceProvider();
MemoryStream mStream = new MemoryStream();
CryptoStream cStream = new CryptoStream(mStream, provider.CreateEncryptor(keyBytes, keyIV), CryptoStreamMode.Write);
cStream.Write(inputByteArray, 0, inputByteArray.Length);
cStream.FlushFinalBlock();
return Convert.ToBase64String(mStream.ToArray());
}
/**//// <summary>
/// DES解密
/// </summary>
/// <param name="decryptString"></param>
/// <returns></returns>
public static string DesDecrypt(string decryptString)
{
byte[] keyBytes = Encoding.UTF8.GetBytes(key.Substring(0, 8));
byte[] keyIV = keyBytes;
byte[] inputByteArray = Convert.FromBase64String(decryptString);
DESCryptoServiceProvider provider = new DESCryptoServiceProvider();
MemoryStream mStream = new MemoryStream();
CryptoStream cStream = new CryptoStream(mStream, provider.CreateDecryptor(keyBytes, keyIV), CryptoStreamMode.Write);
cStream.Write(inputByteArray, 0, inputByteArray.Length);
cStream.FlushFinalBlock();
return Encoding.UTF8.GetString(mStream.ToArray());
}
}
}
⑥获取Token,插入缓存
[HttpGet]
/// <summary>
/// 获取Token密钥
/// </summary>
/// <param name="UserDto"></param>
/// <returns></returns>
public async Task<string> GetToken([FromBody]UserDto userDto)
{
//插入缓存
AppToken token = (AppToken)HttpRuntime.Cache.Get(userDto.AppKey);
if (token == null)
{
token = new AppToken();
token.appKey = userDto.AppKey;
token.SignToken = Guid.NewGuid();
token.ExpireTime = DateTime.Now.AddMinutes(60);
HttpRuntime.Cache.Insert(appToken.appKey, token, null, token.ExpireTime, TimeSpan.Zero);
}
return Common.Content(Status.Code.Success,"获取密钥成功",token.ToJson());
}