• .net api 和java平台对接技术总结


    这两天 一直和京东对接接口,我们用.net api 提供接口,对方用java调用,本来没什么问题,但是对方对数据安全要求特别严,要验签,于是噩梦开始了。

    1、在传输的时候,约定传输格式:

    HttpWebRequest request = (HttpWebRequest)WebRequest.Create(Url);//+ "?RequestData="+ param
                request.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中取出Token
                if (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;//获取到Token
                tag = cookieToken[UserTagName].Value;//获取到Tag
    
                int 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 + "
    ";
                msg+= "UserTag:" + tag + "
    ";
                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 + "
    ";
                msg += "用户Token:" + token + "
    ";
                msg += "校验Token:" + base64Token2 + "
    ";
                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 + "
    ";
                msg += "返回Token:" + base64Token2 + "
    ";
                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

    将偷懒进行到极致,是一个程序员的基本素养
  • 相关阅读:
    Educational Codeforces Round 61
    Codeforces Round #548 (Div. 2)
    Codeforces Round #553 (Div. 2)
    spring mvc接口跨域访问
    json解析;百度统计API返回data解析
    HTML img标签使用base64展示图片
    My Sql 查询连续天数数据
    js 获取url地址栏拼接参数
    在jsp页面获取绝对路径
    java web项目配置Tomcat访问项目外文件
  • 原文地址:https://www.cnblogs.com/bamboo-zhang/p/9177087.html
Copyright © 2020-2023  润新知