You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Security 6未认证与未授权场景异常消息区分问题

解决方案:区分未认证与未授权的自定义响应

核心问题分析

  1. 过滤器链优先级高于方法注解:当设置anyRequest().authenticated()时,Spring Security过滤器链会先于@PreAuthorize/@Secured等方法注解执行权限检查。已认证但无权限的请求会被过滤器链拦截,抛出InsufficientAuthenticationException,而非方法注解抛出的AccessDeniedException,导致两种场景无法区分。
  2. 缺少未授权异常处理器:仅配置AuthenticationEntryPoint处理未认证的AuthenticationException,但未配置专门的处理器处理已登录但无权限的AccessDeniedException。

步骤1:自定义AccessDeniedHandler处理未授权场景

创建专门处理器,针对已登录但无权限的请求返回403 Forbidden自定义响应:

@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {

    private static final Logger logger = LoggerFactory.getLogger(CustomAccessDeniedHandler.class);

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
        logger.error("Access denied error: {}", accessDeniedException.getMessage());

        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setStatus(HttpServletResponse.SC_FORBIDDEN);

        final Map<String, Object> body = new HashMap<>();
        body.put("status", HttpServletResponse.SC_FORBIDDEN);
        body.put("error", "Access Denied");
        body.put("message", "你没有访问该资源的权限");
        body.put("path", request.getRequestURI());

        final ObjectMapper mapper = new ObjectMapper();
        mapper.writeValue(response.getOutputStream(), body);
    }
}

步骤2:调整AuthenticationEntryPoint返回未认证标准响应

修改原处理器,将未认证场景的状态码改为HTTP标准的401 Unauthorized,与403场景明确区分:

@Component
public class AuthEntryPoint implements AuthenticationEntryPoint {

    private static final Logger logger = LoggerFactory.getLogger(AuthEntryPoint.class);

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
        logger.error("Unauthenticated error: {}", authException.getMessage());

        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);

        final Map<String, Object> body = new HashMap<>();
        body.put("status", HttpServletResponse.SC_UNAUTHORIZED);
        body.put("error", "Unauthenticated");
        body.put("message", "请先登录以访问该资源");
        body.put("path", request.getRequestURI());

        final ObjectMapper mapper = new ObjectMapper();
        mapper.writeValue(response.getOutputStream(), body);
    }
}

步骤3:更新SecurityConfig,同时配置两个异常处理器

在过滤器链中同时注册两个异常处理器,并移除与方法注解冲突的权限规则:

@Configuration
@EnableMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class SecurityConfig {

    @Autowired
    private AuthEntryPoint unauthorizedHandler;

    @Autowired
    private CustomAccessDeniedHandler accessDeniedHandler;

    // 省略authenticationProvider、authenticationJwtTokenFilter等Bean定义

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeHttpRequests()
                .requestMatchers("/", "/register", "/login").permitAll()
                // 移除过滤器链内的权限规则,交由方法注解处理
                .anyRequest().authenticated()
                .and().sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and().httpBasic()
                .and().authenticationProvider(authenticationProvider())
                .exceptionHandling()
                .authenticationEntryPoint(unauthorizedHandler) // 处理未认证
                .accessDeniedHandler(accessDeniedHandler) // 处理未授权
                .and()
                .addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }
}

步骤4:用方法注解实现细粒度权限控制

在Controller方法上添加注解,定义权限要求:

@RestController
public class TestController {

    @GetMapping("/test2")
    @PreAuthorize("hasAuthority('ROLE_USER')")
    public String test2() {
        return "Hello, ROLE_USER!";
    }
}

效果验证

  • 未认证访问:返回401状态码,响应消息为"请先登录以访问该资源"
  • 已登录但无权限访问:返回403状态码,响应消息为"你没有访问该资源的权限"

内容的提问来源于stack exchange,提问作者Janas

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 21:31:08