目录

  • 前言
  • HTTPS与SSL证书
  • AES对称加密
  • RSA非对称加密
  • AES + RSA 组合加密
  • 服务端请求参数解密拦截器RequestBodyAdviceAdapter
  • 服务端返回参数加密拦截器ResponseBodyAdvice

前言

  对项目中使用的加密通讯方案以及遇到的问题进行总结.

HTTPS与SSL证书

  • 什么是HTTPS? 全称:Hyper Text Transfer Protocol over SecureSocket Layer,是以安全为目标的 HTTP通道,在HTTP的基础上通过传输加密和身份认证保证了传输过程的安全性.HTTPS 在HTTP 的基础下加入SSL,HTTPS 的安全基础是SSL,因此加密的详细内容就需要 SSL. HTTPS 存在不同于 HTTP 的默认端口及一个加密/身份验证层(在 HTTP与 TCP之间).这个系统提供了身份验证与加密通讯方法.

  • 什么是SSL证书? 数字证书的一种,通过在客户端浏览器和Web服务器之间建立一条SSL安全通道Secure socket layer(SSL)安全协议是由Netscape Communication公司设计开发.该安全协议主要用来提供对用户和服务器的认证,对传送的数据进行加密和隐藏,确保数据在传送中不被改变,即数据的完整性,现已成为该领域中全球化的标准。

  • 为什么要防抓包? 抓包就是对客户端与服务端之间的网络通讯进行截获/重发/编辑.通过抓包获取接口地址以及参数信息,可以被用来编写恶意脚本,发动CC攻击,DDOS攻击等.

  简单来说就是需要购买认证一个域名,然后给这个域名申请SSL证书(各大云厂商都有免费的与收费的,收费的证书安全系数更高,常规抓包软件无法抓包).如果服务端使用ngnix代理转发,需要在ngnix中增加SSL相关配置.配置完成后:

  • WEB端或者服务端再向服务端请求时须使用域名,如:https://xxx.xxx.com/login.
  • 在浏览器访问服务端会显示加锁图标.
  • 移动端不能忽略证书: 需要实现客户端对服务器的校验,认证服务器证书的合法性,当https在握手的协议中返回给客户端的证书应该和保存在客户端本地的证书解析出来的域名一致,才能说明服务器是合法的.
  • 使用域名与证书可以有效防止抓包,但GET接口请求还是不安全的,这里推荐主要业务接口都统一使用POST,包括查询.

AES对称加密

  • 什么是对称加密? 采用单钥密码系统的加密方法,同一个密钥可以同时用作信息的加密和解密,这种加密方法称为对称加密,也称为单密钥加密.

  • 什么是AES? 密码学中的高级加密标准(Advanced Encryption Standard,AES).加密速度快,在目前的计算机体系结构下,没有任何有效的破解手段,绝大部分业务使用AES加密就可以满足需求.

  • 优点: 运算速度快,资源消耗少,理论上无法暴力破解.

  • 缺点: 密钥单一,客户端写死密钥在项目中,泄露风险大.

  • 方案: 客户端与服务端共同使用一个密钥,客户端发起POST请求时,将参数用AES加密成密文,服务端接受到请求后解密.推荐使用RequestBodyAdviceAdapter拦截器解密,见下文.

  • Java工具类:

@Component
public class AESUtil {private static final String AES = "AES";private static final String CHARSET = "UTF-8";private static final String Key = "1234567812345678";//自定义密钥,默认AES-128的密钥长度为16位private static final String IV_STRING = "A-16-Byte-String";//偏移量,增加加密复杂度,可以不用private static final String DEFAULT_CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding";//默认加密算法// 加密public static String encrypt(String content) {String result = "";try {byte[] contentBytes = content.getBytes(CHARSET);byte[] keyBytes = KEY.getBytes(CHARSET);byte[] encryptedBytes = aesEncryptBytes(contentBytes, keyBytes);Encoder encoder = Base64.getEncoder();result = encoder.encodeToString(encryptedBytes);} catch (Exception e) {e.printStackTrace();}return result;}// 解密public static String decrypt(String content) {String result = "";try {Decoder decoder = Base64.getDecoder();byte[] encryptedBytes = decoder.decode(content);byte[] keyBytes = KEY.getBytes(CHARSET);byte[] decryptedBytes = aesDecryptBytes(encryptedBytes, keyBytes);result = new String(decryptedBytes, CHARSET);} catch (Exception e) {e.printStackTrace();}return result;}// 指定key加密public static String encryptToken(String content, String key) {String result = "";try {byte[] contentBytes = content.getBytes(CHARSET);byte[] keyBytes = key.getBytes(CHARSET);byte[] encryptedBytes = aesEncryptBytes(contentBytes, keyBytes);Encoder encoder = Base64.getEncoder();result = encoder.encodeToString(encryptedBytes);} catch (Exception e) {e.printStackTrace();}return result;}// 指定key解密public static String decryptToken(String content, String key) {String result = "";try {Decoder decoder = Base64.getDecoder();byte[] encryptedBytes = decoder.decode(content);byte[] keyBytes = key.getBytes(CHARSET);byte[] decryptedBytes = aesDecryptBytes(encryptedBytes, keyBytes);result = new String(decryptedBytes, CHARSET);} catch (Exception e) {e.printStackTrace();}return result;}private static byte[] cipherOperation(byte[] contentBytes, byte[] keyBytes, int mode) throws Exception {SecretKeySpec secretKey = new SecretKeySpec(keyBytes, AES);99999        Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);cipher.init(mode, secretKey);return cipher.doFinal(contentBytes);}public static byte[] aesEncryptBytes(byte[] contentBytes, byte[] keyBytes) throws Exception {return cipherOperation(contentBytes, keyBytes, Cipher.ENCRYPT_MODE);}public static byte[] aesDecryptBytes(byte[] contentBytes, byte[] keyBytes) throws Exception {return cipherOperation(contentBytes, keyBytes, Cipher.DECRYPT_MODE);}}

特别注意:客户端与服务端要使用同样的AES加密算法,如"AES/ECB/PKCS5Padding"

RSA非对称加密

  • 什么是非对称加密? 密钥成对出现,公开密钥(publickey)和私有密钥(privatekey).如果用公钥对数据进行加密,只有用对应的私有密钥才能解密;如果用私有密钥对数据进行加密,那么只有用对应的公开密钥才能解密。
  • 什么是RSA? RSA是被研究得最广泛的非对称加密算法,从提出到现在已近三十年,经历了各种攻击的考验,逐渐为人们接受,普遍认为是目前最优秀的公钥方案之一.
  • 优点: 密钥成对出现,安全性高,客户端与服务端交换公钥,泄露风险低.
  • 缺点: 加密解密字符串长度有限制,如果超过128字符则需要分段加密,不友好.
  • 方案: 客户端发起POST请求时,将参数用RSA公钥加密,服务端接受到请求后使用RSA私钥解密.
    进阶1: 登录时服务端针对这个客户端生成密钥对,将公钥返回给客户端,自己保存私钥,每个客户端的密钥对不同,有效防止密钥泄露.
    进阶2: 登录时服务端与客户端各自生成密钥对,交换公钥,各自保存私钥,客户端请求时使用服务端公钥加密,服务端返回参数时用客户端公钥加密.
    进阶3: 利用Redis控制密钥对的时效性.
  • 图解:
  • Java工具类:
/*** RSA工具类*/
@Slf4j
public class RSAUtil {private static final String RSA = "RSA";private static final String RSAPublicKey = "RSAPublicKey";private static final String RSAPrivateKey = "RSAPrivateKey";public static void main(String[] args) throws Exception {//post请求参数String param = "{\"password\": \"123456\",\"phoneNum\": \"15555555566\"}";String rsaEncrypt = rsaEncrypt(param);log.info("rsaEncrypt: " + rsaEncrypt);String rsaDecrypt = rsaDecrypt(rsaEncrypt);log.info("rsaDecrypt: " + rsaDecrypt);}/*** 随机生成密钥对*/public static void genKeyPair() {try {// KeyPairGenerator类用于生成公钥和私钥对,基于RSA算法生成对象KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(RSA);// 初始化密钥对生成器keyPairGen.initialize(2048, new SecureRandom());// 生成一个密钥对,保存在keyPair中KeyPair keyPair = keyPairGen.generateKeyPair();RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();   // 得到私钥RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();  // 得到公钥String publicKeyString = new String(Base64.encodeBase64(publicKey.getEncoded()));String privateKeyString = new String(Base64.encodeBase64((privateKey.getEncoded())));log.info("公钥:{}", publicKeyString);log.info("私钥:{}", privateKeyString);} catch (Exception e) {e.printStackTrace();}}/*** RSA公钥加密*/public static String rsaEncrypt(String str) {String result = "";try {//base64编码的公钥byte[] decoded = Base64.decodeBase64(RSAPublicKey);RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance(RSA).generatePublic(new X509EncodedKeySpec(decoded));//RSA加密Cipher cipher = Cipher.getInstance(RSA);cipher.init(Cipher.ENCRYPT_MODE, pubKey);result = Base64.encodeBase64String(cipher.doFinal(str.getBytes(StandardCharsets.UTF_8)));} catch (Exception e) {e.printStackTrace();}return result;}/*** RSA私钥解密*/public static String rsaDecrypt(String str) {String result = "";try {//64位解码加密后的字符串byte[] inputByte = Base64.decodeBase64(str.getBytes(StandardCharsets.UTF_8));//base64编码的私钥byte[] decoded = Base64.decodeBase64(RSAPrivateKey);RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance(RSA).generatePrivate(new PKCS8EncodedKeySpec(decoded));//RSA解密Cipher cipher = Cipher.getInstance(RSA);cipher.init(Cipher.DECRYPT_MODE, priKey);result = new String(cipher.doFinal(inputByte));} catch (Exception e) {e.printStackTrace();}return result;}}

AES + RSA 组合加密

  • 为什么要使用组合加密? AES固定密钥泄露风险大,RSA受长度限制.
  • 方案: 使用AES对称密码体制对传输数据加密,同时使用RSA不对称密码体制来传送AES的密钥,就可以综合发挥AES和RSA的优点.
  • 图解:
  • Java工具类:
/*** AES+RSA组合加密* 客户端使用随机产生的16位AES的密钥对参数进行AES加密,通过使用RSA公钥对AES密钥进行公钥加密.* 服务端对加密后的AES密钥进行RSA私钥解密,拿到密钥原文,对加密后的参数进行AES解密,拿到原始内容.*/
@Slf4j
public class SecretAUtil {private static final String RSAPublicKey = "RSAPublicKey";private static final String RSAPrivateKey = "RSAPrivateKey";private static final String DEFAULT_CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding";public static void main(String[] args) throws Exception {//post请求参数,由key跟content两部分组成,key是用RSA公钥加密的AESKey,content是AES加密的原本参数String encryptBody = "{\"key\": \"bAgOxdNtAGLFF2ly+8vMJt4M89A2fUDk\\/1RM5jxWNdPjTsvuvIqRxJfkQKLOzafdolp7WWw725eaX1ee2CPID7yh2Vn3UcPOFfHeIFZd5Q4ehVd3tHqnv+aY0uj3Q5mDzWo92X1dY67\\/wTrii7+D0LQjBHVmBxMEGwQdaXJskZis8lROV0ursfWr0fgZxeN3vEWbuM7EbIzXDDNH9Gp6zH3B27PPJ4+g+nv7sJ90KBM7ocMWzZmKfW+6H1Cis2jI9Gylm9gc71P04M1zKlNuXfw\\/nWwAb1ez9pCjTp8AiOKLRjdPEk89ovPveOeaKCtd636wxSamHOuMA1YfUzlxIA==\"," +"\"content\": \"WgamUOMvavbJZW+kxU6ZT3TCtS\\/m2+wwBKXjD9gLqfsG7XoGQf1XRRz7sL8Gdh9FJjAt6b94Nsw6Qip0FlBpLBEOF2f6joBP0bVIDVmne8moLZVpuV2faJhGUVwaZwDJ\\/PMfwpFDs\\/JBkPcBvtpauGXR+awsG3pkACs1GXxlbKa3e+EeEgii6xLDL54XpG7J\"}";JSONObject jsonObject = JSON.parseObject(encryptBody);String key = String.valueOf(jsonObject.get("key"));String content = String.valueOf(jsonObject.get("content"));// 1.先使用RSA私钥解密出AESKeyString AESKey = SecretUtil.rsaDecrypt(key, RSAPrivateKey);// 2.使用AESKey解密内容String original = SecretUtil.aesDecrypt(content, AESKey);}/*** RSA公钥加密*/public static String rsaEncrypt(String str) {String result = "";try {//base64编码的公钥byte[] decoded = Base64.decodeBase64(RSAPublicKey);RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(decoded));//RSA加密Cipher cipher = Cipher.getInstance("RSA");cipher.init(Cipher.ENCRYPT_MODE, pubKey);result = Base64.encodeBase64String(cipher.doFinal(str.getBytes(StandardCharsets.UTF_8)));} catch (Exception e) {e.printStackTrace();}return result;}/*** RSA私钥解密*/public static String rsaDecrypt(String str, String privateKey) {String result = "";try {//64位解码加密后的字符串byte[] inputByte = Base64.decodeBase64(str.getBytes(StandardCharsets.UTF_8));//base64编码的私钥byte[] decoded = Base64.decodeBase64(privateKey);RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(decoded));//RSA解密Cipher cipher = Cipher.getInstance("RSA");cipher.init(Cipher.DECRYPT_MODE, priKey);result = new String(cipher.doFinal(inputByte));} catch (Exception e) {e.printStackTrace();}return result;}/*** AES加密ECB模式PKCS5Padding填充方式*/public static String aesEncrypt(String str, String key) throws Exception {Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8);cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(keyBytes, "AES"));byte[] doFinal = cipher.doFinal(str.getBytes(StandardCharsets.UTF_8));return new String(Base64.encodeBase64(doFinal));}/*** AES解密ECB模式PKCS5Padding填充方式*/public static String aesDecrypt(String str, String key) throws Exception {Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8);cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(keyBytes, "AES"));byte[] doFinal = cipher.doFinal(Base64.decodeBase64(str));return new String(doFinal);}

服务端请求参数解密拦截器RequestBodyAdviceAdapter

服务端通过自定义拦截器实现部分接口加密,以及加密开关.

  • 配置类SecretConfig:
@Data
@Component
@ConfigurationProperties(prefix = "secret")
public class SecretConfig {/*** 是否开启*/private boolean enabled;/*** 是否扫描注解*/private boolean scanAnnotation;/*** 扫描自定义注解*/private Class<? extends Annotation> annotationClass = SecretBody.class;
}
  • 配置文件:
secret:enabled: truescan-annotation: true
  • 自定义注解类@SecretBody:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface SecretBody {}
  • 自定义解密拦截器:
//解密拦截器
@Slf4j
@ControllerAdvice
@ConditionalOnProperty(prefix = "secret", name = "enabled", havingValue = "true")
@EnableConfigurationProperties({SecretConfig.class})
public class RequestDecryptAdvice extends RequestBodyAdviceAdapter {@Autowiredprivate SecretConfig secretConfig;@Overridepublic HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {//如果有注解boolean supportSafeMessage = supportSecretRequest(parameter);if (supportSafeMessage) {String httpBody;InputStream encryptStream = inputMessage.getBody();String encryptBody = StreamUtils.copyToString(encryptStream, Charset.defaultCharset());try {//AES+RSA组合解密httpBody = combinationDecryptBody(encryptBody, "userRSAPrivateKey");} catch (Exception e) {e.printStackTrace();log.error("解密失败:" + encryptBody);throw new BusinessException(StatusCode.TOKENERROR, "登录超时,请重新登录");}//返回处理后的消息体给messageConvertreturn new SecretHttpMessage(new ByteArrayInputStream(httpBody.getBytes()), inputMessage.getHeaders());}return inputMessage;}/*** 是否支持加密消息体*/private boolean supportSecretRequest(MethodParameter methodParameter) {if (!secretConfig.isScanAnnotation()) {return true;}//判断class是否存在注解if (methodParameter.getContainingClass().getAnnotation(secretConfig.getAnnotationClass()) != null) {return true;}//判断方法是否存在注解return methodParameter.getMethodAnnotation(secretConfig.getAnnotationClass()) != null;}@Overridepublic boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {return true;}/*** AES+RSA组合解密*/private String combinationDecryptBody(String encryptBody, String privateKey) throws Exception {String original;JSONObject jsonObject = JSON.parseObject(encryptBody);String key = String.valueOf(jsonObject.get("key"));String content = String.valueOf(jsonObject.get("content"));// 1.先使用RSA私钥解密出AESKeyString AESKey = SecretUtil.rsaDecrypt(key, privateKey);// 2.使用AESKey解密内容original = SecretUtil.aesDecrypt(content, AESKey);return original;}

服务端返回参数加密拦截器ResponseBodyAdvice

  • 自定义拦截器(配置类同上):
//加密拦截器
@Slf4j
@ControllerAdvice
@ConditionalOnProperty(prefix = "secret", name = "enabled", havingValue = "true")
@EnableConfigurationProperties({SecretConfig.class})
public class ResponseEncryptAdvice implements ResponseBodyAdvice<Object> {@Autowiredprivate SecretConfig secretConfig;@Overridepublic Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {//如果有注解boolean supportSafeMessage = supportSecretRequest(methodParameter);if (supportSafeMessage) {try {String returnStr;//可以在头部中加入标记告诉客户端此接口是密文返回response.getHeaders().add("encrypt", "true");String srcData = JSON.toJSONString(body);//客户端公钥加密returnStr = RSAUtil.encrypt(srcData, "userPublicKey");return returnStr;} catch (Exception e) {e.printStackTrace();}}return body;}/*** 是否支持加密消息体*/private boolean supportSecretRequest(MethodParameter methodParameter) {if (!secretConfig.isScanAnnotation()) {return true;}//判断class是否存在注解if (methodParameter.getContainingClass().getAnnotation(secretConfig.getAnnotationClass()) != null) {return true;}//判断方法是否存在注解return methodParameter.getMethodAnnotation(secretConfig.getAnnotationClass()) != null;}@Overridepublic boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {return true;}

浅谈客户端与服务端的加密通讯(HTTPS/AES/RSA/RequestBodyAdviceAdapter/ResponseBodyAdvice)相关推荐

  1. Esp8266学习之旅⑧ 你要找的8266作为UDP、TCP客户端或服务端的角色通讯,都在这了。(带Demo)

    本系列博客学习由非官方人员 半颗心脏 潜心所力所写,不做开发板.仅仅做个人技术交流分享,不做任何商业用途.如有不对之处,请留言,本人及时更改. 序号 SDK版本 内容 链接 1 nonos2.0 搭建 ...

  2. 游戏社区App (三):客户端与服务端的加密处理 和 登录

    http请求数据无论是GET或者POST都可能会被抓包获取到数据.为了避免用户的敏感数据被窃取(比如密码),需要对数据进行加密处理. 一.相关名词解析 RSA:非对称加密. 会产生公钥和私钥,公钥在客 ...

  3. php winform通信,C# Winform 通过Socket实现客户端和服务端TCP通信

    操作界面如下: 1.声明Socket 第一个参数:寻址方式,第二个参数:传输数据的方式,第三个参数:通信协议 Socket socket = new Socket(AddressFamily.Inte ...

  4. 自制CA证书,自制客户端,服务端证书

    自制CA证书,客户端.服务端证书 参考资料:HTTPS证书生成原理和部署细节 废话不多讲,我们直入正题. 首先我假设你的系统已经安装了openssl.使用openssl version -a即可查看当 ...

  5. 浅议C#客户端和服务端通信的几种方法:Rest和GRPC和其他

    本文来自:https://michaelscodingspot.com/rest-vs-grpc-for-asp-net/ 浅议C#客户端和服务端通信的几种方法:Rest和GRPC 在C#客户端和C# ...

  6. 验证客户端和服务端可以传输经SM4加密的密文数据,从而验证发送数据已使用服务器密码机进行SM4加密,而不是随便的字符串乱码

    前提操作 搭建客户端和服务端  Socket代码实现服务端 和 客户端之间通信_CHYabc123456hh的博客-CSDN博客 使用wireshark进行数据的监听和测试https://blog.c ...

  7. 一篇文章带你了解https是如何做到客户端与服务端之间安全通信

    https是什么. 超文本传输安全协议(英语:Hypertext Transfer Protocol Secure,缩写:HTTPS,常称为HTTP over TLS,HTTP over SSL或HT ...

  8. QTcpSocket客户端和服务端发送图片(或大文件)小Demo

    先看一下效果: 思路: 发图片.大文件与发短字符不大一样. 1.文件和图片通过TCP可能一次发不过去,可能要发很多次.所以我们在发送文件.数据.以及文字最好带上文件的大小. 2.图片转换成文件流的形式 ...

  9. 在线登录注册功能(android客户端+javaweb服务端+腾讯云服务器+腾讯云数据库)

    在线登录注册功能(android客户端+javaweb服务端+腾讯云服务器+腾讯云数据库) 完整的项目已上传github仓库,链接在文章最下面 注:笔者在安卓客户端部分写了kotlin语言和java语 ...

最新文章

  1. 一天狂揽2000+星,微软面向初学者ML课程来了,完全免费
  2. 计算机基础及ms office应用,全国计算机等级考试一级计算机基础及MS Office应用模拟练习系统...
  3. Java一行代码打印当前系统时间
  4. asp实现注册登录界面_python app (kivy)-与小型数据库连接,实现注册登录操作
  5. Blogger建立Blog部落格​​ - Blog透视镜
  6. JAVA 重写重载/多态/抽象类/封装/接口/包
  7. Java Timestamp Memo
  8. php 开源 采集,迅睿CMS 火车头内容采集
  9. OAuth2.0 微信授权机制
  10. 动态计算未知盒子的高度
  11. MIT 6.828 main.c文件分析
  12. Spring中Bean的作用域差别
  13. 移动开发者应注意的2012年五趋势
  14. 净空法师质疑,人的生命真的变长了吗
  15. 茗创:近红外数据处理业务
  16. 20行Python代码,轻松提取PPT文字到Word!
  17. 上海的211大学中计算机,上海有哪些211大学
  18. 学法减分拍照识题小程序开发
  19. Android 页面Scheme配置
  20. python爬虫获取图片无法打开或已损坏_Python爬取小姐姐图片

热门文章

  1. 读书笔记:《人力资源管理》- 6 汇总
  2. 指针 字符串的复制(函数)
  3. 波士顿儿童医院CHB-MIT癫痫数据集预处理-提取发作时间
  4. 全方位揭秘!大数据从0到1的完美落地之HDFS读写流程
  5. 学习Head First Design Pattern——翻译Chapter 2:The Observer Pattern
  6. php计算机毕业设计基于thinkphp框架的特色旅游网站vue
  7. 服务器运维基础(一)
  8. IDataParameter[]怎么创建
  9. Visual Assist X的安装及破解方法
  10. 如何搭建短网址api平台