apache/shenyu · error · ResponsiveException

401

401

Error message

${result.getReason()}

What it means

The sign plugin verifies the request signature by rewriting and inspecting the request body via signVerifyWithBody; when verification fails it throws a ResponsiveException carrying HTTP-like code 401 and the specific failure reason computed by the verifier (e.g. missing timestamp, expired window, wrong signature, missing appKey header). The plugin's onErrorResume converts it into a failed response so the client receives the reason directly. Any message text is produced by the verification logic, not a fixed constant.

Solutions

  1. Compare the reason field in the 401 response with your client's signing algorithm (usually MD5/HMAC of sorted params + secret) and fix the client-side sign computation.
  2. Synchronize client clock with the gateway or increase the allowed timestamp window in the sign plugin config if the reason indicates an expired timestamp.
  3. Verify the appId/secret configured in the sign plugin matches what the client uses; rotate-sync both sides after key changes.
  4. Ensure the sign headers/parameters (appKey, timestamp, sign, and any required fields per plugin config) are present on every request.
  5. If signatures fail only for POST/PUT bodies, confirm the client signs the exact raw body bytes that are transmitted (no re-serialization).
Defensive patterns

Strategy: try-catch

Validate before calling

// Client-side pre-check before sending
if (appKey == null || timestamp == null || sign == null) {
    throw new IllegalStateException("sign plugin requires appKey, timestamp and sign parameters");
}

Try / catch

try {
    // send signed request
} catch (HttpStatusCodeException e) {
    if (e.getRawStatusCode() == 401) {
        String reason = parseReason(e.getResponseBodyAsString()); // fix client sign per reason
    }
}

Prevention

When it happens

Trigger: A client calls a path protected by the sign plugin with an invalid or missing signature: wrong sign computed over the body, expired/absent timestamp, unknown appId/appKey, or missing required sign headers. The exception is thrown inside the ServerWebExchangeUtils.rewriteRequestBody callback in doExecute when VerifyResult.isSuccess() is false.

Common situations: Client SDKs computing the signature over a different body string than what reaches the gateway (content-type/charset differences, whitespace, re-serialized JSON); clock drift between client and gateway exceeding the timestamp window; stale or rotated secret keys; requests without the sign headers.

Related errors


AI-assisted analysis of apache/shenyu@567142e072 (2026-09-12). Data as JSON: /api/errors/c56bb3333e129e1c. Report an issue: GitHub.

Appendix: source

Thrown at shenyu-plugin/shenyu-plugin-security/shenyu-plugin-sign/src/main/java/org/apache/shenyu/plugin/sign/SignPlugin.java:88

    @Override
    protected Mono<Void> doExecute(final ServerWebExchange exchange, final ShenyuPluginChain chain, final SelectorData selectorData, final RuleData rule) {
        SignRuleHandler ruleHandler = SignPluginDataHandler.CACHED_HANDLE.get().obtainHandle(CacheKeyUtils.INST.getKey(rule));
        if (ObjectUtils.isEmpty(ruleHandler) || !ruleHandler.getSignRequestBody()) {
            VerifyResult result = signService.signatureVerify(exchange);
            if (result.isFailed()) {
                return WebFluxResultUtils.failedResult(ShenyuResultEnum.SIGN_IS_NOT_PASS.getCode(),
                        result.getReason(), exchange);
            }
            return chain.execute(exchange);
        }

        return ServerWebExchangeUtils.rewriteRequestBody(exchange, messageReaders, body -> {
            VerifyResult result = signVerifyWithBody(body, exchange);
            if (result.isSuccess()) {
                return Mono.just(body);
            }
            throw new ResponsiveException(ShenyuResultEnum.SIGN_IS_NOT_PASS.getCode(), result.getReason(), exchange);
        }).flatMap(chain::execute)
                .onErrorResume(error -> {
                    if (error instanceof ResponsiveException) {
                        return WebFluxResultUtils.failedResult((ResponsiveException) error);
                    }
                    return Mono.error(error);
                });
    }

    private VerifyResult signVerifyWithBody(final String originalBody, final ServerWebExchange exchange) {
        // get url params
        return signService.signatureVerify(exchange, originalBody);
    }
}

View on GitHub (pinned to 567142e072)