這兩天 一直和京東對接接口,我們用.net api 提供接口,對方用java調用,本來沒什么問題,但是對方對數據安全要求特別嚴,要驗簽,于是噩夢開始了。
1、在傳輸的時候,約定傳輸格式:
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(Url);//+ "?RequestData="+ paramrequest.Method = "POST";request.ContentType = "application/json";//參數是接送//request.ContentType = "application/x-www-form-urlencoded";//參數為&拼接request.ContentLength = param.Length;
?
2、雙方平臺對編碼不一致,所以在對數據進行MD5加密前,先進行UTF8編碼:
byte[] md5Token = md5.ComputeHash(Encoding.UTF8.GetBytes(data));string base64Token = Convert.ToBase64String(md5Token);
3、我們遇到了這個問題:https://bbs.csdn.net/topics/340058520
具體的描述就是C#和java對二進制的編碼值不一樣
java?byte?:?-128~127
C#???byte?:?0~255
但是,這只是視覺欺騙,做硬件的經理說,雖然兩邊看到對字符的編碼得到的值不一樣,但是,實際上,計算機對這個的值的識別是一樣的。所以這根本就不是個問題。但是對方特別有毅力,反復的試了各種編碼格式,于是我就跟在他后面,一個一個的試,又是遠程合作,不得不佩服,研究生果然比我這種本科畢業的做事有毅力,然而,最后發現,是他在做加密的時候,忘記把私鑰放進去了。哎,感覺身體被掏空┭┮﹏┭┮
4、Cookie在傳輸的過程中,+、/、=會丟失,所以使用了替換
string base64Token2 = base64Token.Replace('+', '-').Replace('/', '_').Replace('=', '*');
5、我們使用模型接收數據,這時候,會出現數據接收不到的情況,那么上面1的ContentType 就顯得比較重要了,既然我上面已經注釋了,就不多寫了。
6、應為我們使用模型接收數據,而對方見有的數據不是必填的,所以就沒有寫,這樣對方填3個參數,進行加密計算,而我這邊會把沒有填的null值也加進來進行加密計算,并且計算的時候,我們這邊采用序列化,使用兩邊還要對參數進行排序,所以,我們的加密結果始終不一樣,崩潰┭┮﹏┭┮,中間考慮過用string接收參數,但是api不支持直接接收string參數,必須要加一個[FromBody]的標記,然而,問題有開始來了,對方的請求,根本進不來,繼續崩潰┭┮﹏┭┮。最終我們還是使用模型對數據進行接收,不過接收參數的方式改了一下,用流來接收,這樣,對方傳什么,我們接收的就是什么,具體代碼如下:
這是原來的代碼
var jsonSetting = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore };var dataOld = JsonConvert.SerializeObject(actionContext.ActionArguments[ArgumentsName], Formatting.Indented, jsonSetting);//這兩行用來去除為空的參數 dataOld = JsonSort.SortJson(JToken.Parse(dataOld), null);//排序
這是修改后的代碼
Stream stream = HttpContext.Current.Request.InputStream;StreamReader streamReader = new StreamReader(stream);responseJson = streamReader.ReadToEnd();
可以看到 接收參數的方式由?actionContext.ActionArguments[ArgumentsName] 換成了下面的?streamReader.ReadToEnd();如果有哪位大神指導string類型的怎么發送,怎么接收,還請告知一下,畢竟第一次做,沒有經驗
?
7、整體的驗簽處理使用的是ActionFilterAttribute 攔截,具體的思路就是,將秘鑰各自保存一份,將數據用秘鑰加密,然后將驗簽的Token可用戶標識(不是秘鑰)寫一份到cookie里面,然后邏輯上就可以不做任何更改直接使用啦
具體的代碼如下,但是驗簽部分因為對方是京東,還是省略的好,大家可以根據自己的應用場景腦補
using Aito.Entity; using Aito.ServBll.JDBll; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Security.Cryptography; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Web; using System.Web.Http; using System.Web.Http.Controllers; using System.Web.Http.Filters; using System.Web.Script.Serialization; using ZP.Comm;namespace Aito.JDService {/// <summary>/// 在action執行前后做額外處理/// </summary>public class TokenProjectorAttribute : ActionFilterAttribute{public string TokenName { get; set; } = "Token";public string UserTagName { get; set; } = "UserTag";public string ArgumentsName { get; set; } = "RequestData";public string UserInfoName { get; set; } = "UserInfo";/// <summary>/// 在action執行之前驗證Token的合法性/// </summary>/// <param name="actionContext"></param>//public override void OnActionExecuting(HttpActionContext actionContext)public override Task OnActionExecutingAsync(HttpActionContext actionContext, CancellationToken cancellationToken){string token = "",tag="";CookieHeaderValue cookieToken = actionContext.Request.Headers.GetCookies(TokenName).FirstOrDefault();//從cookie中取出Tokenif (cookieToken == null)ThrowException(new JDServiceModel<string>() { Success = false, Code = -4001, Msg = "Token不能為空!", Data = "" });CookieHeaderValue cookieTag = actionContext.Request.Headers.GetCookies(UserTagName).FirstOrDefault();//從cookie中取出用戶標識if (cookieTag == null)ThrowException(new JDServiceModel<string>() { Success = false, Code = -4002, Msg = "用戶標識能為空!", Data = "" });token = cookieToken[TokenName].Value;//獲取到Tokentag = cookieToken[UserTagName].Value;//獲取到Tagint userID = -1;if(!int.TryParse(tag,out userID))ThrowException(new JDServiceModel<string>() { Success = false, Code = -4007, Msg = "用戶標識錯誤!", Data = "" });//驗證用戶合法性if (CommOpreJD.UserInfos.Where(u => u.UserID == userID).Count() < 1)ThrowException(new JDServiceModel<string>() { Success = false, Code = -4003, Msg = "用戶標識不合法!", Data = "" });//驗證數據的合法性if (!actionContext.ActionArguments.ContainsKey(ArgumentsName))ThrowException(new JDServiceModel<string>() { Success = false, Code = -4004, Msg = "請求參數不能為空!", Data = "" });string msg = "68行:請求Token:"+ token + "\n";msg+= "UserTag:" + tag + "\n";ErrHandler.WriteServerInfo(msg);//====================================================參數驗證var modelState = actionContext.ModelState;if (!modelState.IsValid){string error = string.Empty;foreach (var key in modelState.Keys){var state = modelState[key];if (state.Errors.Any()){error = state.Errors.First().ErrorMessage;if (String.IsNullOrEmpty(error)){error = "請求參數缺失或錯誤";}break;}}ThrowException(new JDServiceModel<string>() { Success = false, Code = -4008, Msg = "參數驗證失敗-" + error, Data = "" });}//====================================================參數驗證//校驗數據是否被篡改UserInfoServModel model = CommOpreJD.UserInfos.Where(u => u.UserID == userID).ToList()[0];#region 驗簽string responseJson = string.Empty;try{Stream stream = HttpContext.Current.Request.InputStream;StreamReader streamReader = new StreamReader(stream);responseJson = streamReader.ReadToEnd();//responseJson = JsonSort.SortJson(JToken.Parse(responseJson), null); }catch { }//var jsonSetting = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore };//var dataOld = JsonConvert.SerializeObject(actionContext.ActionArguments[ArgumentsName], Formatting.Indented, jsonSetting);//這兩行用來去除為空的參數//dataOld = JsonSort.SortJson(JToken.Parse(dataOld), null);此處為驗簽操作,目的是計算出 base64Token2 msg = "108行:用戶數據:" + data + "\n";msg += "用戶Token:" + token + "\n";msg += "校驗Token:" + base64Token2 + "\n";ErrHandler.WriteServerInfo(msg);if (base64Token2 != token)ThrowException(new JDServiceModel<string>() { Success = false, Code = -4005, Msg = "數據被篡改!", Data = "" });#endregion actionContext.Request.Properties[UserInfoName] = model;return base.OnActionExecutingAsync(actionContext, cancellationToken);}private void ThrowException(JDServiceModel<string> exp){var response = new HttpResponseMessage();response.Content = new StringContent(new JavaScriptSerializer().Serialize(exp));response.StatusCode = HttpStatusCode.Conflict;throw new HttpResponseException(response);}/// <summary>/// 在Action方法調用后,result方法調用前執行,使用場景:異常處理。/// </summary>/// <param name="actionExecutedContext"></param>//public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)public override Task OnActionExecutedAsync(HttpActionExecutedContext actionExecutedContext, CancellationToken cancellationToken){string resultData = GetResponseValues(actionExecutedContext);object user = null;if (!actionExecutedContext.Request.Properties.TryGetValue(UserInfoName, out user))ThrowException(new JDServiceModel<string>() { Success = false, Code = -4006, Msg = "數據返回時,用戶信息丟失!", Data = "" });//HttpStatusCode StatusCode = actionExecutedContext.ActionContext.Response.StatusCode;// 取得由 API 返回的狀態代碼JDServiceModel<string> result = actionExecutedContext.ActionContext.Response.Content.ReadAsAsync<JDServiceModel<string>>().Result;// 取得由 API 返回的資料result.Success = actionExecutedContext.ActionContext.Response.IsSuccessStatusCode; //請求是否成功此處為驗簽操作,目的是計算出 base64Token2 result.Token = base64Token2;// 重新封裝回傳格式actionExecutedContext.Response = ToJson(result);string msg = "返回數據:" + result.Data + "\n";msg += "返回Token:" + base64Token2 + "\n";ErrHandler.WriteServerInfo(msg);ErrHandler.WriteServerInfo(msg);return base.OnActionExecutedAsync(actionExecutedContext, cancellationToken);//base.OnActionExecuted(actionExecutedContext); }/// <summary>/// 讀取action返回的result/// </summary>/// <param name="actionExecutedContext"></param>/// <returns></returns>private string GetResponseValues(HttpActionExecutedContext actionExecutedContext){Stream stream = actionExecutedContext.Response.Content.ReadAsStreamAsync().Result;Encoding encoding = Encoding.UTF8;/*這個StreamReader不能關閉,也不能dispose, 關了就傻逼了因為你關掉后,后面的管道 或攔截器就沒辦法讀取了*/var reader = new StreamReader(stream, encoding);string result = reader.ReadToEnd();/*這里也要注意: stream.Position = 0; 當你讀取完之后必須把stream的位置設為開始因為request和response讀取完以后Position到最后一個位置,交給下一個方法處理的時候就會讀不到內容了。*/stream.Position = 0;return result;}private HttpResponseMessage ToJson(Object obj){String str;if (obj is String || obj is Char)//如果是字符串或字符直接返回 {str = obj.ToString();}else//否則序列為json字串 {JavaScriptSerializer serializer = new JavaScriptSerializer();str = serializer.Serialize(obj);}HttpResponseMessage result = new HttpResponseMessage { Content = new StringContent(str, Encoding.GetEncoding("UTF-8"), "application/json") };return result;}} }
?
參考出處:
https://www.cnblogs.com/hnsongbiao/p/7039666.html ?
https://www.cnblogs.com/goodlucklzq/p/4481956.html
?