如何在自定义AuthenticationEntryPoint子类中修改父类返回的响应体
解决方案与建议
一、改进现有继承方案:规避ExceptionResponse反序列化问题
既然库的ExceptionResponse没有默认构造函数,无法直接用ObjectMapper反序列化,我们可以改用JsonNode解析响应体,不依赖具体的实体类,实现响应内容的修改:
public class CompanyAuthenticationEntryPoint extends LibraryAuthenticationEntryPoint { private final ObjectMapper objectMapper; public CompanyAuthenticationEntryPoint(ObjectMapper objectMapper) { this.objectMapper = objectMapper; } @Override public void commence(final HttpServletRequest request, final HttpServletResponse response, final AuthenticationException exception) throws IOException { ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response); // 调用父类方法生成原响应 super.commence(request, responseWrapper, exception); byte[] responseArray = responseWrapper.getContentAsByteArray(); String responseStr = new String(responseArray, responseWrapper.getCharacterEncoding()); // 解析原响应为JsonNode,无需依赖ExceptionResponse类 JsonNode originalNode = objectMapper.readTree(responseStr); JsonNode originalError = originalNode.get("errors").get(0); // 构建公司标准错误节点 ObjectNode companyErrorNode = objectMapper.createObjectNode(); companyErrorNode.put("identifier", "COMPANY_001"); companyErrorNode.put("message", "Authentication token is missing, but its company standard message!"); companyErrorNode.putNull("solution"); // 构建原错误为reason节点 ArrayNode reasonsNode = objectMapper.createArrayNode(); ObjectNode reasonNode = objectMapper.createObjectNode(); reasonNode.put("identifier", originalError.get("identifier").asText()); reasonNode.put("message", originalError.get("message").asText()); reasonsNode.add(reasonNode); companyErrorNode.set("reasons", reasonsNode); // 组装完整的公司标准响应 ObjectNode companyResponseNode = objectMapper.createObjectNode(); ArrayNode errorsNode = objectMapper.createArrayNode(); errorsNode.add(companyErrorNode); companyResponseNode.set("errors", errorsNode); companyResponseNode.set("warnings", originalNode.get("warnings")); companyResponseNode.set("metadata", originalNode.get("metadata")); // 重置响应,写入新内容 response.reset(); response.setContentType("application/json"); response.setCharacterEncoding(responseWrapper.getCharacterEncoding()); response.getWriter().write(objectMapper.writeValueAsString(companyResponseNode)); } }
二、更优方案:包装原EntryPoint而非继承
继承方式会和库的实现强耦合,推荐采用包装模式:自己实现AuthenticationEntryPoint,直接根据异常生成符合公司规范的响应,无需依赖原EntryPoint的输出内容:
@Component public class CompanyAuthenticationEntryPoint implements AuthenticationEntryPoint { private final ObjectMapper objectMapper; public CompanyAuthenticationEntryPoint(ObjectMapper objectMapper) { this.objectMapper = objectMapper; } @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException { // 构建公司标准错误结构 ObjectNode companyErrorNode = objectMapper.createObjectNode(); companyErrorNode.put("identifier", "COMPANY_001"); companyErrorNode.put("message", "Authentication token is missing, but its company standard message!"); companyErrorNode.putNull("solution"); // 根据异常类型映射库的错误信息 ArrayNode reasonsNode = objectMapper.createArrayNode(); ObjectNode reasonNode = objectMapper.createObjectNode(); // 这里可以根据实际异常类型扩展更多映射规则 if (authException instanceof InsufficientAuthenticationException) { reasonNode.put("identifier", "LIBRARY_001"); reasonNode.put("message", "Authentication token missing!"); } else if (authException instanceof BadCredentialsException) { reasonNode.put("identifier", "LIBRARY_002"); reasonNode.put("message", "Invalid authentication token!"); } reasonsNode.add(reasonNode); companyErrorNode.set("reasons", reasonsNode); // 组装完整响应 ObjectNode companyResponse = objectMapper.createObjectNode(); companyResponse.set("errors", objectMapper.createArrayNode().add(companyErrorNode)); companyResponse.set("warnings", objectMapper.createArrayNode()); companyResponse.set("metadata", null); // 设置响应头与状态码 response.setContentType("application/json"); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.getWriter().write(objectMapper.writeValueAsString(companyResponse)); } }
这种方式的优势:
- 完全解耦于库的实现,后续库升级无需同步修改代码;
- 直接控制响应生成逻辑,避免了修改响应体的繁琐操作;
- 可以灵活扩展不同认证异常与库错误码的映射规则。
三、额外建议
- 绝对避免复制库代码:复制会导致后续库升级时需要同步修改,维护成本极高,且违反开闭原则;
- 优先使用库的官方扩展点:如果认证库提供了自定义错误解析器、异常转换器等扩展机制,优先采用官方方案,这是最合规的集成方式;
- 配置化错误信息:将公司错误码、错误消息配置在
application.yml或配置中心,避免硬编码,便于统一维护; - 响应重置注意事项:使用
ContentCachingResponseWrapper修改响应时,必须调用response.reset()清空原内容,避免原响应片段残留。
内容的提问来源于stack exchange,提问作者Marcin K.
相关产品推荐
相关产品推荐

