Spring Boot Request Signing for Decoupled Frontends and Backends: Vue/Axios Signing and Interceptor Verification

This article focuses on a request header signing mechanism for Spring Boot projects with decoupled frontends and backends: the frontend applies signatures uniformly in an Axios request interceptor, and the backend verifies them uniformly in a Spring Boot interceptor. This approach reduces the risk of forged API calls, replay attacks, and parameter tampering. Keywords: Spring Boot, request signing, frontend-backend separation.

The technical specification snapshot outlines the implementation baseline

Parameter Description
Language Java, JavaScript
Frontend framework Vue + Axios
Backend framework Spring Boot
Signature payload secret + timestamp + method + url
Hash algorithm MD5 (upgradeable to HMAC-SHA256)
Expiration policy Timestamp expires after 5 minutes
Core dependencies js-md5, Spring Web, Hutool
Star count Not provided in the source content

This solution decouples request signing across frontend and backend while enabling centralized governance

In a frontend-backend separation architecture, relying only on tokens does not cover every request tampering scenario. If an attacker reuses an existing request or forges an API call, the system can still be exposed to risk.

The goal of request signing is to attach a verifiable digest to every request. The backend accepts only requests that match the agreed rules, which shifts security validation forward to the gateway or entry layer before business processing begins.

The signing rules must remain fully consistent across frontend and backend

A recommended minimal string to sign includes four parts: secret key, timestamp, HTTP method, and request URL. It is simple, easy to implement, and well suited for quickly integrating into internal admin or business systems.

// signature.js
// The frontend and backend must use the same secret and separator
export const SIGN_SECRET = 'your-strong-secret-key-2026';
export const SIGN_SEPARATOR = '|';

This configuration defines the fixed constants in the signing rules and serves as the foundation for frontend-backend consistency.

The frontend should apply signatures uniformly in an Axios interceptor

Do not manually construct signatures in every API method on the frontend. That approach easily leads to missing signatures, inconsistent rules, and high maintenance cost. The correct approach is to centralize signing in a request interceptor.

When the interceptor runs, it reads the current request method and path dynamically, generates the sign value with the timestamp, and injects both into the request headers.

import axios from 'axios';
import Md5 from 'js-md5';
import { SIGN_SECRET, SIGN_SEPARATOR } from '@/config/signature';

const service = axios.create({
  baseURL: process.env.VUE_APP_BASE_API,
  timeout: 10000
});

service.interceptors.request.use(config => {
  const timestamp = Date.now().toString(); // Generate a millisecond-level timestamp
  const method = (config.method || 'GET').toUpperCase(); // Normalize the HTTP method casing
  const url = config.url || ''; // Get the current request path

  const stringToSign = [SIGN_SECRET, timestamp, method, url].join(SIGN_SEPARATOR);
  const sign = Md5(stringToSign).toUpperCase(); // Generate an uppercase MD5 digest

  config.headers['timestamp'] = timestamp; // Inject the timestamp
  config.headers['sign'] = sign; // Inject the signature value
  return config;
}, error => Promise.reject(error));

export default service;

This code automatically adds the timestamp and sign headers to all outbound requests.

The backend should verify signatures uniformly at the entry point with a Spring interceptor

Backend signature verification should do three things: read the values, validate the timestamp window, and recompute the signature. If any of these checks fails, reject the request immediately so invalid traffic never reaches the business layer.

Compared with writing validation logic in each controller endpoint, HandlerInterceptor is a better fit for cross-cutting security controls and aligns better with maintainability requirements.

package com.example.framework.interceptor;

import cn.hutool.core.util.StrUtil;
import com.example.common.exception.ServiceException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
public class SignatureInterceptor implements HandlerInterceptor {

    @Value("${signature.secret}")
    private String signSecret;

    private static final String SIGN_SEPARATOR = "|";
    private static final long EXPIRE_TIME = 5 * 60 * 1000;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        String timestamp = request.getHeader("timestamp"); // Read the timestamp from the request header
        String clientSign = request.getHeader("sign"); // Read the client-side signature
        String method = request.getMethod(); // Get the HTTP method
        String url = request.getRequestURI(); // Get the request path

        if (StrUtil.hasBlank(timestamp, clientSign)) {
            throw new ServiceException("Missing signature parameters");
        }

        long currentTime = System.currentTimeMillis();
        long requestTime = Long.parseLong(timestamp); // Convert the request time
        if (Math.abs(currentTime - requestTime) > EXPIRE_TIME) {
            throw new ServiceException("Request has expired");
        }

        String stringToSign = String.join(SIGN_SEPARATOR, signSecret, timestamp, method, url);
        String serverSign = StrUtil.upper(StrUtil.md5(stringToSign)); // Recompute the signature on the backend

        if (!serverSign.equals(clientSign)) {
            throw new ServiceException("Signature verification failed");
        }

        return true;
    }
}

This code completes full signature verification before the request enters business processing, making it a critical backend entry point for anti-forgery protection.

Interceptor registration must explicitly define which endpoints are excluded

Endpoints such as login, CAPTCHA, health checks, and some third-party callback interfaces are usually not suitable for mandatory unified signature verification. Otherwise, system availability can suffer.

package com.example.framework.config;

import com.example.framework.interceptor.SignatureInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
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 {

    @Autowired
    private SignatureInterceptor signatureInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(signatureInterceptor)
                .addPathPatterns("/**") // Intercept all business requests
                .excludePathPatterns("/login", "/logout", "/captchaImage") // Exclude unsigned endpoints
                .order(1); // Execute with high priority
    }
}

This configuration declares which endpoints require signature verification and which should be excluded.

The configuration file should externalize the secret instead of hardcoding it

Read the secret from a configuration center, environment variable, or container injection. If you hardcode it in the repository, you increase the risk of leakage and make multi-environment management harder.

signature:
  secret: your-strong-secret-key-2026

This configuration separates the signing secret from the code and makes centralized production management easier.

This minimal implementation is suitable for rapid adoption but still leaves room for stronger protection

The current approach can prevent basic forgery and some replay attempts. However, if the request parameters are not included in the signature, an attacker may still modify the query string or body under the same URL.

A safer approach is to sort parameters by key and include them in the string to sign. You should also upgrade MD5 to HMAC-SHA256 and assign independent secrets per user or application.

Security enhancements should prioritize replay prevention and tamper resistance

First, add a random nonce and cache it briefly on the server side. This can significantly reduce the probability of duplicate submissions and replay attacks. Second, include a body digest in the signature to improve request integrity protection.

Third, standardize the exception response format so the frontend does not only receive ambiguous 500 errors. This makes troubleshooting easier and also helps gateways and monitoring systems identify exception types.

// Expose custom response headers through CORS so the browser can read them
config.setExposedHeaders(Arrays.asList("timestamp", "sign"));

This configuration solves the issue where custom request or response headers are not visible in cross-origin scenarios.

The images mainly show platform UI elements rather than technical architecture

Run button icon AI Visual Insight: This image only shows the style of a code execution entry button, indicating that the page supports one-click execution. It does not present any signature algorithm flow, request structure, or deployment details.

Ad placement image AI Visual Insight: This image is an advertising asset on the page and does not contain implementation details for the request signing approach described in this article, so it can be ignored in technical documentation.

This signing mechanism works well as the first layer of defense for internal API security

It does not replace authentication, authorization, or HTTPS, but it is highly effective for validating whether a request was initiated by a legitimate client according to the agreed rules.

In real-world engineering, combine signature verification with JWT, rate limiting, audit logs, and global exception handling to form a more complete API security model.

FAQ

1. Can request signing replace JWT or Session-based authentication?

No. Request signing addresses request integrity and source consistency, while JWT or Session-based authentication addresses identity and authorization. They have different responsibilities and should usually be used together.

2. Why does signature verification still fail even when the frontend and backend use the same rules?

The four most common causes are: inconsistent URL values, inconsistent HTTP method casing, expired timestamps, and different secrets between frontend and backend. When troubleshooting, print the raw stringToSign first.

3. Why is MD5 not recommended for production environments?

MD5 is simple to implement but relatively weak from a security perspective. It is more suitable for demos or low-risk internal systems. If your system is exposed to the public internet, prioritize HMAC-SHA256 together with a nonce and parameter signing.

[AI Readability Summary]

This article refactors a request header signing approach for frontend-backend separation scenarios: the frontend uses an Axios interceptor to generate timestamp and sign consistently, while the backend uses a Spring Boot interceptor to validate expiration and verify the signature. It also covers security hardening, CORS handling, and solutions to common implementation issues.