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
- 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.
- Synchronize client clock with the gateway or increase the allowed timestamp window in the sign plugin config if the reason indicates an expired timestamp.
- Verify the appId/secret configured in the sign plugin matches what the client uses; rotate-sync both sides after key changes.
- Ensure the sign headers/parameters (appKey, timestamp, sign, and any required fields per plugin config) are present on every request.
- 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
- Sign the exact raw body bytes that are transmitted; never re-serialize before sending.
- Keep client clocks NTP-synced with the gateway.
- Rotate appKey/secret on both sides simultaneously.
- Write an integration test that runs a real signed request through the gateway.
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
- Cannot find the context path(AppName) from the request url
- websocket sync token is invalid
- userName is null
- userName( ) can not be found.
- user( ) is disabled.
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)