惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

S
Schneier on Security
P
Proofpoint News Feed
Apple Machine Learning Research
Apple Machine Learning Research
WordPress大学
WordPress大学
博客园 - Franky
V
V2EX
爱范儿
爱范儿
J
Java Code Geeks
小众软件
小众软件
Last Week in AI
Last Week in AI
The Cloudflare Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Hugging Face - Blog
Hugging Face - Blog
T
The Blog of Author Tim Ferriss
酷 壳 – CoolShell
酷 壳 – CoolShell
The Register - Security
The Register - Security
GbyAI
GbyAI
Vercel News
Vercel News
Y
Y Combinator Blog
腾讯CDC
F
Fortinet All Blogs
I
InfoQ
N
Netflix TechBlog - Medium
B
Blog RSS Feed
D
Docker
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
量子位
博客园 - 司徒正美
阮一峰的网络日志
阮一峰的网络日志
The GitHub Blog
The GitHub Blog
Microsoft Security Blog
Microsoft Security Blog
V
Visual Studio Blog
博客园 - 三生石上(FineUI控件)
宝玉的分享
宝玉的分享
Blog — PlanetScale
Blog — PlanetScale
H
Help Net Security
云风的 BLOG
云风的 BLOG
A
About on SuperTechFans
Scott Helme
Scott Helme
T
Tor Project blog
U
Unit 42
Google Online Security Blog
Google Online Security Blog
PCI Perspectives
PCI Perspectives
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
NISL@THU
NISL@THU
D
Darknet – Hacking Tools, Hacker News & Cyber Security
aimingoo的专栏
aimingoo的专栏
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Stack Overflow Blog
Stack Overflow Blog
Security Archives - TechRepublic
Security Archives - TechRepublic

博客园 - KLAPT

Gateway 网关 CodeX =>Skills Redis 内存满了怎么处理 SpringBoot 默认配置修改 JWT 续签 Access Token + Refresh Token 双 Token claudeCode 命令 MyBatis 的 Mapper 接口 AI | CC GUI 集成 IDEA 完整教程 在IDEA中使用Claude Code IDEA中使用CodeX MyBatisPlus解决大数据量查询慢问题 idea 中的 claude code Token Dubbo 和 Spring Cloud Gateway的区别 Transactional 注解中propagation 掌握 Spring 框架这 10 个扩展点 Spring Boot/Cloud 中 bootstrap.yml 与 application.yml SpringBoot 实现 DOCX 转 PDF 微服务Token鉴权设计的几种方案 进程、线程、协程 RSA 加密 Java二维码 ntp服务端和客户端 Chronyd与NTP chronyd 作为服务器时钟 chrony sudo命令和su 的区别 java -cp 和 java -jar Maven 项目打包:实现业务代码与第三方依赖分离 达梦数据库创建用户 梦数据库新增大字段报错问题 达梦数据库操作 MySQL UPDATE多表关联更新 达梦数据库 为HTTP POST请求设置请求体 在Java中调用第三方接口并返回第三方页面 Java调用第三方接口的方法 Nginx 之Rewrite 使用详解 linux 命令 Spring Boot项目中集成Spring Security OAuth2和Apache Shiro
SpringBoot 快速实现 api 加密
KLAPT · 2026-05-27 · via 博客园 - KLAPT

思路

   客户端发送请求时,先把业务 JSON 做 AES 加密,再用 appId、timestamp、nonce、cipherText 拼一个待签名串,用 HmacSHA256 算签名。服务端收到请求后,先验签,再看时间戳,再解密。响应出去时反过来,把响应体再加密一层。

@Data
public class SecureApiRequest {
    private String appId;
    private Long timestamp;
    private String nonce;
    private String data;
    private String sign;
}

public final class SignTextBuilder {

    public static String build(String appId, Long timestamp, String nonce, String data) {
        return "appId=" + appId
                + "&timestamp=" + timestamp
                + "&nonce=" + nonce
                + "&data=" + data;
    }
}

加解密工具==========

public final class AesCipher {

    public static String encrypt(String plainText, String key, String iv) {
        try {
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES");
            IvParameterSpec ivSpec = new IvParameterSpec(iv.getBytes(StandardCharsets.UTF_8));
            cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
            byte[] bytes = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
            return Base64.getEncoder().encodeToString(bytes);
        } catch (Exception e) {
            throw new IllegalStateException("aes encrypt fail", e);
        }
    }

    public static String decrypt(String cipherText, String key, String iv) {
        try {
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES");
            IvParameterSpec ivSpec = new IvParameterSpec(iv.getBytes(StandardCharsets.UTF_8));
            cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
            byte[] bytes = Base64.getDecoder().decode(cipherText);
            return new String(cipher.doFinal(bytes), StandardCharsets.UTF_8);
        } catch (Exception e) {
            throw new IllegalStateException("aes decrypt fail", e);
        }
    }
}

====签名工具======

public final class HmacSigner {

    public static String sign(String text, String secret) {
        try {
            Mac mac = Mac.getInstance("HmacSHA256");
            mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
            byte[] bytes = mac.doFinal(text.getBytes(StandardCharsets.UTF_8));
            StringBuilder sb = new StringBuilder();
            for (byte b : bytes) {
                sb.append(String.format("%02x", b));
            }
            return sb.toString();
        } catch (Exception e) {
            throw new IllegalStateException("sign fail", e);
        }
    }
}

==================HandlerInterceptor======

HandlerInterceptor拦截器接口

HandlerInterceptor是 Spring 框架提供的一个拦截器接口,用于在请求处理过程中拦截和处理请求。

当一个请求到达 Spring MVC 的控制器之前或之后,拦截器可以对请求进行预处理、后处理或者进行拦截操作。

通过实现HandlerInterceptor接口,可以自定义拦截器并注册到 Spring MVC 的拦截器链中。

拦截器可以用于实现一些通用的功能,如权限验证、日志记录、跨域处理等,以及对请求进行修改、重定向或拦截等操作。

HandlerInterceptor接口定义了三个方法,分别是:

  1. preHandle:在请求处理之前被调用。可以进行一些预处理操作,如身份验证、参数校验等。如果返回true,则继续执行后续的拦截器或请求处理器;如果返回false,则中断请求处理流程。
  2. postHandle:在请求处理之后、视图渲染之前被调用。可以对请求的结果进行后处理,如添加额外的模型数据或修改视图等。
  3. afterCompletion:在整个请求处理完成后被调用,即在视图渲染完成后调用。可以进行一些资源清理操作或日志记录等。

通过实现HandlerInterceptor接口,并在配置文件中进行注册,可以将自定义的拦截器应用到 Spring MVC 的请求处理流程中。

拦截器按照配置的顺序依次执行,并可以对请求进行拦截、修改或处理,实现特定的业务逻辑或功能需求。

=====

@Component
public class ApiSecurityInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String body = request.getReader().lines().collect(Collectors.joining());
        SecureApiRequest req = new ObjectMapper().readValue(body, SecureApiRequest.class);

        ApiClient client = loadClient(req.getAppId());
        String plainSignText = SignTextBuilder.build(req.getAppId(), req.getTimestamp(), req.getNonce(), req.getData());
        String localSign = HmacSigner.sign(plainSignText, client.getSignSecret());

        if (!localSign.equals(req.getSign())) {
            throw new RuntimeException("sign invalid");
        }

        long now = System.currentTimeMillis();
        if (Math.abs(now - req.getTimestamp()) > 5 * 60 * 1000) {
            throw new RuntimeException("request expired");
        }

        String cacheKey = "api:nonce:" + req.getAppId() + ":" + req.getNonce();
        if (!saveNonce(cacheKey, 5 * 60)) {
            throw new RuntimeException("duplicate request");
        }

        String json = AesCipher.decrypt(req.getData(), client.getAesKey(), client.getIv());
        request.setAttribute("plainBody", json);
        return true;
    }
}