前端接口安全与性能优化实战

文章目录

接口请求封装

请求拦截器

请求前

设置请求头防止重放攻击:

X-Timestamp 时间戳

后端代码

java 复制代码
/**
     * 验证时间戳有效性(例如5分钟内有效)
     */
    private boolean isValidTimestamp(String timestampStr, HttpServletResponse response) throws IOException {
        if (timestampStr == null) {
            sendErrorResponse(response, "缺少时间戳");
            return false;
        }

        try {
            long timestamp = Long.parseLong(timestampStr);
            long currentTime = System.currentTimeMillis();
            long timeDiff = Math.abs(currentTime - timestamp);

            if (timeDiff > 5 * 60 * 1000) {
                sendErrorResponse(response, "请求已过期");
                return false;
            }
        } catch (NumberFormatException e) {
            sendErrorResponse(response, "时间戳格式错误");
            return false;
        }

        return true;
    }
X-Nonce 长度16位的随机字符串
java 复制代码
/**
     * 验证随机字符串有效性
     *
     * @param nonce
     * @param response
     * @return
     * @throws IOException
     */
    private boolean isValidNonce(String nonce, HttpServletResponse response) throws IOException {
        if (nonce == null || nonce.isEmpty()) {
            sendErrorResponse(response, "缺少随机字符串");
            return false;
        }

        // 验证nonce是否已使用(防止重放攻击)
        String nonceKey = "nonce:" + nonce;
        Boolean isNewNonce = redisTemplate.opsForValue().setIfAbsent(nonceKey, "1", 5, TimeUnit.MINUTES);
        if (Boolean.FALSE.equals(isNewNonce)) {
            sendErrorResponse(response, "重复的请求");
            return false;
        }
        return true;
    }
X-Signateure api请求签名
js 复制代码
// 生成请求签名
export function generateSignature(secretKey, method, url, timestamp, nonce, body = '') {
  const key = String(secretKey || '')
  const httpMethod = String(method || '').toUpperCase()
  const requestUrl = String(url || '')
  const time = String(timestamp || '')
  const nonceStr = String(nonce || '')

  const finalUrl = processUrl(requestUrl)
  const bodyStr = serializeBody(httpMethod, body)
  const data = buildSignatureData(httpMethod, finalUrl, time, nonceStr, bodyStr)

  console.log('Signature data:', data)
  const signature = sha256.hmac(key, data)
  console.log('Generated signature:', signature)
  return signature
}
java 复制代码
/**
 * 验证签名有效性
 *
 * @param signature
 * @param response
 * @param dataForSigning
 * @return
 * @throws IOException
 */
private boolean isValidSignature(String signature, HttpServletResponse response, String dataForSigning) throws IOException {
    if (signature == null || signature.isEmpty()) {
        sendErrorResponse(response, "缺少签名");
        return true;
    }

    // 构造待签名数据(不包含请求体)
    log.info("Data for signing (multipart): {}", dataForSigning);

    // 计算签名
    String computedSignature = computeSignature(dataForSigning, clientSecret);
    log.info("Computed signature: {}", computedSignature);
    log.info("Received signature: {}", signature);

    // 验证签名
    if (!computedSignature.equals(signature)) {
        sendErrorResponse(response, "无效的签名");
        return true;
    }
    return false;
}
java 复制代码
/**
 * 计算HMAC-SHA256签名
 */
private String computeSignature(String data, String secret) {
    try {
        Mac mac = Mac.getInstance("HmacSHA256");
        SecretKeySpec secretKeySpec = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
        mac.init(secretKeySpec);
        byte[] signatureBytes = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
        return bytesToHex(signatureBytes);
    } catch (Exception e) {
        log.error("签名计算失败", e);
        throw new RuntimeException("签名计算失败", e);
    }
}
X-Client-ID 客户端id

​ 如果token存在并且请求路径不是白名单则添加认证头Authorization

​ 为每个请求生成唯一标识

js 复制代码
const requestId = `${config.url}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
config.requestId = requestId

​ 只有当配置中 showLoading 不为 false 时才显示 loading或者进度条

​ 记录请求开始时间

请求失败

​ 请求错误时关闭 loading或者进度条

​ 关闭对应请求的 loading或者进度条

​ ...

响应拦截器

成功

​ 响应成功时关闭对应请求的 loading或者进度条

​ 计算接口耗时

​ 如果response.data instanceof Blob return resopnse.data

​ 如果业务状态码表示成功则返回response.data.data

​ 其他情况:业务码[code]?.()

失败

​ 响应错误时关闭对应请求的 loading或者进度条

​ 计算接口耗时(即使出错也记录)

​ 处理401状态码清除token跳转到登录页或刷新token,拿短token换长token

​ 处理其他错误:httpErrorCode[status]?.()

缓存接口数据防止重复请求

职责链模式

缓存模块-》防止重复提交模块-》请求模块》请求处理模块

用时间切片优化项目速度

webworker执行耗时任务

注意事项

vue2项目改用vite

删除node_modules

删除package.json中的开发依赖

删除babel配置文件

删除package.json中的eslint配置

删除postcss配置

从资源加载优化角度加速项目

浏览器缓存的两种机制

强缓存

优点:一定期限内,根本不用向服务器询问,一定是拿到缓存。速度最快

缺点:如何不配合hash,无法感知到文件更新

协商缓存

优点:能够保证每次前端打包后丢上服务器资源一定更新

缺点:只要文件是新放的,即使文件内容没变也不缓存

相关推荐
Cobyte19 小时前
3.响应式系统基础:从发布订阅模式的角度理解 Vue2 的数据响应式原理
前端·javascript·vue.js
竹林81819 小时前
从零到一:在React前端中集成The Graph查询Uniswap V3池数据实战
前端·javascript
上海云盾-小余19 小时前
DDoS 攻击全解析:常见类型识别与分层防御思路
网络协议·tcp/ip·安全·ddos
Mintopia19 小时前
别再迷信"优化":大多数性能问题根本不在代码里
前端
倾颜19 小时前
接入 MCP,不一定要先平台化:一次 AI Runtime 的实战取舍
前端·后端·mcp
军军君0119 小时前
Three.js基础功能学习十八:智能黑板实现实例五
前端·javascript·vue.js·3d·typescript·前端框架·threejs
恋猫de小郭19 小时前
Android 上为什么主题字体对 Flutter 不生效,对 Compose 生效?Flutter 中文字体问题修复
android·前端·flutter
Moment19 小时前
AI全栈入门指南:一文搞清楚NestJs 中的 Controller 和路由
前端·javascript·后端
禅思院19 小时前
前端架构演进:基于AST的常量模块自动化迁移实践
前端·vue.js·前端框架
程序员马晓博19 小时前
前端并发治理:从 Token 刷新聊起,一个 Promise 就够了
前端·javascript