{"record":{"id":"c56bb3333e129e1c","repo":"apache/shenyu","slug":"401-result-getreason","errorCode":"401","errorMessage":"${result.getReason()}","messagePattern":"\\$\\{result\\.getReason\\(\\)\\}","errorType":"error_code","errorClass":"ResponsiveException","httpStatus":401,"severity":"error","filePath":"shenyu-plugin/shenyu-plugin-security/shenyu-plugin-sign/src/main/java/org/apache/shenyu/plugin/sign/SignPlugin.java","lineNumber":88,"sourceCode":"\n    @Override\n    protected Mono<Void> doExecute(final ServerWebExchange exchange, final ShenyuPluginChain chain, final SelectorData selectorData, final RuleData rule) {\n        SignRuleHandler ruleHandler = SignPluginDataHandler.CACHED_HANDLE.get().obtainHandle(CacheKeyUtils.INST.getKey(rule));\n        if (ObjectUtils.isEmpty(ruleHandler) || !ruleHandler.getSignRequestBody()) {\n            VerifyResult result = signService.signatureVerify(exchange);\n            if (result.isFailed()) {\n                return WebFluxResultUtils.failedResult(ShenyuResultEnum.SIGN_IS_NOT_PASS.getCode(),\n                        result.getReason(), exchange);\n            }\n            return chain.execute(exchange);\n        }\n\n        return ServerWebExchangeUtils.rewriteRequestBody(exchange, messageReaders, body -> {\n            VerifyResult result = signVerifyWithBody(body, exchange);\n            if (result.isSuccess()) {\n                return Mono.just(body);\n            }\n            throw new ResponsiveException(ShenyuResultEnum.SIGN_IS_NOT_PASS.getCode(), result.getReason(), exchange);\n        }).flatMap(chain::execute)\n                .onErrorResume(error -> {\n                    if (error instanceof ResponsiveException) {\n                        return WebFluxResultUtils.failedResult((ResponsiveException) error);\n                    }\n                    return Mono.error(error);\n                });\n    }\n\n    private VerifyResult signVerifyWithBody(final String originalBody, final ServerWebExchange exchange) {\n        // get url params\n        return signService.signatureVerify(exchange, originalBody);\n    }\n}\n","sourceCodeStart":70,"sourceCodeEnd":103,"githubUrl":"https://github.com/apache/shenyu/blob/567142e07261b3e615ae8850b30f4421f455cc5d/shenyu-plugin/shenyu-plugin-security/shenyu-plugin-sign/src/main/java/org/apache/shenyu/plugin/sign/SignPlugin.java#L70-L103","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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)."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// Client-side pre-check before sending\nif (appKey == null || timestamp == null || sign == null) {\n    throw new IllegalStateException(\"sign plugin requires appKey, timestamp and sign parameters\");\n}","typeGuard":null,"tryCatchPattern":"try {\n    // send signed request\n} catch (HttpStatusCodeException e) {\n    if (e.getRawStatusCode() == 401) {\n        String reason = parseReason(e.getResponseBodyAsString()); // fix client sign per reason\n    }\n}","preventionTips":["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."],"tags":["authentication","signature","gateway-plugin","http-401"],"backgroundTag":"signature-verification-failed","analyzedSha":"567142e07261b3e615ae8850b30f4421f455cc5d","analyzedAt":"2026-09-12T10:08:21.293Z","contentChangedAt":"2026-09-12T10:08:21.293Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}