详解API接口如何安全的传输数据

概述

API接口的安全传输是确保数据在API请求和响应之间的传输过程中不被截获、篡改或泄露的重要步骤。以下是一些用于增强API接口安全传输的常见技术和最佳实践:

  1. 使用HTTPS:使用HTTPS协议而不是HTTP,以确保数据在传输过程中的安全性。HTTPS使用SSL/TLS协议对通信进行加密,防止中间人攻击和数据窃听。

  2. 验证HTTPS请求:验证HTTPS请求的来源,确保请求来自授权的客户端。这可以通过检查SSL证书的颁发机构和有效期来实现。

  3. 验证API密钥:验证API请求中包含的API密钥的合法性。这可以通过检查密钥的唯一标识符、有效性和权限来实现。

  4. 使用JSON Web Tokens (JWT):JWT是一种开放标准,用于在双方之间安全地传输信息。JWT包含一组声明,由JSON对象表示,并使用数字签名进行验证。它可以用于API身份验证和授权。

  5. 限制API访问频率:限制API请求的频率和并发数,以防止滥用和拒绝服务攻击。这可以通过设置速率限制和并发限制来实现。

  6. 使用消息身份验证码(MAC):消息身份验证码是一种用于验证消息完整性和认证性的机制。它可以用于防止篡改和重放攻击。

  7. 加密敏感数据:对传输的敏感数据进行加密,例如用户密码和个人信息。这可以通过使用对称加密或公钥加密来实现。

  8. 使用合适的HTTP标头:使用适当的HTTP标头来防止跨站脚本攻击(XSS)和其他安全漏洞。例如,设置"X-XSS-Protection: 1; mode=block"标头来启用浏览器的内置XSS保护机制。

  9. 实施访问控制:根据用户的身份和权限,对API请求进行访问控制。这可以通过使用基于角色的访问控制(RBAC)或基于声明的访问控制(ABAC)来实现。

  10. 定期更新和修补:确保API和相关系统得到及时更新和修补,以修复任何已知的安全漏洞。

在Spring中我们通过继承RequestBodyAdviceAdapter实现对于请求的内容进行解密操作,实现ResponseBodyAdvice来对相应内容进行加密处理。接下来将详细讲解数据加解密的实现过程。

定义加密解密的接口:

复制代码
public interface SecretProcess {    /**   *  <p>数据加密</p>   *  <p>时间:2020年12月24日-下午12:22:13</p>   * @author xg   * @param data 待加密数据   * @return String 加密结果   */  String encrypt(String data) ;    /**   *  <p>数据解密</p>   *  <p>时间:2020年12月24日-下午12:23:20</p>   * @author xg   * @param data 待解密数据   * @return String 解密后的数据   */  String decrypt(String data) ;    /**   *  <p>加密算法格式:算法[/模式/填充]</p>   *  <p>时间:2020年12月24日-下午12:32:49</p>   * @author xg   * @return String   */  String getAlgorithm() ;    public static class Hex {        private static final char[] HEX = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',        'a', 'b', 'c', 'd', 'e', 'f' };        public static byte[] decode(CharSequence s) {      int nChars = s.length();      if (nChars % 2 != 0) {        throw new IllegalArgumentException("16进制数据错误");      }      byte[] result = new byte[nChars / 2];      for (int i = 0; i < nChars; i += 2) {        int msb = Character.digit(s.charAt(i), 16);        int lsb = Character.digit(s.charAt(i + 1), 16);        if (msb < 0 || lsb < 0) {          throw new IllegalArgumentException(            "Detected a Non-hex character at " + (i + 1) + " or " + (i + 2) + " position");        }        result[i / 2] = (byte) ((msb << 4) | lsb);      }      return result;    }        public static String encode(byte[] buf) {      StringBuilder sb = new StringBuilder() ;      for (int i = 0, leng = buf.length; i < leng; i++) {        sb.append(HEX[(buf[i] & 0xF0) >>> 4]).append(HEX[buf[i] & 0x0F]) ;      }      return sb.toString() ;    }      }  }

该接口中定义了两个方法分别是加密与解密的方法,还有Hex类 该类用来对数据处理16进制的转换。

定义一个抽象类实现上面的接口,具体的加解密实现细节在该抽象类中

AbstractSecretProcess

复制代码
public abstract class AbstractSecretProcess implements SecretProcess {    @Resource  private SecretProperties props ;    @Override  public String decrypt(String data) {    try {      Cipher cipher = Cipher.getInstance(getAlgorithm()) ;      cipher.init(Cipher.DECRYPT_MODE, keySpec()) ;      byte[] decryptBytes = cipher.doFinal(Hex.decode(data)) ;      return new String(decryptBytes) ;    } catch (Exception e) {      throw new RuntimeException(e) ;    }  }    @Override  public String encrypt(String data) {    try {      Cipher cipher = Cipher.getInstance(getAlgorithm()) ;      cipher.init(Cipher.ENCRYPT_MODE, keySpec()) ;      return Hex.encode(cipher.doFinal(data.getBytes(Charset.forName("UTF-8")))) ;    } catch (Exception e) {      throw new RuntimeException(e) ;    }  }    /**   *  <p>根据密钥生成不同的密钥材料</p>   *  <p>目前支持:AES, DES</p>   *  <p>时间:2020年12月25日-下午1:02:54</p>   * @author xg   * @param secretKey 密钥   * @param algorithm 算法   * @return Key   */  public Key getKeySpec(String algorithm) {    if (algorithm == null || algorithm.trim().length() == 0) {      return null ;    }    String secretKey = props.getKey() ;    switch (algorithm.toUpperCase()) {      case "AES":        return new SecretKeySpec(secretKey.getBytes(), "AES") ;      case "DES":        Key key = null ;        try {          DESKeySpec desKeySpec = new DESKeySpec(secretKey.getBytes()) ;          SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("DES") ;          key = secretKeyFactory.generateSecret(desKeySpec);        } catch (Exception e) {          throw new RuntimeException(e) ;        }        return key ;      default:        return null ;    }  }    /**   *  <p>生成密钥材料</p>   *  <p>时间:2020年12月25日-上午11:35:03</p>   * @author xg   * @return Key 密钥材料   */  public abstract Key keySpec() ;  }

该抽象类中提供了2中对称加密的密钥还原,分表是AES和DES算法。一个抽象方法,该抽象方法

keySpec该方法需要子类实现(具体使用的是哪种对称加密算法)。

具体加密算法的实现类

AESAlgorithm

复制代码
public class AESAlgorithm extends AbstractSecretProcess {
  @Override  public String getAlgorithm() {    return "AES/ECB/PKCS5Padding";  }    @Override  public Key keySpec() {    return this.getKeySpec("AES") ;  }
}

SecretProperties

复制代码
@Configurationpublic class SecretConfig {    @Bean  @ConditionalOnMissingBean(SecretProcess.class)  public SecretProcess secretProcess() {    return new AESAlgorithm() ;  }    @Component  @ConfigurationProperties(prefix = "secret")  public static class SecretProperties {        private Boolean enabled ;    private String key ;
    public Boolean getEnabled() {      return enabled;    }
    public void setEnabled(Boolean enabled) {      this.enabled = enabled;    }
    public String getKey() {      return key;    }
    public void setKey(String key) {      this.key = key;    }      }  }

配置文件中如下配置:

复制代码
secret:  key: aaaabbbbccccdddd #密钥  enabled: true #是否开启加解密功能

在项目中可能不是所有的方法都要进行数据的加密解密出来,所以接下来定义一个注解,只有添加有该注解的Controller类或是具体接口方法才进行数据的加密解密,如下:

SIProtection

复制代码
@Target({ElementType.METHOD, ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Mapping@Documentedpublic @interface SIProtection {
}

对请求内容进行解密出来,通过RequestBodyAdvice

DecryptRequestBodyAdivce

复制代码
@ControllerAdvice@ConditionalOnProperty(name = "secret.enabled", havingValue = "true")public class DecryptRequestBodyAdivce extends RequestBodyAdviceAdapter {
  @Resource  private SecretProcess secretProcess ;    @Override  public boolean supports(MethodParameter methodParameter, Type targetType,      Class<? extends HttpMessageConverter<?>> converterType) {    return methodParameter.getMethod().isAnnotationPresent(SIProtection.class)         || methodParameter.getMethod().getDeclaringClass().isAnnotationPresent(SIProtection.class) ;  }
  @Override  public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,      Class<? extends HttpMessageConverter<?>> converterType) throws IOException {    String body = secretProcess.decrypt(inToString(inputMessage.getBody())) ;    return new HttpInputMessage() {      @Override      public HttpHeaders getHeaders() {        return inputMessage.getHeaders();      }      @Override      public InputStream getBody() throws IOException {        return new ByteArrayInputStream(body.getBytes()) ;      }    } ;  }    private String inToString(InputStream is) {    byte[] buf = new byte[10 * 1024] ;    int leng = -1 ;    StringBuilder sb = new StringBuilder() ;    try {      while ((leng = is.read(buf)) != -1) {        sb.append(new String(buf, 0, leng)) ;      }      return sb.toString() ;    } catch (IOException e) {      throw new RuntimeException(e) ;    }  }
}

注意这里的:@ConditionalOnProperty(name = "secret.enabled", havingValue = "true")注解,只有开启了加解密功能才会生效。注意这里的supports方法

对响应内容加密出来

EncryptResponseBodyAdivce

复制代码
@ControllerAdvice@ConditionalOnProperty(name = "secret.enabled", havingValue = "true")public class EncryptResponseBodyAdivce implements ResponseBodyAdvice<Object>  {
  @Resource  private SecretProcess secretProcess ;
  @Override  public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {    return returnType.getMethod().isAnnotationPresent(SIProtection.class)         || returnType.getMethod().getDeclaringClass().isAnnotationPresent(SIProtection.class) ;  }
  @Override  public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,      Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request,      ServerHttpResponse response) {    if (body == null) {      return body ;    }    try {      String jsonStr = new ObjectMapper().writeValueAsString(body) ;      return secretProcess.encrypt(jsonStr) ;    } catch (Exception e) {      throw new RuntimeException(e) ;    }  }}

Controller接口

复制代码
@PostMapping("/save")@SIProtectionpublic R save(@RequestBody Users users) {  return R.success(usersService.save(users)) ;} // 这对具体方法进行加解密
@RestController@RequestMapping("/users")@SIProtection public class UsersController { // 对该Controller中的所有方法进行加解密处理}

前端

引入第三方插件:crypto-js

工具方法加解密:

复制代码
/** * 加密方法 * @param data 待加密数据 * @returns {string|*} */encrypt (data) {  let key = CryptoJS.enc.Utf8.parse(Consts.Secret.key)  if (typeof data === 'object') {    data = JSON.stringify(data)  }  let plainText = CryptoJS.enc.Utf8.parse(data)  let secretText = CryptoJS.AES.encrypt(plainText, key, {mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7}).ciphertext.toString()  return secretText},/** * 解密数据 * @param data 待解密数据 */decrypt (data) {  let key = CryptoJS.enc.Utf8.parse(Consts.Secret.key)  let secretText = CryptoJS.enc.Hex.parse(data)  let encryptedBase64Str = CryptoJS.enc.Base64.stringify(secretText)  let result = CryptoJS.AES.decrypt(encryptedBase64Str, key, {mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7}).toString(CryptoJS.enc.Utf8)  return JSON.parse(result)}

配置:

复制代码
let Consts = {  Secret: {    key: 'aaaabbbbccccdddd', // 必须16位(前后端要一致,密钥)    urls: ['/users/save']  }}export default Consts

这里的urls表示对那些请求进行拦截出来(加解密),这里也可以配置 "*" 表示对所有的请求出来。

axios请求前和响应后对数据进行加解密出来:

发送请求前:

复制代码
axios.interceptors.request.use((config) => {  let uri = config.url  if (uri.includes('?')) {    uri = uri.substring(0, uri.indexOf('?'))  }  if (window.cfg.enableSecret === '1' && config.data && (Consts.Secret.urls.indexOf('*') > -1 || Consts.Secret.urls.indexOf(uri) > -1)) {    let data = config.data    let secretText = Utils.Secret.encrypt(data)    config.data = secretText  }  return config}, (error) => {  let errorMessage = '请求失败'  store.dispatch(types.G_SHOW_ALERT, {title: '请求失败', content: errorMessage, showDetail: false, detailContent: String(error)})  return Promise.reject(error)})axios.interceptors.response.use((response) => {  let uri = response.config.url  if (uri.includes('?')) {    uri = uri.substring(0, uri.indexOf('?'))  }  if (window.cfg.enableSecret === '1' && response.data && (Consts.Secret.urls.indexOf('*') > -1 || Consts.Secret.urls.indexOf(uri) > -1)) {    let data = Utils.Secret.decrypt(response.data)    if (data) {      response.data = data    }  }  return response}, (error) => {  console.error(`test interceptors.response is in, ${error}`)  return Promise.reject(error)})

这里的 window.cfg.enableSecret 配置是我自己项目中有个配置文件配置是否开启,这个大家可以根据自己的环境来实现。

测试:

这里可以看到前端发起的请求内容已经被加密了

响应内容:

完毕!!!

相关推荐
octopus_c2 小时前
数据结构:堆(Heap)详解
c语言·数据结构·算法
爱吃苹果的梨叔4 小时前
训练集群网络中断怎么办?智算机房的 Console 串口排障方案
网络·python·智能路由器·php
kk”7 小时前
Linux的组和权限管理
linux·运维·服务器
SeaDhdhdhdhdh8 小时前
MCP Server 搭建与使用指南
java·ai·agent·mcp
YH55269848 小时前
GPT‑5.6 Sol 原本支持 1M 上下文,Codex 现已放开此前限制,如何看待这次调整?
java·jvm·人工智能·gpt·算法·chatgpt
三垣网安8 小时前
一次学校系统水平越权漏洞的实战测试记录
安全
智购科技智能售货柜8 小时前
2026自动售货机整机可靠性测试:从高低温交变到EMC电磁兼容的认证工程实践~YH
运维·服务器·数据库·人工智能·物联网
AI_小站9 小时前
刚面完百度的 Agent 开发岗,我才发现:世界就是个巨大的草台班子
java·开发语言·人工智能·spring·百度·langchain
xiaohaiAIgeo9 小时前
【2026年】医药研发实验室GLP的通风设计要求:稳定性可验证性与合规性
网络·人工智能·科普知识
许彰午9 小时前
03-三种开发模式
java·架构