这两天 一直和京东对接接口,我们用.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 = -, Msg = "Token不能为空!", Data = "" }); CookieHeaderValue cookieTag = actionContext.Request.Headers.GetCookies(UserTagName).FirstOrDefault();//从cookie中取出用户标识
if (cookieTag == null)
ThrowException(new JDServiceModel<string>() { Success = false, Code = -, Msg = "用户标识能为空!", Data = "" }); token = cookieToken[TokenName].Value;//获取到Token
tag = cookieToken[UserTagName].Value;//获取到Tag int userID = -;
if(!int.TryParse(tag,out userID))
ThrowException(new JDServiceModel<string>() { Success = false, Code = -, Msg = "用户标识错误!", Data = "" }); //验证用户合法性
if (CommOpreJD.UserInfos.Where(u => u.UserID == userID).Count() < )
ThrowException(new JDServiceModel<string>() { Success = false, Code = -, Msg = "用户标识不合法!", Data = "" }); //验证数据的合法性
if (!actionContext.ActionArguments.ContainsKey(ArgumentsName))
ThrowException(new JDServiceModel<string>() { Success = false, Code = -, 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 = -, Msg = "参数验证失败-" + error, Data = "" });
}
//====================================================参数验证
//校验数据是否被篡改
UserInfoServModel model = CommOpreJD.UserInfos.Where(u => u.UserID == userID).ToList()[]; #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 = -, 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 = -, 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 = ;
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

.net api 和java平台对接技术总结的更多相关文章

  1. JAVA平台AOP技术研究

    3.1 Java平台AOP技术概览 3.1.1 AOP技术在Java平台中的应用 AOP在实验室应用和商业应用上,Java平台始终走在前面.从最初也是目前最成熟的AOP工具--AspectJ,到目前已 ...

  2. 1、Java语言概述与开发环境——Java特性和技术体系平台

    一.Java语言的主要特性 1.Java语言是易学的: Java语言的语法与C语言和C++语言很接近,使得大多数的程序员很容易学习和使用Java. 2.Java语言是强制面向对象的: Java语言提供 ...

  3. 基于java平台的常用资源整理

    这里整理了基于java平台的常用资源 翻译 from :akullpp | awesome-java 大家一起学习,共同进步. 如果大家觉得有用,就mark一下,赞一下,或评论一下,让更多的人知道.t ...

  4. 这里整理了基于java平台的常用资源

    这里整理了基于java平台的常用资源 翻译 from :akullpp | awesome-java 大家一起学习,共同进步. 如果大家觉得有用,就mark一下,赞一下,或评论一下,让更多的人知道.t ...

  5. java集群技术(转)

    来源:http://blog.csdn.net/cdh1213/article/details/21443239 序言 越来越多的关键应用运行在J2EE(Java2, Enterprise Editi ...

  6. Java 远程通讯技术及原理分析

    在分布式服务框架中,一个最基础的问题就是远程服务是怎么通讯的,在Java领域中有很多可实现远程通讯的技术,例如:RMI.MINA.ESB.Burlap.Hessian.SOAP.EJB和JMS等,这些 ...

  7. 2015第44周五Java集群技术(转)

    从http://blog.csdn.net/cdh1213/article/details/21443239上看到这篇文章,感觉很不错,找好久没找到中文出处,最早看是从http://www.these ...

  8. 【IBM】Merlin 给 Java 平台带来了非阻塞 I/O

    Merlin 给 Java 平台带来了非阻塞 I/O 新增的功能大幅降低了线程开销 Java 技术平台早就应该提供非阻塞 I/O 机制了.幸运的是,Merlin(JDK 1.4)有一根几乎在各个场合都 ...

  9. 深入了解java集群技术

    原文源自:http://blog.csdn.net/happyangelling/article/details/6413584 序言 越来越多的关键应用运行在J2EE(Java 2, Enterpr ...

随机推荐

  1. 【Python】利用豆瓣短评数据生成词云

    在之前的文章中,我们获得了豆瓣爬取的短评内容,汇总到了一个文件中,但是,没有被利用起来的数据是没有意义的. 前文提到,有一篇微信推文的关于词云制作的一个实践记录,准备照此试验一下. 思路分析 读文件 ...

  2. centos 7 中如何提取IP地址

    ifconfig |grep -Eo "(([1-9)?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|1[0-9]{2}|2[0-4][0 ...

  3. Hive-生成一个大文件(小文件合并)

    set hive.execution.engine=mr; --在 map-reduce 作业结束时合并小文件.如启用,将创建 map-only 作业以合并目标表/分区中的文件. set hive.m ...

  4. word定义多级列表

    1.单击开始选项卡里的多级列表按钮,在下拉列表中选择定义新的多级列表 2.先设置第一级编号,选择阿拉伯数字1,2,3,...,并在自动编号“1”的左右分别输入“第”“章”,级别链接到样式选择标题一 3 ...

  5. iview报错[Vue warn]: Error in render: "TypeError: ctx.injections.tableRoot.$scopedSlots[ctx.props.column.slot] is not a function"

    原因是我使用了iview的<Table>组件,我给Table组件的columns中定义了4个含有slot的列,但是实际在<Table>中只使用了其中3个,导致的报错. 也就是说 ...

  6. 安装CentOS7虚拟机

    基础环境 Windows 10 VMware Workstation 1.下载CentOS7镜像 https://www.centos.org/download/ 此次安装使用的版本为: CentOS ...

  7. phpstudy添加PHP

    想在phpstudy2018里面增加一个php版本,操作如下: 一.下载php-7.2.19-ts文件,解压缩,放在相应的目录下: 二.修改Apache的配置文件1.修改httpd.conf 配置,D ...

  8. HNUSTOJ-1638 遍地桔子(贪心)

    1638: 遍地桔子 时间限制: 1 Sec  内存限制: 128 MB提交: 711  解决: 134[提交][状态][讨论版] 题目描述 为了实验室的发展,队长决定在实验室外面的空地种桔子树.空地 ...

  9. P2510 [HAOI2008]下落的圆盘

    传送门 首先考虑两个圆覆盖的情况,我们可以求出圆心与交点连线 $A$ 的极角 具体就是求出两圆心连线 $B$ 极角加上余弦定理加反余弦求出 $A,B$ 之间夹角 ,然后覆盖了多少就可以得出 但是多个圆 ...

  10. 自动清理ES索引脚本

    #/bin/bash #指定日期(3个月前) DATA=`date -d "3 month ago" +%Y.%m.%d` #当前日期 time=`date` #删除3个月前的日志 ...