Java提供接口给第三方使用,校验保证接口的安全性

  • 前言
    • 接口Controller
    • 接口幂等性校验
    • 实现类ServiceImpl
    • 第三方调用接口Api实现类
    • 生成签名工具类
    • HttpCilent工具类

前言

相信有很多小伙伴,在日常的开发中都有遇到过需要调用第三方接口的需求吧,但是自己有没有写过接口提供给第三方使用呢,常规的都是我们调用别人的接口,但是自己需要开发接口提供给第三方使用的场景应该不是很多,很多小伙伴可能会想不就开发一个接口对外开放嘛岂不是很简单,但是在开发接口对外开放,我们需要考虑一个问题,没有限制条件,那岂不是太不安全了,谁都可以调我这个接口了啊。
所以接下来的就是我们需要考虑的问题了,在开发接口的时候就要考虑到安全性的问题,那么应该如何去解决这个问题呢?提供接口给第三方使用的时候需要加上校验保证接口的安全性。
下面是我写的一个例子希望对大家有帮助。

接口Controller

在写接口前一定要签名做签名校验,我的签名方式做了特殊处理,因为接口是对外开放的,这个是为了避免恶意调用接口做的处理,叫做签名的混淆值,这个签名混淆值的作用是就算别人知道了接口,并且知道签名方式也不能被攻击,是为了避免被恶意篡改数据,签名混淆值就是一组特定加密后的数据。

 @PostMapping("refundDeductionPoints")public Result<SysIntegralStatement> refundDeductionPoints (@RequestParam Map<String,String> params){Result<SysIntegralStatement> result = new Result<SysIntegralStatement>();try {//签名校验String msgDigest = params.get("msgDigest");//签名String msgData = params.get("msgData");String timeStamp = params.get("timeStamp");String secret = params.get("secret");//  秘钥String sign = SignUtil.sign(msgData+"wf8la1tw7p9o2xz",timeStamp);//wf8la1tw7p9o2xz为签名混淆值if (!msgDigest.equals(sign)) {return result.setCode(1006).setReason("数字签名无效");}if (Common.isEmpty(secret)) {//先签名后幂等校验return result.setCode(1001).setReason("密钥不能为空");}/*** 幂等校验* 1.同一个用户操作同一个退货单一分钟内操作该单据视为重复操作(此秘钥已通过特殊处理)*/String value = redistempalte.opsForValue().get(secret);if (Common.isNotEmpty(value)) {   logger.error("重复请求 secret={}",value);return result.setCode(1007).setReason("重复请求");    }redistempalte.opsForValue().set(secret, "1",60,TimeUnit.SECONDS);//设置缓存一分钟return service.refundDeductionPoints(params);} catch (Exception e) {logger.error("添加积分流水异常", e);return result.setCode(ErrorCodes.BUSINESS_ERROR).setReason("生成积分流水失败");}}

接口幂等性校验

此接口做幂等性校验,幂等性校验常见解决方案有很多,可以自行根据实际情况选择,
说到幂等首先要先了解什么是幂等
概念:
幂等(idempotent、idempotence)是一个数学与计算机学概念,常见于抽象代数中。

在编程中.一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。幂等函数,或幂等方法,是指可以使用相同参数重复执行,并能获得相同结果的函数。

这些函数不会影响系统状态,也不用担心重复执行会对系统造成改变。例如,“getUsername()和setTrue()”函数就是一个幂等函数.

幂等性, 通俗的说就是一个接口, 多次发起同一个请求, 必须保证操作只能执行一次,比如:

订单接口, 不能多次创建订单
支付接口, 重复支付同一笔订单只能扣一次钱
支付宝回调接口, 可能会多次回调, 必须处理重复回调
普通表单提交接口, 因为网络超时等原因多次点击提交, 只能成功一次
等等
解决方案常见的几种方式
唯一索引 – 防止新增脏数据

token机制 – 防止页面重复提交

悲观锁 – 获取数据的时候加锁(锁表或锁行)

乐观锁 – 基于版本号version实现, 在更新数据那一刻校验数据

分布式锁 – redis(jedis、redisson)或zookeeper实现

状态机 – 状态变更, 更新数据时判断状态

如果有小伙伴不理解什么是幂等可以看看官方是解释

实现类ServiceImpl

 @Transactional@Overridepublic Result<SysIntegralStatement> refundDeductionPoints(Map<String, String> params) {String msgData = params.get("msgData");ParamIntegral entity = new Gson().fromJson(msgData, ParamIntegral.class);if (Common.isNull(entity)) {return new Result<SysIntegralStatement>().setCode(ErrorCodes.INNER_ERROR).setReason("请求参数不能为空");}if (Common.isEmpty(entity.getBitems())) {return new Result<SysIntegralStatement>().setCode(ErrorCodes.INNER_ERROR).setReason("请求参数不能为空");}int row = 0;for (ParamIntegral bitem : entity.getBitems()) {if (Common.isEmpty(bitem.getDdh())) {return new Result<SysIntegralStatement>().setCode(ErrorCodes.INNER_ERROR).setReason("订单号为必传参数");}if (null == bitem.getJfz()) {return new Result<SysIntegralStatement>().setCode(ErrorCodes.INNER_ERROR).setReason("扣减积分不能为空");}List<MallOrderInfo> orderInfo = mallOrderInfoMapper.selectByDdh(bitem.getDdh());if (orderInfo == null) {return new Result<SysIntegralStatement>().setCode(ErrorCodes.INNER_ERROR).setReason("订单号为" + bitem.getDdh() + "没有此订单请联系客服核对信息。");}if (orderInfo != null && orderInfo.size() > 1) {return new Result<SysIntegralStatement>().setCode(ErrorCodes.INNER_ERROR).setReason("订单号为" + bitem.getDdh() + "有多个相同订单请联系客服核对信息。");}if (!"E".equals(orderInfo.get(0).getDdzt())) {return new Result<SysIntegralStatement>().setCode(ErrorCodes.INNER_ERROR).setReason("订单号为" + bitem.getDdh() + "未确认收货还没产生积分不允许退货。");}SysIntegral integral = Common.first(integralMapper.selectByMdbm(orderInfo.get(0).getMdbm()));if (integral == null) {return new Result<SysIntegralStatement>().setCode(ErrorCodes.INNER_ERROR).setReason("门店编码为" + orderInfo.get(0).getMdbm() + "积分汇总没有找到此门店,请联系客服核实");}BigDecimal kyjf = BigDecimal.ZERO;if (entity.getReturnGoods() == true) {// 可用积分小于扣减积分不够扣ERP使用前抵扣if (bitem.getJfz().compareTo(integral.getKyjf()) == 1) {kyjf = BigDecimal.ZERO;                 } else {// 可用积分 = 当前可用积分-扣减积分kyjf = Common.nvl(integral.getKyjf(), BigDecimal.ZERO).subtract(bitem.getJfz());}} else {// 可用积分 = 当前可用积分+退还积分kyjf = Common.nvl(integral.getKyjf(), BigDecimal.ZERO).add(bitem.getJfz());             }       // 更新积分汇总SysIntegral dataMap = new SysIntegral();dataMap.setIntegralId(integral.getIntegralId());dataMap.setMdbm(integral.getMdbm());dataMap.setKyjf(kyjf);dataMap.setUpdateTime(new Date());dataMap.setUpdateUser(entity.getUserName());dataMap.setUpdateUserid(entity.getUserId().intValue());       row = integralMapper.updateByPrimaryKeySelective(dataMap);if (row == 0) {TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();return new Result<SysIntegralStatement>().setCode(ErrorCodes.INNER_ERROR).setReason("更新积分失败");}//推送到ERP门店信息BdMdxxH mdxx =new BdMdxxH();mdxx.setMdbm(integral.getMdbm());mdxx.setMdjf(kyjf);com.lkfs.cw.common.Result<BdMdxxH> bdMdxxh = dataBaseServiceApi.updateStorePoints(mdxx);if (!bdMdxxh.isComplete()) {TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();return new Result<SysIntegralStatement>().setCode(bdMdxxh.getCode()).setReason(bdMdxxh.getReason());}      SysIntegralStatement statement = new SysIntegralStatement();if (entity.getReturnGoods() == true) {statement.setJfz(bitem.getJfz().negate());// 消费的积分值if (bitem.getJfz().compareTo(integral.getKyjf()) == 1) {// 可用积分小于扣减积分不够扣ERP使用前抵扣statement.setTzhjfz(BigDecimal.ZERO);// 调整后积分值} else {statement.setTzhjfz(Common.nvl(integral.getKyjf(), BigDecimal.ZERO).subtract(bitem.getJfz()));// 调整后积分值}statement.setJfxflx("E");// 积分支出statement.setXxsm("退货扣减积分(订单号为:" + bitem.getDdh() + "," + "退货单号为:" + entity.getDjh() + ")" + "已扣除:"+ bitem.getJfz().negate() + ":积分");} else {// 取消退货statement.setJfxflx("I");// 积分收入statement.setJfz(bitem.getJfz());// 取消退货把积分赠送回来statement.setTzhjfz(Common.nvl(integral.getKyjf(), BigDecimal.ZERO).add(bitem.getJfz()));// 调整后积分值statement.setXxsm("取消退货(订单号为:" + bitem.getDdh() + "," + "退货单号为:" + entity.getDjh() + ")" + "已退还:"+ bitem.getJfz() + ":积分");}statement.setIntegralId(integral.getIntegralId());// 该门店积分编码statement.setTzqjfz(integral.getKyjf());// 调整前积分值statement.setDdh(entity.getDdh());statement.setCreateTime(new Date());// 流水生成时间statement.setCreateUser(entity.getUserName());statement.setCreateUserid(entity.getUserId().intValue());statement.setJftz("T");// 积分扣减为Tstatement.setZt("Y");// 状态 Y:有效row = mapper.insert(statement);if (row == 0) {TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();return new Result<SysIntegralStatement>().setCode(ErrorCodes.INNER_ERROR).setReason("插入积分流水失败");}}return new Result<SysIntegralStatement>().setCode(ErrorCodes.SUCCESS).setReason("操作成功");}

第三方调用接口Api实现类

模拟第三方合作方调用接口

//此方式以上已写了封装信息就不一一展示了,可以根据实际情况自行操作private void pushIntegral(Long djlsh) {FiSjysjsH fiSjysjsh = mapper.selectByPrimaryKey(djlsh);             //订单退货调用某某退货扣减积分接口List<FiSjysjsB> sjysjsbList = bmapper.selectByKhddh(djlsh);if (sjysjsbList != null && sjysjsbList.size() > 0) {List<ParamIntegral> list = new ArrayList<ParamIntegral>();for (FiSjysjsB bitem : sjysjsbList) {ParamIntegral temp = new ParamIntegral();temp.setDdh(bitem.getKhddh());temp.setJfz(bitem.getJfz());            list.add(temp);}ParamIntegral param = new ParamIntegral();param.setBitems(list);param.setDjh(fiSjysjsh.getDjh());param.setUserId(AppRealm.getCurrentUser().getUserId());param.setUserName(AppRealm.getCurrentUser().getUserName());if (new Short("1").equals(fiSjysjsh.getLocked())) {param.setReturnGoods(true);}else {param.setReturnGoods(false);}String msgData = new Gson().toJson(param).toString();Map<String, String> params = new HashMap<String, String>();String timeStamp = String.valueOf(System.currentTimeMillis());//时间戳params.put("timeStamp", timeStamp);params.put("msgData", msgData);    params.put("msgDigest", SignUtil.sign(msgData+"wf8la1tw7p9o2xz", timeStamp));//生成签名第二个值暂定(wf8la1tw7p9o2xz签名混淆值)params.put("secret",IDEMPOTENT_SECRET_PREFIX + fiSjysjsh.getDjh() + AppRealm.getCurrentUser().getUserId()+param.getReturnGoods() );//自定义密钥 做幂等校验String result = HttpCilent.post(B2B_URL, params); //发送http post请求B2bIntegralResponse res = new Gson().fromJson(result, B2bIntegralResponse.class);if (null == res) {throw new RuntimeException("调用积分接口系统异常");}if (res.getCode() != 0) {//接口返回失败异常代码提示throw new RuntimeException("调用积分接口发生异常,异常代码为:"+res.getCode()+"异常信息为:"+res.getReason());   }}}

生成签名工具类

package com.cy.xgsm.util;import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;/*** * @author Dylan**/
public class SignUtil {private static final Logger log = LoggerFactory.getLogger(SignUtil.class);/*** */public static String sign(String str, String secret) {StringBuilder enValue = new StringBuilder();enValue.append(secret);enValue.append(str);enValue.append(secret);return encryptByMD5(enValue.toString());}private static String encryptByMD5(String data) {String re_md5 = new String();try {MessageDigest md = MessageDigest.getInstance("MD5");md.update(data.getBytes());byte b[] = md.digest();int i;StringBuffer buf = new StringBuffer();for (int offset = 0; offset < b.length; offset++) {i = b[offset];if (i < 0)i += 256;if (i < 16)buf.append("0");buf.append(Integer.toHexString(i));}re_md5 = buf.toString();} catch (NoSuchAlgorithmException e) {e.printStackTrace();}return re_md5.toUpperCase();}public static String compare(String jsonStr,String secret ){JsonParser jsonParser = new JsonParser();JsonObject jsonObject = jsonParser.parse(jsonStr).getAsJsonObject();String sign1 = "null";JsonElement signElement = jsonObject.remove("sign");if( signElement != null ){sign1 = signElement.getAsString();}log.info("sign1: " + sign1);StringBuilder enValue = new StringBuilder();enValue.append(secret);enValue.append(jsonObject.toString());enValue.append(secret);String sign2 = encryptByMD5(enValue.toString());jsonObject.addProperty("sign", sign2);return jsonObject.toString();}}

HttpCilent工具类

这个工具类在我之前的文章也有但是没有把这个方式的放上去,如果有需要用到可直接把一下代码复制到这个Http工具类 最后即可直接使用。

   /*** 发送post请求* @param url 目的url* @param parameters 参数* @return*/public static String post(String url, Map<String, String> parameters) {String result = "";// 返回的结果BufferedReader in = null;// 读取响应输入流PrintWriter out = null;StringBuffer sb = new StringBuffer();// 处理请求参数String params = "";// 编码之后的参数try {// 编码请求参数if (parameters.size() == 1) {for (String name : parameters.keySet()) {sb.append(name).append("=").append(java.net.URLEncoder.encode(parameters.get(name), "UTF-8"));}params = sb.toString();} else {for (String name : parameters.keySet()) {sb.append(name).append("=").append(java.net.URLEncoder.encode(parameters.get(name), "UTF-8")).append("&");}String temp_params = sb.toString();params = temp_params.substring(0, temp_params.length() - 1);}// 创建URL对象java.net.URL connURL = new java.net.URL(url);// 打开URL连接java.net.HttpURLConnection httpConn = (java.net.HttpURLConnection) connURL.openConnection();// 设置通用属性httpConn.setRequestProperty("Accept", "*/*");httpConn.setRequestProperty("Connection", "Keep-Alive");httpConn.setRequestProperty("content-type", "application/x-www-form-urlencoded");httpConn.setRequestProperty("User-Agent","Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1)");// 设置POST方式httpConn.setDoInput(true);httpConn.setDoOutput(true);// 获取HttpURLConnection对象对应的输出流out = new PrintWriter(httpConn.getOutputStream());// 发送请求参数out.write(params);// flush输出流的缓冲out.flush();// 定义BufferedReader输入流来读取URL的响应,设置编码方式in = new BufferedReader(new InputStreamReader(httpConn.getInputStream(), "UTF-8"));String line;// 读取返回的内容while ((line = in.readLine()) != null) {result += line;}} catch (Exception e) {e.printStackTrace();} finally {try {if (out != null) {out.close();}if (in != null) {in.close();}} catch (IOException ex) {ex.printStackTrace();}}return result;}

此文章仅供参考,如果能帮助到大家,可以给博主支持一下。

Java 提供给第三方使用接口方法相关推荐

  1. [Java基础]-- 调用第三方支付接口

    1.调用第三方支付接口参考 http://blog.csdn.net/saindy5828/article/details/6800331点击打开链接 2.举例 一般情况下,各个支付平台都会给我们一个 ...

  2. 线程的生命周期以及java提供的方法

    一.生命周期的五种状态 1.新建(new Thread) 当创建Thread类的一个实例(对象)时,此线程进入新建状态(未被启动). 例如:Thread  t1=new Thread(); 2.就绪( ...

  3. 【Java】 第六章 接口、lambda 表达式与内部类 Java核心技术卷1基础知识原书第10版 读书笔记

    第六章 接口.lambda表达式与内部类 6. 接口.lambda 表达式与内部类 6.1 接口 6.1.1 接口概念 6.1.2 接口的特性 6.1.3 接口与抽象类 6.1.4 静态方法 6.1. ...

  4. Java 调用第三方接口方法

    Java 调用第三方接口方法 一. 通过JDK网络类Java.net.HttpURLConnection 1.java.net包下的原生java api提供的http请求 使用步骤: 1.通过统一资源 ...

  5. java静态接口方法使用_使用静态方法对接口进行Java编程

    java静态接口方法使用 In this quick tutorial, we would understand how static methods on interfaces helps us d ...

  6. java提供文件下载的方法

    java提供文件下载的方法     String root  =  getServletContext().getRealPath( " / " );     String pat ...

  7. command对象提供的3个execute方法是_前阿里P9的Java面试重点3:多线程

    1. 并行和并发有什么区别? 并行:多个处理器或多核处理器同时处理多个任务. 并发:多个任务在同一个 CPU 核上,按细分的时间片轮流(交替)执行,从逻辑上来看那些任务是同时执行. 如下图: 并发 = ...

  8. Java 中类的比较与排序方法(应用Comparable接口与Comparator接口)通俗易懂

    引言 在平时写Java的程序的时候,如果要进行一些基本类型的变量的比较,可以很方便得调用Math.max().Math.min()等方法,如果要对数组或者列表进行排序,也可以用Arrays.sort( ...

  9. java学习笔记20(Arraylist复习,Collection接口方法,迭代器,增强型for循环)

    集合:集合是Java提供的一种容器,可以用来存储多个数据: 集合与数组的区别:集合的长度是可变的,数组的长度是固定的 集合中存储的数据必须是引用类型数据: ArrayList回顾: public cl ...

最新文章

  1. 图表复现|PRD地下水微生物群落的多样性分析文献
  2. MyBatis——Java API
  3. linux 杀死t状态进程,Linux查杀stopped进程
  4. 392. Is Subsequence 判断子序列
  5. java ftp 中文上传_java实现ftp文件上传下载,解决慢,中文乱码,多个文件下载等问题...
  6. Redis使用不当导致应用卡死
  7. Div+CSS布局入门教程(四) 页面顶部制作之二
  8. shiro 同时实现url和按钮的拦截_Shiro是如何拦截未登录请求的(一)
  9. 如何减少返工工作量?
  10. iOS推送机制下AppDelegate中代理函数执行顺序
  11. 使用ILSpy查看dll里的部分代码
  12. Java 操作Word书签:用文本、图片、表格替换书签
  13. 学号密码错误的计算机流程图,自学考试管理信息系统练习题及答案
  14. 【MATLAB】三角函数
  15. 新编计算机基础教程考试知识点,新编计算机基础教程
  16. 官网---2019年上半年软考报名时间
  17. 如何4步绘制出高水平的气泡图?
  18. C++ 模版类和模板函数介绍及使用
  19. 单相全桥逆变原理及仿真实验
  20. TimX_Ch1与TimX_Ch1N的差异

热门文章

  1. OpenCV——绘制等高线
  2. hysVideoQC v0.0.2.002版本发布
  3. php怎么读取word文档
  4. Android开机画面定制
  5. 51单片机I/O端口的结构和工作原理
  6. Linux之更改配置文件永久修改IP地址
  7. 【FLink】Assigned key must not be null
  8. android与mysql连接不上去_安卓连接不上mysql怎么办
  9. 【分享】成功将Thinkpad E40 0578M59更换无线网卡AR9280
  10. [附源码]Python计算机毕业设计大学生校园社团管理系统