{"record":{"id":"ebeedf09cbf50242","repo":"lenve/vhr","slug":"error-ebeedf","errorCode":null,"errorMessage":"尚未登录，请登录!","messagePattern":"尚未登录，请登录!","errorType":"exception","errorClass":"AccessDeniedException","httpStatus":403,"severity":"error","filePath":"vhr/vhrserver/vhr-web/src/main/java/org/javaboy/vhr/config/CustomUrlDecisionManager.java","lineNumber":31,"sourceCode":"\n/**\n * @作者 江南一点雨\n * @公众号 江南一点雨\n * @微信号 a_java_boy\n * @GitHub https://github.com/lenve\n * @博客 http://wangsong.blog.csdn.net\n * @网站 http://www.javaboy.org\n * @时间 2019-09-29 7:53\n */\n@Component\npublic class CustomUrlDecisionManager implements AccessDecisionManager {\n    @Override\n    public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {\n        for (ConfigAttribute configAttribute : configAttributes) {\n            String needRole = configAttribute.getAttribute();\n            if (\"ROLE_LOGIN\".equals(needRole)) {\n                if (authentication instanceof AnonymousAuthenticationToken) {\n                    throw new AccessDeniedException(\"尚未登录，请登录!\");\n                }else {\n                    return;\n                }\n            }\n            Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();\n            for (GrantedAuthority authority : authorities) {\n                if (authority.getAuthority().equals(needRole)) {\n                    return;\n                }\n            }\n        }\n        throw new AccessDeniedException(\"权限不足，请联系管理员!\");\n    }\n\n    @Override\n    public boolean supports(ConfigAttribute attribute) {\n        return true;\n    }","sourceCodeStart":13,"sourceCodeEnd":49,"githubUrl":"https://github.com/lenve/vhr/blob/03abbd35af24e55368ce4e09f4038dc2aba3ff5f/vhr/vhrserver/vhr-web/src/main/java/org/javaboy/vhr/config/CustomUrlDecisionManager.java#L13-L49","documentation":"Thrown by the project's custom AccessDecisionManager when a secured resource requires only the synthetic 'ROLE_LOGIN' marker but the current Authentication is an AnonymousAuthenticationToken. ROLE_LOGIN is this app's convention for 'any authenticated user is allowed'; an anonymous principal means Spring Security's filter chain ran without a successful authentication, so the request is rejected before role evaluation.","triggerScenarios":"Any request to a URL mapped with ROLE_LOGIN (via the FilterSecurityInterceptor/MetadataSource) that arrives with no valid session or no Bearer/session cookie; the user logged out, the session expired, or the client never authenticated. The decide() method sees authentication instanceof AnonymousAuthenticationToken == true and throws before checking any real roles.","commonSituations":"Session timeout on a logged-in page; front-end dropped the JSESSIONID cookie or the token header; the security config permitted the URL but did not set a login page/auth filter to run first; CORS preflight or a reverse proxy stripped the auth header; dev environment with a different session store (Redis) that lost the session; clock skew invalidating a signed token.","solutions":["Ensure the client sends a valid session cookie or auth token on every protected request — log in first and verify the cookie/header is present in the failing request.","Confirm the session timeout (server.servlet.session.timeout) and that the SessionRegistry is not evicting active sessions prematurely.","Check Spring Security config so the login filter and session-management filters run before FilterSecurityInterceptor for the failing URL — a misordered filter chain can leave authentication anonymous.","If using a custom AuthenticationEntryPoint, make sure it is wired so anonymous access to ROLE_LOGIN redirects to login (401) instead of surfacing the raw AccessDeniedException.","Verify the front-end axios/fetch is including credentials (withCredentials: true for cookie auth, or the Authorization header for token auth) on cross-origin requests."],"exampleFix":"// Front-end: ensure credentials travel with every request\n// before\naxios.get('/system/basic/hr/');\n// after\naxios.defaults.withCredentials = true;\n// or per-call\naxios.get('/system/basic/hr/', { withCredentials: true });","handlingStrategy":"validation","validationCode":"// Front-end guard: refuse to call protected endpoints when there is no session/token.\nfunction ensureAuthed() {\n  if (!document.cookie.includes('JSESSIONID') && !localStorage.getItem('token')) {\n    router.push('/login');\n    return false;\n  }\n  return true;\n}\n// before any protected call:\nif (!ensureAuthed()) return;","typeGuard":"// Server-side narrowing: treat anonymous auth as 'not logged in' explicitly.\nif (authentication instanceof AnonymousAuthenticationToken) {\n    // user is not authenticated — return 401 / redirect to login\n}\n// (CustomUrlDecisionManager already does exactly this; mirror it in any custom filter.)","tryCatchPattern":"// Register an AccessDeniedHandler that distinguishes anonymous (401) from forbidden (403):\n@Component\npublic class CustomAccessDeniedHandler implements AccessDeniedHandler {\n    @Override\n    public void handle(HttpServletRequest req, HttpServletResponse resp,\n                       AccessDeniedException ex) throws IOException {\n        resp.setContentType(\"application/json;charset=UTF-8\");\n        int status = (req.getUserPrincipal() == null) ? 401 : 403;\n        resp.setStatus(status);\n        resp.getWriter().write(new ObjectMapper().writeValueAsString(\n            Map.of(\"status\", status, \"msg\", ex.getMessage())));\n    }\n}","preventionTips":["Set axios.defaults.withCredentials = true (cookie auth) or always attach the Authorization header (token auth).","Watch the session/token on each response; on 401, clear local state and route to login.","Keep session timeout in sync with the client idle timer so the UI logs out before the server does.","Use sticky sessions or a shared session store (Spring Session + Redis) behind a load balancer."],"tags":["spring-security","access-control","authentication","session","anonymous"],"backgroundTag":null,"analyzedSha":"03abbd35af24e55368ce4e09f4038dc2aba3ff5f","analyzedAt":"2026-08-14T04:43:34.269Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}