lenve/vhr · error · AccessDeniedException
尚未登录,请登录!
Error message
尚未登录,请登录!
What it means
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.
Source
Thrown at vhr/vhrserver/vhr-web/src/main/java/org/javaboy/vhr/config/CustomUrlDecisionManager.java:31
/**
* @作者 江南一点雨
* @公众号 江南一点雨
* @微信号 a_java_boy
* @GitHub https://github.com/lenve
* @博客 http://wangsong.blog.csdn.net
* @网站 http://www.javaboy.org
* @时间 2019-09-29 7:53
*/
@Component
public class CustomUrlDecisionManager implements AccessDecisionManager {
@Override
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
for (ConfigAttribute configAttribute : configAttributes) {
String needRole = configAttribute.getAttribute();
if ("ROLE_LOGIN".equals(needRole)) {
if (authentication instanceof AnonymousAuthenticationToken) {
throw new AccessDeniedException("尚未登录,请登录!");
}else {
return;
}
}
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
for (GrantedAuthority authority : authorities) {
if (authority.getAuthority().equals(needRole)) {
return;
}
}
}
throw new AccessDeniedException("权限不足,请联系管理员!");
}
@Override
public boolean supports(ConfigAttribute attribute) {
return true;
}View on GitHub (pinned to 03abbd35af)
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.
Example fix
// Front-end: ensure credentials travel with every request
// before
axios.get('/system/basic/hr/');
// after
axios.defaults.withCredentials = true;
// or per-call
axios.get('/system/basic/hr/', { withCredentials: true }); Defensive patterns
Strategy: validation
Validate before calling
// Front-end guard: refuse to call protected endpoints when there is no session/token.
function ensureAuthed() {
if (!document.cookie.includes('JSESSIONID') && !localStorage.getItem('token')) {
router.push('/login');
return false;
}
return true;
}
// before any protected call:
if (!ensureAuthed()) return; Type guard
// Server-side narrowing: treat anonymous auth as 'not logged in' explicitly.
if (authentication instanceof AnonymousAuthenticationToken) {
// user is not authenticated — return 401 / redirect to login
}
// (CustomUrlDecisionManager already does exactly this; mirror it in any custom filter.) Try / catch
// Register an AccessDeniedHandler that distinguishes anonymous (401) from forbidden (403):
@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest req, HttpServletResponse resp,
AccessDeniedException ex) throws IOException {
resp.setContentType("application/json;charset=UTF-8");
int status = (req.getUserPrincipal() == null) ? 401 : 403;
resp.setStatus(status);
resp.getWriter().write(new ObjectMapper().writeValueAsString(
Map.of("status", status, "msg", ex.getMessage())));
}
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
AI-assisted analysis of lenve/vhr@03abbd35af (2026-08-14).
Data as JSON: /api/errors/ebeedf09cbf50242.
Report an issue: GitHub.