YunaiV/yudao-cloud · error · AccessDeniedException
错误的用户类型
Error message
错误的用户类型
What it means
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.
Source
Thrown at yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/filter/TokenAuthenticationFilter.java:95
SecurityFrameworkUtils.setLoginUser(loginUser, request);
}
// 继续过滤链
chain.doFilter(request, response);
}
private LoginUser buildLoginUserByToken(String token, Integer userType) {
try {
// 校验访问令牌
OAuth2AccessTokenCheckRespDTO accessToken = oauth2TokenApi.checkAccessToken(token).getCheckedData();
if (accessToken == null) {
return null;
}
// 用户类型不匹配,无权限
// 注意:只有 /admin-api/* 和 /app-api/* 有 userType,才需要比对用户类型
// 类似 WebSocket 的 /ws/* 连接地址,是不需要比对用户类型的
if (userType != null
&& ObjectUtil.notEqual(accessToken.getUserType(), userType)) {
throw new AccessDeniedException("错误的用户类型");
}
// 构建登录用户
return new LoginUser().setId(accessToken.getUserId()).setUserType(accessToken.getUserType())
.setInfo(accessToken.getUserInfo()) // 额外的用户信息
.setTenantId(accessToken.getTenantId()).setScopes(accessToken.getScopes())
.setExpiresTime(accessToken.getExpiresTime());
} catch (ServiceException serviceException) {
// 校验 Token 不通过时,考虑到一些接口是无需登录的,所以直接返回 null 即可
return null;
}
}
/**
* 模拟登录用户,方便日常开发调试
*
* 注意,在线上环境下,一定要关闭该功能!!!
*
* @param request 请求View on GitHub (pinned to 477be9dd49)
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
Example fix
// before: member token used against admin endpoint GET /admin-api/system/user/profile Authorization: Bearer <member-token> // after: admin token for admin endpoint (or use the app endpoint) GET /app-api/member/user/get Authorization: Bearer <member-token>
Defensive patterns
Strategy: validation
Validate before calling
// before calling, pick the token that matches the URL's user type
Integer urlUserType = url.startsWith("/admin-api") ? 2 : (url.startsWith("/app-api") ? 1 : null);
if (urlUserType != null && tokenUserType != urlUserType) {
token = tokenStore.get(urlUserType == 2 ? "admin" : "member"); // re-select token
} Try / catch
try {
return restTemplate.getForObject(url, Resp.class);
} catch (HttpClientErrorException e) {
if (e.getStatusCode() == HttpStatus.FORBIDDEN && e.getResponseBodyAsString().contains("错误的用户类型")) {
throw new IllegalStateException("Wrong token type for " + url + " — use the matching admin/app token");
}
throw e;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
AI-assisted analysis of YunaiV/yudao-cloud@477be9dd49 (2026-08-14).
Data as JSON: /api/errors/b5c2cd59de6fa65a.
Report an issue: GitHub.