原文地址:WebApi 使用TOKEN+簽名驗證
一、不進行驗證的方式
api查詢接口:
客戶端調用:http://api.XXX.com/getproduct?id=value1
如上,這種方式簡單粗暴,在瀏覽器直接輸入"http://api.XXX.com/getproduct?id=value1",即可獲取產品列表信息了,但是這樣的方式會存在很嚴重的安全性問題,沒有進行任何的驗證,大家都可以通過這個方法獲取到產品列表,導致產品信息泄露。
那麼,如何驗證調用者身份呢?如何防止參數被篡改呢?如何保證請求的唯一性? 如何保證請求的唯一性,防止請求被惡意攻擊呢?
二、使用TOKEN+簽名認證 保證請求安全性
token+簽名認證的主要原理是:1.做一個認證服務,提供一個認證的webapi,用戶先訪問它獲取對應的token
2.用戶拿着相應的token以及請求的參數和服務器端提供的簽名算法計算出簽名後再去訪問指定的api
3.服務器端每次接收到請求就獲取對應用戶的token和請求參數,服務器端再次計算簽名和客戶端簽名做對比,如果驗證通過則正常訪問相應的api,驗證失敗則返回具體的失敗信息
具體代碼如下 :
1.用戶請求認證服務GetToken,將TOKEN保存在服務器端緩存中,並返回對應的TOKEN到客戶端(該請求不需要進行簽名認證)
public HttpResponseMessage GetToken(string staffId)
{
ResultMsg resultMsg = null;
int id = 0;
//判斷參數是否合法
if (string.IsNullOrEmpty(staffId) || (!int.TryParse(staffId, out id)))
{
resultMsg = new ResultMsg();
resultMsg.StatusCode = (int)StatusCodeEnum.ParameterError;
resultMsg.Info = StatusCodeEnum.ParameterError.GetEnumText();
resultMsg.Data = "";
return HttpResponseExtension.toJson(JsonConvert.SerializeObject(resultMsg));
}
//插入緩存
Token token =(Token)HttpRuntime.Cache.Get(id.ToString());
if (HttpRuntime.Cache.Get(id.ToString()) == null)
{
token = new Token();
token.StaffId = id;
token.SignToken = Guid.NewGuid();
token.ExpireTime = DateTime.Now.AddDays(1);
HttpRuntime.Cache.Insert(token.StaffId.ToString(), token, null, token.ExpireTime, TimeSpan.Zero);
}
//返回token信息
resultMsg =new ResultMsg();
resultMsg.StatusCode = (int)StatusCodeEnum.Success;
resultMsg.Info = "";
resultMsg.Data = token;
return HttpResponseExtension.toJson(JsonConvert.SerializeObject(resultMsg));
}
2.客戶端調用服務器端API,需要對請求進行簽名認證,簽名方式如下
(1) get請求:按照請求參數名稱將所有請求參數按照字母先後順序排序得到:keyvaluekeyvalue...keyvalue 字符串如:將arong=1,mrong=2,crong=3 排序爲:arong=1, crong=3,mrong=2 然後將參數名和參數值進行拼接得到參數字符串:arong1crong3mrong2。
public static Tuple<string,string> GetQueryString(Dictionary<string, string> parames)
{
// 第一步:把字典按Key的字母順序排序
IDictionary<string, string> sortedParams = new SortedDictionary<string, string>(parames);
IEnumerator<KeyValuePair<string, string>> dem = sortedParams.GetEnumerator();
// 第二步:把所有參數名和參數值串在一起
StringBuilder query = new StringBuilder(""); //簽名字符串
StringBuilder queryStr = new StringBuilder(""); //url參數
if (parames == null || parames.Count == 0)
return new Tuple<string,string>("","");
while (dem.MoveNext())
{
string key = dem.Current.Key;
string value = dem.Current.Value;
if (!string.IsNullOrEmpty(key))
{
query.Append(key).Append(value);
queryStr.Append("&").Append(key).Append("=").Append(value);
}
}
return new Tuple<string, string>(query.ToString(), queryStr.ToString().Substring(1, queryStr.Length - 1));
}
Product product = new Product() { Id = 1, Name = "安慕希", Count = 10, Price = 58.8 };
var data=JsonConvert.SerializeObject(product);
//加入頭信息
request.Headers.Add("staffid", staffId.ToString()); //當前請求用戶StaffId
request.Headers.Add("timestamp", timeStamp); //發起請求時的時間戳(單位:毫秒)
request.Headers.Add("nonce", nonce); //發起請求時的時間戳(單位:毫秒)
request.Headers.Add("signature", GetSignature(timeStamp,nonce,staffId,data)); //當前請求內容的數字簽名
private static string GetSignature(string timeStamp,string nonce,int staffId,string data)
{
Token token = null;
var resultMsg = GetSignToken(staffId);
if (resultMsg != null)
{
if (resultMsg.StatusCode == (int)StatusCodeEnum.Success)
{
token = resultMsg.Result;
}
else
{
throw new Exception(resultMsg.Data.ToString());
}
}
else
{
throw new Exception("token爲null,員工編號爲:" +staffId);
}
var hash = System.Security.Cryptography.MD5.Create();
//拼接簽名數據
var signStr = timeStamp +nonce+ staffId + token.SignToken.ToString() + data;
//將字符串中字符按升序排序
var sortStr = string.Concat(signStr.OrderBy(c => c));
var bytes = Encoding.UTF8.GetBytes(sortStr);
//使用MD5加密
var md5Val = hash.ComputeHash(bytes);
//把二進制轉化爲大寫的十六進制
StringBuilder result = new StringBuilder();
foreach (var c in md5Val)
{
result.Append(c.ToString("X2"));
}
return result.ToString().ToUpper();
}
(4) webapi接收到相應的請求,取出請求頭中的timespan,nonc,staffid,signature 數據,根據timespan判斷此次請求是否失效,根據staffid取出相應token判斷token是否失效,根據請求類型取出對應的請求參數,然後服務器端按照同樣的規則重新計算請求籤名,判斷和請求頭中的signature數據是否相同,如果相同的話則是合法請求,正常返回數據,如果不相同的話,該請求可能被惡意篡改,禁止訪問相應的數據,返回相應的錯誤信息
如下使用全局過濾器攔截所有api請求進行統一的處理
public class ApiSecurityFilter : ActionFilterAttribute
{
public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext)
{
ResultMsg resultMsg = null;
var request = actionContext.Request;
string method = request.Method.Method;
string staffid = String.Empty, timestamp = string.Empty, nonce = string.Empty, signature = string.Empty;
int id = 0;
if (request.Headers.Contains("staffid"))
{
staffid = HttpUtility.UrlDecode(request.Headers.GetValues("staffid").FirstOrDefault());
}
if (request.Headers.Contains("timestamp"))
{
timestamp = HttpUtility.UrlDecode(request.Headers.GetValues("timestamp").FirstOrDefault());
}
if (request.Headers.Contains("nonce"))
{
nonce = HttpUtility.UrlDecode(request.Headers.GetValues("nonce").FirstOrDefault());
}
if (request.Headers.Contains("signature"))
{
signature = HttpUtility.UrlDecode(request.Headers.GetValues("signature").FirstOrDefault());
}
//GetToken方法不需要進行簽名驗證
if (actionContext.ActionDescriptor.ActionName == "GetToken")
{
if (string.IsNullOrEmpty(staffid) || (!int.TryParse(staffid, out id) || string.IsNullOrEmpty(timestamp) || string.IsNullOrEmpty(nonce)))
{
resultMsg = new ResultMsg();
resultMsg.StatusCode = (int)StatusCodeEnum.ParameterError;
resultMsg.Info = StatusCodeEnum.ParameterError.GetEnumText();
resultMsg.Data = "";
actionContext.Response = HttpResponseExtension.toJson(JsonConvert.SerializeObject(resultMsg));
base.OnActionExecuting(actionContext);
return;
}
else
{
base.OnActionExecuting(actionContext);
return;
}
}
//判斷請求頭是否包含以下參數
if (string.IsNullOrEmpty(staffid) || (!int.TryParse(staffid, out id) || string.IsNullOrEmpty(timestamp) || string.IsNullOrEmpty(nonce) || string.IsNullOrEmpty(signature)))
{
resultMsg = new ResultMsg();
resultMsg.StatusCode = (int)StatusCodeEnum.ParameterError;
resultMsg.Info = StatusCodeEnum.ParameterError.GetEnumText();
resultMsg.Data = "";
actionContext.Response = HttpResponseExtension.toJson(JsonConvert.SerializeObject(resultMsg));
base.OnActionExecuting(actionContext);
return;
}
//判斷timespan是否有效
double ts1 = 0;
double ts2 = (DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0)).TotalMilliseconds;
bool timespanvalidate = double.TryParse(timestamp, out ts1);
double ts = ts2 - ts1;
bool falg = ts > int.Parse(WebSettingsConfig.UrlExpireTime) * 1000;
if (falg || (!timespanvalidate))
{
resultMsg = new ResultMsg();
resultMsg.StatusCode = (int)StatusCodeEnum.URLExpireError;
resultMsg.Info = StatusCodeEnum.URLExpireError.GetEnumText();
resultMsg.Data = "";
actionContext.Response = HttpResponseExtension.toJson(JsonConvert.SerializeObject(resultMsg));
base.OnActionExecuting(actionContext);
return;
}
//判斷token是否有效
Token token = (Token)HttpRuntime.Cache.Get(id.ToString());
string signtoken = string.Empty;
if (HttpRuntime.Cache.Get(id.ToString()) == null)
{
resultMsg = new ResultMsg();
resultMsg.StatusCode = (int)StatusCodeEnum.TokenInvalid;
resultMsg.Info = StatusCodeEnum.TokenInvalid.GetEnumText();
resultMsg.Data = "";
actionContext.Response = HttpResponseExtension.toJson(JsonConvert.SerializeObject(resultMsg));
base.OnActionExecuting(actionContext);
return;
}
else
{
signtoken = token.SignToken.ToString();
}
//根據請求類型拼接參數
NameValueCollection form = HttpContext.Current.Request.QueryString;
string data = string.Empty;
switch (method)
{
case "POST":
Stream stream = HttpContext.Current.Request.InputStream;
string responseJson = string.Empty;
StreamReader streamReader = new StreamReader(stream);
data = streamReader.ReadToEnd();
break;
case "GET":
//第一步:取出所有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);
}
}
data = query.ToString();
break;
default:
resultMsg = new ResultMsg();
resultMsg.StatusCode = (int)StatusCodeEnum.HttpMehtodError;
resultMsg.Info = StatusCodeEnum.HttpMehtodError.GetEnumText();
resultMsg.Data = "";
actionContext.Response = HttpResponseExtension.toJson(JsonConvert.SerializeObject(resultMsg));
base.OnActionExecuting(actionContext);
return;
}
bool result = SignExtension.Validate(timestamp, nonce, id, signtoken,data, signature);
if (!result)
{
resultMsg = new ResultMsg();
resultMsg.StatusCode = (int)StatusCodeEnum.HttpRequestError;
resultMsg.Info = StatusCodeEnum.HttpRequestError.GetEnumText();
resultMsg.Data = "";
actionContext.Response = HttpResponseExtension.toJson(JsonConvert.SerializeObject(resultMsg));
base.OnActionExecuting(actionContext);
return;
}
else
{
base.OnActionExecuting(actionContext);
}
}
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
base.OnActionExecuted(actionExecutedContext);
}
}
詳細地址:http://www.cnblogs.com/MR-YY/p/5972380.html#!comments