如何设计一个安全的对外接口

博主之前做过恒丰银行代收付系统(相当于支付接口),包括现在的oltpapi交易接口和虚拟业务的对外提供数据接口。总之,当你做了很多项目写了很多代码的时候,就需要回过头来,多总结总结,这样你会看到更多之前写代码的时候看不到的东西,也能更明白为什么要这样做。

做接口需要考虑的问题

什么是接口

接口无非就是客户端请求你的接口地址,并传入一堆该接口定义好的参数,通过接口自身的逻辑处理,返回接口约定好的数据以及相应的数据格式。

接口怎么开发

接口由于本身的性质,由于和合作方对接数据,所以有以下几点需要在开发的时候注意:

1.定义接口入参:写好接口文档

2.定义接口返回数据类型:一般都需要封装成一定格式,确定返回json还是xml报文等

d2c584f78fa5fdeafebd7ace657baca9.png

见如下返回数据定义格式:

package com.caiex.vb.model;
 
import java.io.Serializable;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlType;
 
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "Result", propOrder = { "resultCode", "resultMsg" })
public class Result implements Serializable {
 private static final long serialVersionUID = 10L;
 protected int resultCode;
 protected String resultMsg;
 
 public int getResultCode() {
  return this.resultCode;
 }
 
 public void setResultCode(int value) {
  this.resultCode = value;
 }
 
 public String getResultMsg() {
  return this.resultMsg;
 }
 
 public void setResultMsg(String value) {
  this.resultMsg = value;
 }
}
package com.caiex.vb.model;
 
import java.io.Serializable;
 
public class Response implements Serializable {
 
 private static final long serialVersionUID = 2360867989280235575L;
 
 private Result result;
 
 private Object data;
 
 public Result getResult() {
  if (this.result == null) {
   this.result = new Result();
  }
  return result;
 }
 
 public void setResult(Result result) {
  this.result = result;
 }
 
 public Object getData() {
  return data;
 }
 
 public void setData(Object data) {
  this.data = data;
 }
 
}

3.确定访问接口的方式,get or post等等,可以根据restful接口定义规则RESTful API:RESTful API

4.定义一套全局统一并通用的返回码,以帮助排查问题;

public static int NO_AGENT_RATE = 1119;  //未找到兑换率
 
 public static int SCHEME_COMMIT_FAIL = 4000;  //方案提交失败
 
 public static int SCHEME_CONFIRMATION = 4001;  //方案确认中
 
 public static int SCHEME_NOT_EXIST = 4002;  //方案不存在
 
 public static int SCHEME_CANCEL= 4005;  //方案不存在
 
 //。。。。

5.统一的异常处理:应该每个系统都需要一套统一的异常处理

package com.caiex.vb.interceptor;
 
import javax.servlet.http.HttpServletRequest;
 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
 
import com.caiex.vb.model.Response;
 
@ControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {
 
 private  Logger  logger = LoggerFactory.getLogger(this.getClass()); 
 
    /**
     * 所有异常报错
     * @param request
     * @param exception
     * @return
     * @throws Exception
     */
    @ExceptionHandler(value=Exception.class)  
    public Response allExceptionHandler(HttpServletRequest request,  
            Exception exception) throws Exception  
    {  
     logger.error("拦截到异常:", exception);
        Response response = new Response();
        response.setData(null);
        response.getResult().setResultCode(9999);
        response.getResult().setResultMsg("系统繁忙");
        return response;  
    }  
 
}

6.拦截器链设置:合作方访问接口的时候,会根据你接口定义好的传参访问你的接口服务器,但是会存在接口参数类型错误或者格式不对,必传参数没传的问题,甚至一些恶意请求,都可以通过拦截器链进行前期拦截,避免造成接口服务的压力。

学习资料:Java进阶视频资源

还有很重要的一点,加签验签也可以在拦截器设置。继承WebMvcConfigurerAdapter实现springboot的拦截器链。实现HandlerInterceptor方法编写业务拦截器。

package com.caiex.vb.interceptor;
 
 
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
 
import com.alibaba.fastjson.JSON;
import com.caiex.redis.service.api.RedisApi;
import com.caiex.vb.model.Response;
import com.caiex.vb.utils.CaiexCheckUtils;
 
@Component
public class SignInterceptor extends BaseValidator implements HandlerInterceptor{
 
 private Logger logger = LogManager.getLogger(this.getClass());
 
 @Resource
 private RedisApi redisApi;
 
 
 public void afterCompletion(HttpServletRequest arg0,
   HttpServletResponse arg1, Object arg2, Exception arg3)
   throws Exception {
  // TODO Auto-generated method stub
  
 }
 
 public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1,
   Object arg2, ModelAndView arg3) throws Exception {
  // TODO Auto-generated method stub
  
 }
 
 public boolean preHandle(HttpServletRequest arg0, HttpServletResponse arg1,
   Object arg2) throws Exception {
  if(isTestIpAddr(arg0)){
   return true;
  }
  String securityKey = redisApi.hGet("securityKey", arg0.getParameter("agentid"));
  if(StringUtils.isEmpty(securityKey)){
   Response response = new Response();
   response.setData(null);
   response.getResult().setResultCode(8001);
   response.getResult().setResultMsg("缺少私钥, 渠道号:" + arg0.getParameter("agentid"));
   logger.error("缺少私钥, 渠道号:" + arg0.getParameter("agentid"));
   InterceptorResp.printJson(arg1, response);
   return false;
  }
  
  if(StringUtils.isEmpty(arg0.getParameter("sign")) || !arg0.getParameter("sign").equals(CaiexCheckUtils.getSign(arg0.getParameterMap(), securityKey))){
   Response response = new Response();
   response.setData(null);
   response.getResult().setResultCode(3203);
   response.getResult().setResultMsg("参数签名认证失败");
   logger.error("参数签名认证失败:" + JSON.toJSONString(arg0.getParameterMap()) + " securityKey = " + securityKey);
   InterceptorResp.printJson(arg1, response);
   return false;
  }else{
   return true;
  }
  
 }
 
}
package com.caiex.oltp.config;
 
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
 
import com.caiex.oltp.interceptor.APILimitRateValidator;
import com.caiex.oltp.interceptor.CommonValidator;
import com.caiex.oltp.interceptor.DDSAuthValidator;
import com.caiex.oltp.interceptor.QueryPriceParamsValidator;
import com.caiex.oltp.interceptor.TradeParamsValidator;
 
 
@EnableWebMvc
@Configuration
@ComponentScan
public class WebAppConfigurer extends WebMvcConfigurerAdapter {
 
   @Bean
   CommonValidator commonInterceptor() {
         return new CommonValidator();
     }
 
   @Bean
   DDSAuthValidator ddsAuthInterceptor() {
         return new DDSAuthValidator();
     }
 
   @Bean
   QueryPriceParamsValidator queryPriceParamsInterceptor() {
         return new QueryPriceParamsValidator();
     }
 
   @Bean
   TradeParamsValidator tradeParamsInterceptor() {
         return new TradeParamsValidator();
     }
   
  @Bean
   APILimitRateValidator aPILimitRateInterceptor() {
         return new APILimitRateValidator();
     }
 
 
     @Override
     public void addInterceptors(InterceptorRegistry registry) {
      
      //访问速率限制
      registry.addInterceptor(aPILimitRateInterceptor())
      .addPathPatterns("/*/*");
      //.addPathPatterns("/price/getPriceParam");
 
      //参数签名认证
         registry.addInterceptor(ddsAuthInterceptor())
         .addPathPatterns("/tradeState/*")
         .addPathPatterns("/recycle/*")
         .addPathPatterns("/matchInfo/*")
         .addPathPatterns("/price/tradeTicketParam");
         
         //公共参数检查
         registry.addInterceptor(commonInterceptor())
         .addPathPatterns("/price/tradeTicketParam")
         .addPathPatterns("/tradeState/*")
         .addPathPatterns("/recycle/*");
         
         //询价参数校验
         registry.addInterceptor(queryPriceParamsInterceptor())
         .addPathPatterns("/price/getPriceParam");
         
         //交易参数检查
         registry.addInterceptor(tradeParamsInterceptor())
         .addPathPatterns("/price/tradeTicketParam");
         
         super.addInterceptors(registry);
     }
}

7.token令牌和sign数字签名实现数据保密性。

创建令牌(Token)

为保证请求的合法性,我们提供第三方创建令牌接口,某些接口需要通过token验证消息的合法性,以免遭受非法攻击。

token过期时间目前暂时定为1天,由于考虑到合作方往往是分布式环境,多台机器都有可能申请token,为了降低合作方保证token一致性的难度,调用接口创建token成功以后一分钟以内,再次请求token返回的数据是一样的。

获取私钥

获取用于数字签名的私钥,第三方获取的私钥需妥善保存,并定期更新,私钥只参与数字签名,不作为参数传输。

数字签名方式:

参数签名;签名方式:所有值不为null的参数(不包括本参数)均参与数字签名,按照“参数名+参数值+私钥”的格式得到一个字符串,再将这个字符串MD5一次就是这个参数的值。(示例:h15adc39y9ba59abbe56e057e60f883g),所以需要先获取私钥。

验签方式:

将用户的所有非null参数放入定义好排序规则的TreeSet中进行排序,再用StringBuilder按照按照“参数名+参数值+私钥”的格式得到一个字符串(私钥从redis拿),再将这个字符串MD5一次就是这个参数的值。将这个值与用户传来的sign签名对比,相同则通过,否则不通过。

private String createToken(){
  String utk = "Msk!D*"+System.currentTimeMillis()+"UBR&FLP";
  logger.info("create token   --- "+Md5Util.md5(utk));
  return Md5Util.md5(utk);
 }

8.接口限流

有时候服务器压力真的太大,以防交易接口被挤死,就可以对一些其他不影响主要业务功能并且计算量大的接口做限流处理。RateLimit--使用guava来做接口限流,当接口超过指定的流量时,就不处理该接口的请求。详细可看RateLimit。也可参考其他限流框架。

9.协议加密,http升级成https;

为什么要升级呢,为了保证数据的安全性。当使用https访问时,数据从客户端到服务断,服务端到客户端都加密,即使黑客抓包也看不到传输内容。当然还有其他好处,这里不多讲。但这也是开发接口项目需要注意的一个问题。

如何提高接口的高并发和高可用

接口开发好了,接下来就讨论接口的可用性问题。首先我们要将高并发和高可用区分一下,毕竟高可用是在可用的情况,只是很慢或者效率不高。其实也可以归为一类问题,但是不重要啦,重要的是怎么提高你写的接口的访问速度和性能。

接口的高并发解决方案(其实没有唯一答案,业界针对不同业务也有很多不同的方法)

当访问一个接口获取数据时,发现返回很慢,或者总是超时,如果排除网络的原因,那就是接口服务器压力太大,处理不过来了。在世界杯期间,我们查看后台日志总是connection by reset和borker pipe和一些超时问题。

这时候,你可能遇到了高并发和高可用问题。但是,不管遇到什么问题,都不能臆断和乱改,你得需要找到慢的原因,才能对症下药,乱改可能会导致其他问题的出现。首先,解决高并发问题的三个方向是负载均衡,缓存和集群。

学习资料:Java进阶视频资源

负载均衡

我们使用的是阿里云服务器的负载均衡,后台分布式服务管理,我们运维小哥哥搭建了一套k8s,可以自由在k8s上扩展服务节点,各个服务结点也能随内存的使用自动漂移,不用多说,k8s真的很厉害,感兴趣的同学可以详细去学。那么问题来了,阿里云的负载均衡怎么对应到k8s的负载均衡呢?

这个涉及到了k8s的service暴露的一些特点,简单说就是k8s把所有集群的服务都通过指定的内部负载均衡,在指定的服务器上暴露,然后我们又把这几个服务器接在阿里云负载均衡下,这个涉及的细节和配置很多。当然,除nginx外,还有其他负载均衡解决方案,软件硬件都有,硬件如f5等。

阿里云的nginx负载均衡,我们使用的是加权轮询策略,其实轮询是最低效的方式;

这就是最基本的负载均衡实例,但这不足以满足实际需求;目前Nginx服务器的upstream模块支持6种方式的分配:

负载均衡策略

轮询默认方式
weight权重方式
ip_hash依据ip分配方式
least_conn最少连接方式
fair(第三方)响应时间方式
url_hash(第三方)依据URL分配方式
集群

首先,通过排查问题,发现是oltpapi接口服务处理请求很慢,大量请求过来,总是超时和中断连接,这时候,我们想着最简单的方法就是加机器,给oltp接口服务多加几台机器。

嗯,一切都很完美,如预期进行,但是加到一定数量,你发现,怎么不起效果,异步响应还是很慢,或者更直观的说,消息队列出现了严重的消息堆积。这时候,你发现出现了新的问题或者瓶颈,这个问题已经不是说加oltp服务器能解决了,那么,就需要去重新定位问题。发现是消息堆积,消息堆积就是生产者过快,导致消费者消费不过来,这时候,你就需要增加消费者的消费数量。给风控系统多加几台机器,让消费者和生产者达到一定平衡。

这里有个误区,你可能以为是rocketmq的broker数量过少,增加broker数量,其实当消费者和生产者保持一样的速度时,消息肯定不对堆积,按照原始的broker数量就足够。但是增加broker也会使得消息得到尽快的处理,提升一定效率。

缓存

当加机器不能解决问题时,或者说没那么多服务器可使用时,那么就要重代码层面解决高并发问题。Redis 是一个高性能的key-value数据库,当获取数据从数据库拿很慢时,就可以存储到redis,从redis取值。

  • 用ConcurrentHashMap缓存对象,并设置过期时间

  • redis缓存数据,结合spring定时任务定时获取不会经常改动的key

  • 提高使用redis的效率:比如使用mGet一次获取多个key

  • ....等

接口高可用问题

高可用问题应该上升到整个服务的架构问题上,就是说在搭建整体系统是就应该考虑到。高可用问题是以单点故障,访问速度慢的问题为主导。见  服务高可用

  • redis主从分布式(redis的单点故障和访问速度的提高和主从备份)

  • 分布式dubbo服务的zookeeper主从集群

  • strom的主从集群

  • ...等

总结

下面对接口开发服务做一些总结:

1.是拉还是推:

当接口作为数据源时,还要考虑数据是让合作方主动过来拉还是数据有变化就推送呢,当然是推的效果更好,但是如何有效的推数据,不推重复数据等都是需要根据实际业务考虑的问题。

2.多台分布式服务器上,怎么保证交易的幂等和订单的唯一性

当接口服务和合作方都处于分布式情况下,就很容易出现一个订单号申请多次交易请求,但是根据幂等性,一张彩票只能交易一次,并且每次不管何时请求,结果都应该一样不会改变。这种情况下,我们怎么保证唯一性呢,我们需要把该订单和订单状态存redis,每次请求时去看是否订单已存在。但可能这次交易不成功,下次这张票还可以继续交易,可以生成新的订单号啊。

redis的setNX是一个很好的解决方案,意思是当存在该key时,返回false,当没有时,该key和value插入成功。用作检查订单是否正在提交,如果是,则阻塞本次请求,避免重复提交 ,可以设置过期时间3s。提交之前锁定订单,防止重复提交。

3.处理时间超过10s,自动返回该订单交易失败

总之,博主发现,在高并发场景下,导致服务崩溃的原因还是redis和数据库,可能是redis读写太慢,或者数据库的一些sql使用不当,或者没建索引导致读写很慢。

总之,这是一条很漫长的路,我们都需要慢慢积累经验和学习前人更优秀的解决办法。

78e8023ab632a82d6d1772c61e950439.gif

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1、某投资人士用Modem拨号上网,通过金融机构的网上银行系统,进行证券、基金与理财 产品的网上交易,并需要用电子邮件与朋友交流投资策略。该用户面临的安全威胁主要有 : (1)计算机硬件设备的安全; (2)计算机病毒; (3)网络蠕虫; (4)恶意攻击; (5)木马程序; (6)网站恶意代码; (7)操作系统与应用软件漏洞; (8)电子邮件安全。 试据此给出该用户的网络安全解决方案。 在网关位置配置多接口防火墙,将整个网络划分为外部网络、内部网络、DMZ区等多个安 全区域,将工作主机放置于内部网络区域,将WEB服务器、数据库服务器等服务器放置在D MZ区域,其她区域对服务器区的访问必须经过防火墙模块的检查。 在中心交换机上配置基于网络的IDS系统,监控整个网络内的网络流量。 在DMZ区内的数据库服务器等重要服务器上安装基于主机的入侵检测系统,对所有上述服 务器的访问进行监控,并对相应的操作进行记录与审计。 将电子商务网站与进行企业普通WEB发布的服务器进行独立配置,对电子商务网站的访 问将需要身份认证与加密传输,保证电子商务的安全性。 在DMZ区的电子商务网站配置基于主机的入侵检测系统;防止来自INTERNET对HTTP服务 的攻击行为。 在企业总部安装统一身份认证服务器,对所有需要的认证进行统一管理,并根据客户的 安全级别设置所需要的认证方式(如静态口令,动态口令,数字证书等)。 2、某局域网(如下图所示)由1个防火墙、2个交换机、DMZ区的WEB与Email服务器,以及内 网3台个人计算机组成。 请完成下述要求: 1. 在下图的空白框中填写设备名 2. 完成下图中设备之间的连线,以构成完整的网络结构图。 3、入侵检测系统结构图 响应分析 知识库 —— 入侵分析 —— 数据存储 数据提取 ——————— 原始数据流 4、某局域网如下图,其中:1号设备就是路由器,4号设备就是交换机,5与6号设备就是DMZ 区服务器,7、8与9号设备就是个人计算机。 请回答下列问题: (1)2与3号设备中,哪个设备就是防火墙?哪个设备就是交换机? (2)3套个人防火墙软件最适合安装在哪3个设备上?(只能选3个设备) (3)5套防病毒软件应该安装在哪5个设备上?(只能选5个设备) 答:2号设备就是防火墙 3号设备就是交换机 3套个人防火墙最适合安装在7、8、9号设备上 5套防病毒软件应该分别装在5、6、7、8、9号设备上 5、某电子商务企业的网络拓扑结构如题33图所示, 试分析该系统可能存在的典型的网络安全威胁。 一般的计算机网络系统的安全威胁主要来自外部网络的非法入侵者的攻击,网络中的敏感 信息可能被泄漏或被修改,从内部网络发出的信息可能被窃听或篡改。 (1)窃听:网络中传输的敏感信息被窃听。 (2)重传:攻击者事先获得部分或全部信息,以后将其发送给接收者。 (3)伪造:攻击者将伪造的信息发送给接收者。 (4)篡改:攻击者对合法用户之间的通信信息进行修改、删除或插入,再发送给接收者。 (5)非授权访问:攻击者通过假冒、身份攻击、系统漏洞等手段,获取系统访问权,从而使 非法用户进入网络系统读取、修改、删除或插入信息等。 (6)拒绝服务攻击:攻击者通过某种方法使系统响应速度减慢甚至瘫痪,阻止合法用户获得 服务。 (7)行为否认:通信实体否认已经发生的行为。 (8)旁路控制:攻击者发掘系统的缺陷或安全脆弱性。 (9)电磁、射频截获:攻击者从电子或机电设备发出的无线射频或其她电磁辐射中提取信 息。 (10)人员疏忽:授权的人为了利益或疏忽将信息泄漏给未授权的人。 6、在内部网络与外部网络之间设计一个屏蔽子网体系结构的防火墙,要求: (1)在下面给出的防火墙结构图的括号中标注组件名称,并补充缺失的连线。 (2)简述防火墙结构图中各组件的基本功能。 1、 (1)外部路由器 (2)内部路由器 (3)堡垒主机 2、各组件基本功能: (1)周边网络就是在入侵者与内部网络之间提供了一个附加的保护层。 (2)堡垒主机就是接受来自外界连接的主要入口。 (3)内部路由器保护内部网络使之免受来自外部网络与周边网络的侵犯。 (4)外部路由器保护周边网络与内部网络使之免受来自外部网络的侵犯。 某大型企业欲建立自己的局域网,对外进行Web发布与电子商务;电子商务与数据库服务要 求安全等级高。另外,企业总部就是企业的核心,在进入企业总部的时候要求进行身份认 证。试分析该网络安全的解决方案。 服务器配置:分成外部服务器与内部一级、二级服务器,将内部服务器设置高安全系数,外 部服务器仅作为网站的外围措施的存放进行进行简单设置。之后把一级服务器与外部服 务器连接实现存储重要数据。 二级服务器作为企业内部使用,与一级服务器进行通讯,在确定无误后由一级服务器存入 二级。 分部服务器、总部

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值