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

  1. Use a token issued for the matching user type: admin tokens for /admin-api/*, member tokens for /app-api/*
  2. Re-login through the correct auth endpoint (admin login vs member login) and replace the stored token
  3. Check the frontend request base URL — the app must not point at the admin-api prefix
  4. 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

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.