{"record":{"id":"f3f1ad18abd42f7a","repo":"apache/shenyu","slug":"websocket-sync-token-is-invalid","errorCode":null,"errorMessage":"websocket sync token is invalid","messagePattern":"websocket sync token is invalid","errorType":"validation","errorClass":"ShenyuException","httpStatus":null,"severity":"error","filePath":"shenyu-admin/src/main/java/org/apache/shenyu/admin/listener/websocket/WebsocketConfigurator.java","lineNumber":105,"sourceCode":"    @Override\n    public void onStartup(final ServletContext servletContext) {\n        int messageMaxSize = getWebsocketSyncProperties().getMessageMaxSize();\n        if (messageMaxSize > 0) {\n            servletContext.setInitParameter(TEXT_BUFFER_SIZE_SERVLET_CONTEXT_INIT_PARAM,\n                    String.valueOf(messageMaxSize));\n            servletContext.setInitParameter(BINARY_BUFFER_SIZE_SERVLET_CONTEXT_INIT_PARAM,\n                    String.valueOf(messageMaxSize));\n        }\n    }\n\n    private void checkSyncToken(final HandshakeRequest request) {\n        String configuredToken = getWebsocketSyncProperties().getToken();\n        if (StringUtils.isBlank(configuredToken)) {\n            throw new ShenyuException(\"websocket sync token is not configured\");\n        }\n        String requestToken = getHeader(request.getHeaders(), Constants.X_SHENYU_SYNC_TOKEN);\n        if (StringUtils.isBlank(requestToken) || !isSameToken(configuredToken, requestToken)) {\n            throw new ShenyuException(\"websocket sync token is invalid\");\n        }\n    }\n\n    private WebsocketSyncProperties getWebsocketSyncProperties() {\n        return Optional.ofNullable(websocketSyncProperties)\n                .orElseGet(() -> SpringBeanUtils.getInstance().getBean(WebsocketSyncProperties.class));\n    }\n\n    private boolean isSameToken(final String configuredToken, final String requestToken) {\n        return MessageDigest.isEqual(\n                configuredToken.getBytes(StandardCharsets.UTF_8),\n                requestToken.getBytes(StandardCharsets.UTF_8));\n    }\n\n    private String getHeader(final Map<String, List<String>> headers, final String name) {\n        return Optional.ofNullable(headers)\n                .orElse(Collections.emptyMap())\n                .entrySet()","sourceCodeStart":87,"sourceCodeEnd":123,"githubUrl":"https://github.com/apache/shenyu/blob/567142e07261b3e615ae8850b30f4421f455cc5d/shenyu-admin/src/main/java/org/apache/shenyu/admin/listener/websocket/WebsocketConfigurator.java#L87-L123","documentation":"During the websocket handshake, checkSyncToken compares the client's X-Shenyu-Sync-Token header to the admin's configured shenyu.sync.websocket.token using a constant-time comparison. If the header is missing/blank or the value differs, it throws ShenyuException and the handshake is rejected. This is an authentication failure, not a network problem.","triggerScenarios":"Gateway connects to ws://admin:9095/websocket with a missing X-Shenyu-Sync-Token header, an empty token, or a token value that does not exactly equal the admin's configured token (case/whitespace differences count).","commonSituations":"Token set on admin but not on the gateway (or vice versa); values differ after rotating the secret on one side only; YAML quoting/whitespace causing one side to read a different string; gateway behind a proxy that strips custom X- headers.","solutions":["Make shenyu.sync.websocket.token identical on admin and gateway configs, then restart both.","Check the gateway logs/config: ensure the token is actually sent as the X-Shenyu-Sync-Token header on the upgrade request.","Verify no reverse proxy strips or renames custom X- prefixed headers.","Compare raw values (e.g. echo the config) to catch invisible whitespace or quote mismatches after a secret rotation."],"exampleFix":"// before (gateway)\nshenyu:\n  sync:\n    websocket:\n      token: \"oldSecret\"\n// after (must match admin)\nshenyu:\n  sync:\n    websocket:\n      token: \"mySharedSecret\"","handlingStrategy":"validation","validationCode":"String adminToken = config.getAdminToken();\nString gatewayToken = config.getGatewayToken();\nif (gatewayToken == null || !MessageDigest.isEqual(adminToken.getBytes(), gatewayToken.getBytes()))\n    throw new IllegalStateException(\"websocket sync token mismatch between admin and gateway\");","typeGuard":null,"tryCatchPattern":"try {\n    handshake();\n} catch (ShenyuException e) {\n    if (e.getMessage().contains(\"token is invalid\"))\n        LOG.error(\"sync token mismatch — compare shenyu.sync.websocket.token on both sides\");\n}","preventionTips":["Keep admin and gateway tokens in one source of truth (shared secret template)","Rotate the token on both sides atomically","Confirm reverse proxies forward the X-Shenyu-Sync-Token header","Watch for YAML quoting/whitespace altering the token string"],"tags":["websocket","authentication","token-mismatch","security"],"backgroundTag":"authentication-required","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"}