使用Jetty服务器时,Spring Security中JWT拦截后如何返回JSON格式401错误?
问题:Jetty下Spring Security JWT过滤器返回HTML格式响应而非JSON
我的Spring Security Bean在Tomcat服务器下能正常拦截未授权请求,返回包含错误消息的简洁JSON异常响应;但切换到Jetty服务器后,即使在Postman中请求,也会返回text/html格式的响应。
现有JwtFilter代码
public class JwtFilter extends OncePerRequestFilter { @Autowired private JWTUtility jwtUtility; @Autowired private UserServiceImpl userService; private final ObjectMapper mapper; @Override protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException { String authorization = httpServletRequest.getHeader("Authorization"); String token = null; String userName = null; if(null != authorization && authorization.startsWith("Bearer ")) { token = authorization.substring(7); userName = jwtUtility.getUsernameFromToken(token); } if(null != userName && SecurityContextHolder.getContext().getAuthentication() == null) { UserDetails userDetails = userService.loadUserByUsername(userName); try { if (jwtUtility.validateToken(token, userDetails)) { UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); usernamePasswordAuthenticationToken.setDetails( new WebAuthenticationDetailsSource().buildDetails(httpServletRequest) ); SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken); } } catch (Exception e){ // System.out.println("Hello world"); Map<String, Object> errorDetails = new HashMap<>(); errorDetails.put("ACCESS_DENIED", e.getMessage()); httpServletResponse.setStatus(HttpStatus.FORBIDDEN.value()); httpServletResponse.setContentType(String.valueOf(MediaType.APPLICATION_JSON)); mapper.writeValue(httpServletResponse.getWriter(), errorDetails); return; } } filterChain.doFilter(httpServletRequest, httpServletResponse); } }
需要替换的代码段
Map<String, Object> errorDetails = new HashMap<>(); errorDetails.put("ACCESS_DENIED", e.getMessage()); httpServletResponse.setStatus(HttpStatus.FORBIDDEN.value()); httpServletResponse.setContentType(String.valueOf(MediaType.APPLICATION_JSON)); mapper.writeValue(httpServletResponse.getWriter(), errorDetails); return;
期望的响应效果
当用户未发送JWT时,返回:
HTTP 401 Unauthorized { "ACCESS_DENIED": "Some error message here" }
合法请求则正常通过过滤器,无需返回额外内容。
修改方案
针对Jetty的响应处理特性,调整异常处理代码如下:
Map<String, Object> errorDetails = new HashMap<>(); errorDetails.put("ACCESS_DENIED", e.getMessage()); // 设置符合期望的未授权状态码 httpServletResponse.setStatus(HttpStatus.UNAUTHORIZED.value()); // 明确指定JSON内容类型及UTF-8编码,避免Jetty自动转为HTML格式 httpServletResponse.setContentType("application/json;charset=UTF-8"); httpServletResponse.setCharacterEncoding("UTF-8"); // 使用OutputStream写入响应,规避Jetty对Writer的兼容问题 mapper.writeValue(httpServletResponse.getOutputStream(), errorDetails); // 强制刷新缓冲区,确保响应内容立即发送 httpServletResponse.flushBuffer(); return;
关键调整说明
- 状态码修正:将原403(FORBIDDEN)改为401(UNAUTHORIZED),匹配未授权场景的语义。
- 内容类型与编码:Jetty对响应头处理更严格,明确指定完整的
application/json;charset=UTF-8,避免被容器默认的HTML格式覆盖。 - OutputStream替代Writer:部分Jetty版本中,使用
getWriter()可能触发响应格式自动转换,改用getOutputStream()可确保JSON内容直接写入。 - 强制刷新缓冲区:调用
flushBuffer()避免后续过滤器或容器修改响应格式,保证内容按预期发送。
另外,当前代码仅处理token验证失败的场景,若要覆盖未发送JWT的情况,需在authorization == null时添加对应逻辑:
if (null == authorization) { Map<String, Object> errorDetails = new HashMap<>(); errorDetails.put("ACCESS_DENIED", "JWT token is missing"); httpServletResponse.setStatus(HttpStatus.UNAUTHORIZED.value()); httpServletResponse.setContentType("application/json;charset=UTF-8"); httpServletResponse.setCharacterEncoding("UTF-8"); mapper.writeValue(httpServletResponse.getOutputStream(), errorDetails); httpServletResponse.flushBuffer(); return; }
内容的提问来源于stack exchange,提问作者Jet Ezra
相关产品推荐
相关产品推荐

