对外接口安全措施的作用主要体现在两个方面,一方面是如何保证数据在传输过程中的安全性,另一方面是数据现已到达服务器端,服务器端如何识别数据。

1. 数据加密

数据在传输过程中是很简单被抓包的,假如直接传输,数据能够被任何人获取,所以有必要对数据加密。加密办法有对称加密和非对称加密:

  • 对称加密:对称密钥在加密和解密的过程中运用的密钥是相同的,常见的对称加密算法有 DES、AES。长处是核算速度快,缺陷是在数据传送前,发送方和接收方有必要商定好密钥,并无缺保存。假如一方的密钥被泄露,那么加密信息也就不安全了;
  • 非对称加密:服务端会生成一对密钥,私钥存放在服务器端,公钥能够发布给任何人运用。与对称加密比较,这种办法更安全,但速度慢太多了。

现在主流的做法是运用 HTTPS 协议,在 HTTP 和 TCP 之间增加一层加密层 (SSL 层),这一层担任数据的加密和解密。HTTPS 的完成办法结合了对称加密与非对称加密的长处,在安全和性能方面都比较突出。

示例

javascript

const CryptoJS = require("crypto-js");
// 密钥,8 字节
const key = CryptoJS.enc.Utf8.parse('12345678');
// 偏移量,8 字节
const iv = CryptoJS.enc.Utf8.parse('12345678');
// DES 加密
function encryptDES(data) {
  const encrypted = CryptoJS.DES.encrypt(data, key, {
    iv: iv,
    mode: CryptoJS.mode.CBC,
    padding: CryptoJS.pad.Pkcs7
  });
  return encrypted.toString();
}
// DES 解密
function decryptDES(encryptedData) {
  const decrypted = CryptoJS.DES.decrypt(encryptedData, key, {
    iv: iv,
    mode: CryptoJS.mode.CBC,
    padding: CryptoJS.pad.Pkcs7
  });
  return decrypted.toString(CryptoJS.enc.Utf8);
}
// 测验
const originalData = 'Hello, world!';
const encryptedData = encryptDES(originalData); //YcunRrQmVq9nAmF4fyALkw==
console.log('加密后的数据:', encryptedData);
const decryptedData = decryptDES(encryptedData);
console.log('解密后的数据:', decryptedData);

java

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
import org.springframework.stereotype.Component;
public class DesUtil {
    // 密钥,长度为8个字符
    private static final String KEY = "12345678";
    // 偏移量,长度为8个字符
    private static final String IV = "12345678";
    // 加密算法
    private static final String ALGORITHM = "DES";
    // 加密模式
    private static final String TRANSFORMATION = "DES/CBC/PKCS5Padding";
    // 加密
    public static String encrypt(String input) throws Exception {
        // 创立密钥目标
        SecretKeySpec keySpec = new SecretKeySpec(KEY.getBytes(), ALGORITHM);
        // 创立偏移量目标
        IvParameterSpec ivSpec = new IvParameterSpec(IV.getBytes());
        // 创立加密器目标
        Cipher cipher = Cipher.getInstance(TRANSFORMATION);
        // 初始化加密器
        cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
        // 加密
        byte[] encrypted = cipher.doFinal(input.getBytes());
        // 将加密后的字节数组转换为Base64字符串
        return Base64.encodeBase64String(encrypted);
    }
    // 解密
    public static String decrypt(String input) throws Exception {
        // 创立密钥目标
        SecretKeySpec keySpec = new SecretKeySpec(KEY.getBytes(), ALGORITHM);
        // 创立偏移量目标
        IvParameterSpec ivSpec = new IvParameterSpec(IV.getBytes());
        // 创立解密器目标
        Cipher cipher = Cipher.getInstance(TRANSFORMATION);
        // 初始化解密器
        cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
        // 将Base64字符串解码为字节数组
        byte[] decrypted = cipher.doFinal(Base64.decodeBase64(input));
        // 将解密后的字节数组转换为字符串
        return new String(decrypted);
    }
    // 测验
    public static void main(String[] args) throws Exception {
        String originalData = "Hello, world!";
        String encryptedData = encrypt(originalData);
        System.out.println(encryptedData); // YcunRrQmVq9nAmF4fyALkw==
    }  
}

2. 数据加签

数据加签便是由发送者产生一段无法伪造的数字串,来保证数据在传输过程中不被篡改。数据假如现已通过 HTTPS 加密了,其加密部分只是在外网,而加签能够避免内网中数据被篡改。

数据签名运用较多的是 MD5 算法,将需求提交的数据通过某种办法组合,然后通过 MD5 生成一段加密字符串,这段加密字符串便是数据包的签名。而其间的用户密钥,客户端和服务端都保存一份,会愈加安全。

示例

java

界说一个东西类来完成带密钥的MD5加签和验签:

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class MD5Util {
    public static String md5(String data, String key) {
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            md.update((data + key).getBytes());
            byte[] digest = md.digest();
            StringBuilder sb = new StringBuilder();
            for (byte b : digest) {
                sb.append(String.format("%02x", b & 0xff));
            }
            return sb.toString();
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("MD5加签失利", e);
        }
    }
    public static boolean verify(String data, String sign, String key) {
        return md5(data, key).equals(sign);
    }
}

在事务代码中运用该东西类进行MD5加签和验签:

@RestController
public class DemoController {
    private static final String KEY = "123456";
    @GetMapping("/demo")
    public String demo(String data, String sign) {
        // 对数据进行MD5加签
        String signData = MD5Util.md5(data, KEY);
        if (signData.equals(sign)) {
            // 验签通过
            return "验签通过";
        } else {
            // 验签不通过
            return "验签不通过";
        }
    }
}

javascript

界说一个东西类来完成带密钥的 MD5 加签和验签:

const CryptoJS = require("crypto-js");
const MD5Util = {
  md5(data, key) {
    // 运用 crypto-js 库核算 MD5 值
    const hash = CryptoJS.MD5(data + key); 
    return hash.toString();
  },
  verify(data, sign, key) {
    // 核算数据的签名
    const dataSign = this.md5(data, key); 
    // 回来验签结果
    return dataSign === sign; 
  }
};

在事务代码中运用该东西类进行 MD5 加签和验签:

const KEY = '123456';
function demo(data, sign) {
  // 对数据进行 MD5 加签
  const signData = MD5Util.md5(data, KEY);
  if (signData === sign) {
    // 验签通过
    return '验签通过';
  } else {
    // 验签不通过
    return '验签不通过';
  }
}

3. 时刻戳机制

数据通过如上的加密、加签处理后,就算被抓包也不能看到实在的数据。但是有的不法者不关心实在数据,而是直接拿到抓取的数据包进行歹意恳求。这时能够运用时刻戳机制,在每次恳求中参加当时的时刻,服务器端会拿到当时时刻与音讯中的时刻相减,看看是否在一个固定的时刻范围内,比方 5 分钟,这样歹意恳求的数据包是无法更改时刻的,所以 5 分钟后就视为非法恳求了。

java

界说一个阻拦器:

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.concurrent.TimeUnit;
public class TimeStampInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 获取恳求头中的时刻戳
        String timeStampStr = request.getHeader("timeStamp"); 
        if (timeStampStr != null) {
            // 将时刻戳转换为 long 类型
            long timeStamp = Long.parseLong(timeStampStr); 
            // 获取当时时刻戳
            long now = System.currentTimeMillis(); 
            // 验证时刻戳是否有用
            if (TimeUnit.MILLISECONDS.toMinutes(now - timeStamp) < 5) { 
                return true;
            }
        }
        response.getWriter().print("时刻戳无效!");
        response.getWriter().flush();
        response.getWriter().close();
        return false;
    }
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    }
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    }
}

将阻拦器注册到springboot中:

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
      	// 增加需求阻拦的接口途径
        registry.addInterceptor(new TimeStampInterceptor())
                .addPathPatterns("/api/**"); 
    }
}

4. AppID 机制

大部分网站都需求用户名和暗码才干登录,这其实也是一种安全机制。相应的对外接口也需求这么一种机制,运用接口的用户需求在后台注册 AppID,供给给用户相关的密钥。在调用的接口中需求供给 AppID+ 密钥,服务器端会进行相关的验证。生成仅有的 AppID 即可,依据实际情况看是否需求大局仅有,一起密钥运用字母、数字等特别字符随机生成。

java

界说一个阻拦器:

import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class AppIdSecretInterceptor implements HandlerInterceptor {
    @Value("${app.id}")
    private String appId;
    @Value("${app.secret}")
    private String appSecret;
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
      	// 获取恳求头中的 AppID
        String appIdParam = request.getHeader("appId"); 
      	// 获取恳求头中的密钥
        String secretParam = request.getHeader("secret"); 
      	// 验证 AppID 和密钥是否正确
        if (appId.equals(appIdParam) && DigestUtils.md5Hex(appIdParam + appSecret).equals(secretParam)) { 
            return true;
        }
        response.getWriter().print("AppID 或密钥错误!");
        response.getWriter().flush();
        response.getWriter().close();
        return false;
    }
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    }
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    }
}

将阻拦器注册到springboot中:

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
      	// 增加需求阻拦的接口途径
        registry.addInterceptor(new AppIdSecretInterceptor())
                .addPathPatterns("/api/**"); 
    }
}

5. 限流机制

本来便是实在的用户,而且注册了 AppID,但出现了频频调用接口的情况,这时需求给相关 AppID 限流处理,常用的限流算法包含:令牌桶限流、漏桶限流、计数器限流。

漏桶算法的原理是依照固定常量速率流出恳求,流入恳求速率恣意,当恳求数超越桶的容量时,新的恳求等候或许拒绝服务,漏桶算法能够强制限制数据的传输速度。

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

令牌桶算法的原理是令牌桶算法 和漏桶算法 作用一样但方向相反的算法,愈加简单了解。跟着时刻流逝,体系会按稳定 1/QPS 时刻距离(假如 QPS=100,则距离是 10ms)往桶里参加 Token(幻想和漏洞漏水相反,有个水龙头在不断的加水),假如桶现已满了就不再加了。新恳求来暂时,会各自拿走一个 Token,假如没有 Token 可拿了就堵塞或许拒绝服务。

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

计数器算法比较简单粗暴,主要用来限制总并发数,比方数据库连接池、线程池、秒杀的并发数。计数器限流只需必定时刻内的总恳求数超越设定的阀值,就会进行限流。

java

import com.google.common.util.concurrent.RateLimiter;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class RateLimitInterceptor implements HandlerInterceptor {
		// 创立一个令牌桶,每秒放行 10 个恳求
    private static final RateLimiter RATE_LIMITER = RateLimiter.create(10); 
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 测验获取一个令牌
        if (RATE_LIMITER.tryAcquire()) { 
            return true;
        }
        response.getWriter().print("恳求过于频频,请稍后重试!");
        response.getWriter().flush();
        response.getWriter().close();
        return false;
    }
}

以上代码中,咱们界说了一个阻拦器 RateLimitInterceptor,并创立了一个 RATE_LIMITER 目标作为令牌桶,每秒放行 10 个恳求。在 preHandle 办法中,咱们运用 RATE_LIMITER.tryAcquire() 办法测验获取一个令牌,假如获取成功,则回来 true,不然拒绝调用接口并回来 false。

阻拦器注册到 Spring Boot:

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 增加需求阻拦的接口途径
        registry.addInterceptor(new RateLimitInterceptor())
                .addPathPatterns("/api/**"); 
    }
}

6. 黑名单机制

假如一个 AppID 进行过许多非法操作,或许专门有一个中黑体系,通过剖析之后能够直接将此 AppID 列入黑名单,一切恳求直接回来错误码。如何查看黑名单列表呢?能够给用户设置一个状况比方:初始化状况、正常状况、中黑状况、封闭状况等等。或许直接通过分布式装备中心,保存黑名单列表,每次检查用户是否在列表中即可。

java

创立一个黑名单服务,用于管理被禁用的 IP:

import org.springframework.stereotype.Service;
import java.util.HashSet;
import java.util.Set;
@Service
public class BlacklistService {
		// 存储被禁用的 IP
    private Set<String> blackList = new HashSet<>(); 
    public void add(String ip) {
        blackList.add(ip);
    }
    public boolean contains(String ip) {
        return blackList.contains(ip);
    }
}

界说一个阻拦器:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class BlacklistInterceptor implements HandlerInterceptor {
    @Autowired
    private BlacklistService blacklistService;
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String ip = request.getRemoteAddr();
        if (blacklistService.contains(ip)) {
            response.getWriter().print("您的 IP 已被禁用!");
            response.getWriter().flush();
            response.getWriter().close();
            return false;
        }
        return true;
    }
}

阻拦器注册到 Spring Boot:

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 增加需求阻拦的接口途径
        registry.addInterceptor(new BlacklistInterceptor())
                .addPathPatterns("/api/**"); 
    }
}

7. 数据合法性校验

这是每个体系都会有的处理机制,只有在数据合法的情况下才会进行数据处理。每个体系都有自己的验证规矩,当然也可能有一些惯例性的规矩,比方身份证号码长度和组成、电话号码长度和组成等等。

合法性校验包含惯例性校验以及事务校验,前者包含签名校验、必填校验、长度校验、类型校验、格局校验等,后者依据实际事务而定,比方订单金额不能小于 0 等等。