{"record":{"id":"ef2b2048e4704796","repo":"dromara/Sa-Token","slug":"code-30011","errorCode":"CODE_30011","errorMessage":"该 ticket 不属于 client={client}, ticket 值: {ticket}","messagePattern":"该 ticket 不属于 client=(.+?), ticket 值: (.+?)","errorType":"exception","errorClass":"SaSsoException","httpStatus":null,"severity":"error","filePath":"sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/template/SaSsoServerTemplate.java","lineNumber":213,"sourceCode":"     * 校验 Ticket，无效 ticket 会抛出异常，如果此ticket是有效的，则立即删除\n     *\n     * @param ticket Ticket码\n     * @param client client 标识\n     * @return /\n     */\n    public TicketModel checkTicketParamAndDelete(String ticket, String client) {\n        TicketModel ticketModel = checkTicket(ticket);\n\n        // 校验 client 参数是否正确，即：创建 ticket 的 client 和当前校验 ticket 的 client 是否一致\n        String ticketClient = ticketModel.getClient();\n        if(SaSsoConsts.CLIENT_WILDCARD.equals(client)) {\n            // 如果提供的是通配符，直接越过 client 校验\n        } else if (SaFoxUtil.isEmpty(client) && SaFoxUtil.isEmpty(ticketClient)) {\n            // 如果提供的和期望的两者均为空，则通过校验\n        } else {\n            // 开始详细比对\n            if(SaFoxUtil.notEquals(client, ticketClient)) {\n                throw new SaSsoException(\"该 ticket 不属于 client=\" + client + \", ticket 值: \" + ticket).setCode(SaSsoErrorCode.CODE_30011);\n            }\n        }\n\n        // 删除 ticket 信息，使其只有一次性有效\n        deleteTicket(ticket);\n        deleteTicketIndex(client, ticketModel.getLoginId());\n\n        //\n        return ticketModel;\n    }\n\n    // ticket 索引\n\n    /**\n     * 保存 Ticket 索引 （id 反查 ticket）\n     *\n     * @param client 应用端\n     * @param ticket ticket码","sourceCodeStart":195,"sourceCodeEnd":231,"githubUrl":"https://github.com/dromara/Sa-Token/blob/ac2c7f6e94a78573cf0bcb932dd8b04e68fad189/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/template/SaSsoServerTemplate.java#L195-L231","documentation":"Thrown during ticket validation when the client identifier presented at check time differs from the client that the ticket was created for (unless the wildcard is used). This binds each one-time ticket to the client app that triggered it, preventing a ticket issued for app A from being redeemed by app B.","triggerScenarios":"checkTicketParamAndDelete(ticket, client) is called with a client value that does not string-equal ticketModel.getClient() — e.g. the client app sends a different/missing client parameter at /sso/login than it sent at /sso/auth.","commonSituations":"Two client apps share one callback domain and the ticket gets intercepted by the other app; the client's configured allow-client / client identifier changed between auth and callback; one client deliberately reuses a ticket meant for another (blocked by design).","solutions":["Make the same client identifier flow through both steps: the redirect into /sso/auth and the callback that validates the ticket","Check the client param name mapping (paramName.client) is not being rewritten by a proxy between the two requests","If you genuinely need multi-client ticket redemption, pass SaSsoConsts.CLIENT_WILDCARD to bypass the check (understand the security implications first)"],"exampleFix":"// before\nssoServerTemplate.checkTicketParamAndDelete(ticket, requestClient); // CODE_30011 when mismatch\n\n// after — ensure the auth redirect carried this client's id\nString authUrl = ssoServerTemplate.buildAuthUrl(\"http://client.com/sso/login\", \"client-1\");\n// ... on callback, validate with the same id\nssoServerTemplate.checkTicketParamAndDelete(ticket, \"client-1\");","handlingStrategy":"validation","validationCode":"String expected = myClientId; // same id sent to /sso/auth\nif(!expected.equals(req.getParam(paramName.client))) {\n    // fix the parameter before calling checkTicketParamAndDelete\n}","typeGuard":null,"tryCatchPattern":"try { ssoServerTemplate.checkTicketParamAndDelete(ticket, client); } catch (SaSsoException e) { if(SaSsoErrorCode.CODE_30011 == e.getCode()) { /* reject callback, restart SSO flow */ } }","preventionTips":["Thread one client identifier through the entire SSO round trip","Don't share callback endpoints between client apps unless you intentionally use the wildcard"],"tags":["sa-token","sso","ticket","client-mismatch","security"],"backgroundTag":null,"analyzedSha":"ac2c7f6e94a78573cf0bcb932dd8b04e68fad189","analyzedAt":"2026-08-14T14:36:10.271Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}