{"record":{"id":"b5c2cd59de6fa65a","repo":"YunaiV/yudao-cloud","slug":"error-b5c2cd","errorCode":null,"errorMessage":"错误的用户类型","messagePattern":"错误的用户类型","errorType":"http","errorClass":"AccessDeniedException","httpStatus":403,"severity":"error","filePath":"yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/filter/TokenAuthenticationFilter.java","lineNumber":95,"sourceCode":"            SecurityFrameworkUtils.setLoginUser(loginUser, request);\n        }\n        // 继续过滤链\n        chain.doFilter(request, response);\n    }\n\n    private LoginUser buildLoginUserByToken(String token, Integer userType) {\n        try {\n            // 校验访问令牌\n            OAuth2AccessTokenCheckRespDTO accessToken = oauth2TokenApi.checkAccessToken(token).getCheckedData();\n            if (accessToken == null) {\n                return null;\n            }\n            // 用户类型不匹配，无权限\n            // 注意：只有 /admin-api/* 和 /app-api/* 有 userType，才需要比对用户类型\n            // 类似 WebSocket 的 /ws/* 连接地址，是不需要比对用户类型的\n            if (userType != null\n                    && ObjectUtil.notEqual(accessToken.getUserType(), userType)) {\n                throw new AccessDeniedException(\"错误的用户类型\");\n            }\n            // 构建登录用户\n            return new LoginUser().setId(accessToken.getUserId()).setUserType(accessToken.getUserType())\n                    .setInfo(accessToken.getUserInfo()) // 额外的用户信息\n                    .setTenantId(accessToken.getTenantId()).setScopes(accessToken.getScopes())\n                    .setExpiresTime(accessToken.getExpiresTime());\n        } catch (ServiceException serviceException) {\n            // 校验 Token 不通过时，考虑到一些接口是无需登录的，所以直接返回 null 即可\n            return null;\n        }\n    }\n\n    /**\n     * 模拟登录用户，方便日常开发调试\n     *\n     * 注意，在线上环境下，一定要关闭该功能！！！\n     *\n     * @param request 请求","sourceCodeStart":77,"sourceCodeEnd":113,"githubUrl":"https://github.com/YunaiV/yudao-cloud/blob/477be9dd49ab7223a972a6abdff0684d6423dec3/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/filter/TokenAuthenticationFilter.java#L77-L113","documentation":"Thrown by TokenAuthenticationFilter when a valid access token's userType does not match the userType expected by the URL prefix: /admin-api/* expects USER_TYPE_ADMIN (2) and /app-api/* expects USER_TYPE_MEMBER (1). The token itself is valid — it simply belongs to the other user population. Endpoints without a URL userType (e.g. WebSocket /ws/*) skip this check because userType is null.","triggerScenarios":"Calling an /admin-api/** endpoint with a member (app) token obtained from member login; calling an /app-api/** endpoint with an admin后台 token; frontend storing both tokens in one key and sending the wrong one; copying a token from the admin UI into an app-API request in Apifox/Postman.","commonSituations":"Swagger/Apifox debugging with a member token against admin endpoints; a mini-program frontend accidentally pointed at the admin API base URL; token storage key collision after adding the app UI to the same domain; tests that reuse one fixture token for both API families.","solutions":["Use a token issued for the matching user type: admin tokens for /admin-api/*, member tokens for /app-api/*","Re-login through the correct auth endpoint (admin login vs member login) and replace the stored token","Check the frontend request base URL — the app must not point at the admin-api prefix","Verify the Authorization header is not being overwritten by an interceptor with a stale token of the other type"],"exampleFix":"// before: member token used against admin endpoint\nGET /admin-api/system/user/profile\nAuthorization: Bearer <member-token>\n\n// after: admin token for admin endpoint (or use the app endpoint)\nGET /app-api/member/user/get\nAuthorization: Bearer <member-token>","handlingStrategy":"validation","validationCode":"// before calling, pick the token that matches the URL's user type\nInteger urlUserType = url.startsWith(\"/admin-api\") ? 2 : (url.startsWith(\"/app-api\") ? 1 : null);\nif (urlUserType != null && tokenUserType != urlUserType) {\n    token = tokenStore.get(urlUserType == 2 ? \"admin\" : \"member\"); // re-select token\n}","typeGuard":null,"tryCatchPattern":"try {\n    return restTemplate.getForObject(url, Resp.class);\n} catch (HttpClientErrorException e) {\n    if (e.getStatusCode() == HttpStatus.FORBIDDEN && e.getResponseBodyAsString().contains(\"错误的用户类型\")) {\n        throw new IllegalStateException(\"Wrong token type for \" + url + \" — use the matching admin/app token\");\n    }\n    throw e;\n}","preventionTips":["Keep admin and member tokens in separate storage keys and select by API prefix","Assert the URL prefix matches the token family in a shared request interceptor","In tests, parameterize the token fixture per API family"],"tags":["security","authentication","user-type","token","spring-security"],"backgroundTag":null,"analyzedSha":"477be9dd49ab7223a972a6abdff0684d6423dec3","analyzedAt":"2026-08-14T13:35:31.121Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}