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

Spring Boot 3.2+Spring Security 6.2 JWT认证后403错误求助

Spring Boot 3.2 + Spring Security 6.2 403空响应与JWT昵称传递解决方案

一、403空响应的排查与修复

1. 检查SecurityFilterChain权限规则

首先确认安全配置是否给目标接口开放了正确权限,前后端分离场景下通常需禁用CSRF,同时明确接口的认证要求:

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http
        .csrf(csrf -> csrf.disable())
        .authorizeHttpRequests(auth -> auth
            .requestMatchers("/auth/login", "/auth/register").permitAll() // 放行登录注册接口
            .requestMatchers("/posts/create").authenticated() // 发帖接口要求认证
            .anyRequest().authenticated() // 其他未声明接口默认需认证
        )
        .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()));
    return http.build();
}

如果使用了自定义JWT过滤器,要确保它被正确添加到过滤器链前端:

http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);

2. 验证JWT认证上下文填充逻辑

403空响应大概率是SecurityContextHolder未正确存入认证信息,检查JWT过滤器的核心逻辑:

@Component
public class JwtAuthFilter extends OncePerRequestFilter {
    private final JwtDecoder jwtDecoder;

    public JwtAuthFilter(JwtDecoder jwtDecoder) {
        this.jwtDecoder = jwtDecoder;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String token = extractToken(request);
        if (token != null) {
            try {
                Jwt jwt = jwtDecoder.decode(token);
                // 从JWT中提取昵称并构造认证对象
                String nickname = jwt.getClaim("nickname").asString();
                Authentication auth = new UsernamePasswordAuthenticationToken(nickname, null, Collections.emptyList());
                SecurityContextHolder.getContext().setAuthentication(auth);
            } catch (JwtException e) {
                // 无效JWT直接返回401,避免走到后续逻辑触发403
                response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                return;
            }
        }
        filterChain.doFilter(request, response);
    }

    private String extractToken(HttpServletRequest request) {
        String bearer = request.getHeader("Authorization");
        return bearer != null && bearer.startsWith("Bearer ") ? bearer.substring(7) : null;
    }
}

3. 配置自定义403响应

空内容的403是因为Spring Security默认不返回JSON格式响应,添加异常处理器让返回更明确:

@Bean
public AccessDeniedHandler accessDeniedHandler() {
    return (request, response, ex) -> {
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setStatus(HttpServletResponse.SC_FORBIDDEN);
        response.getWriter().write("{\"code\":403,\"msg\":\"无权访问\"}");
    };
}

在SecurityFilterChain中绑定该处理器:

http.exceptionHandling(ex -> ex.accessDeniedHandler(accessDeniedHandler()));

二、将JWT昵称传入DTO的两种实现方式

1. 直接从SecurityContextHolder提取(快速实现)

在Controller中直接获取认证信息填充DTO:

@PostMapping("/posts/create")
public ResponseEntity<PostDTO> createPost(@RequestBody PostCreateDTO dto) {
    // 从认证上下文取昵称
    String author = (String) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
    dto.setAuthor(author);
    PostDTO savedPost = postService.savePost(dto);
    return ResponseEntity.ok(savedPost);
}

2. 自定义参数解析器(优雅解耦)

定义注解标记需要获取昵称的参数:

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface CurrentAuthor {
}

实现参数解析器:

@Component
public class CurrentAuthorResolver implements HandlerMethodArgumentResolver {
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(CurrentAuthor.class);
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        if (auth == null || !auth.isAuthenticated()) {
            throw new AccessDeniedException("用户未登录");
        }
        // 若使用自定义UserDetails,可强转后取昵称;此处假设认证主体为JWT对象
        Jwt jwt = (Jwt) auth.getPrincipal();
        return jwt.getClaim("nickname").asString();
    }
}

注册解析器到Spring MVC:

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    private final CurrentAuthorResolver authorResolver;

    public WebMvcConfig(CurrentAuthorResolver authorResolver) {
        this.authorResolver = authorResolver;
    }

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(authorResolver);
    }
}

在Controller中直接使用注解:

@PostMapping("/posts/create")
public ResponseEntity<PostDTO> createPost(@RequestBody PostCreateDTO dto, @CurrentAuthor String author) {
    dto.setAuthor(author);
    PostDTO savedPost = postService.savePost(dto);
    return ResponseEntity.ok(savedPost);
}

三、关键注意点

  • 确保生成JWT时已将昵称存入Claims,比如登录接口的token生成逻辑:
    private String generateJwt(User user) {
        Map<String, Object> claims = new HashMap<>();
        claims.put("nickname", user.getNickname()); // 必须包含该字段
        return Jwts.builder()
            .setClaims(claims)
            .setSubject(user.getUsername())
            .setIssuedAt(new Date())
            .setExpiration(new Date(System.currentTimeMillis() + 3600 * 1000))
            .signWith(SignatureAlgorithm.HS256, "your-secret-key")
            .compact();
    }
    
  • 检查跨域过滤器是否在JWT过滤器之前执行,避免请求被提前拦截:
    @Bean
    public CorsFilter corsFilter() {
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.addAllowedOriginPattern("*");
        config.addAllowedHeader("*");
        config.addAllowedMethod("*");
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config);
        return new CorsFilter(source);
    }
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 20:34:54