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

  1. 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.
  2. Confirm the session timeout (server.servlet.session.timeout) and that the SessionRegistry is not evicting active sessions prematurely.
  3. 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.
  4. 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.
  5. 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

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.