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

如何在自定义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));
    }
}

这种方式的优势:

  • 完全解耦于库的实现,后续库升级无需同步修改代码;
  • 直接控制响应生成逻辑,避免了修改响应体的繁琐操作;
  • 可以灵活扩展不同认证异常与库错误码的映射规则。

三、额外建议

  1. 绝对避免复制库代码:复制会导致后续库升级时需要同步修改,维护成本极高,且违反开闭原则;
  2. 优先使用库的官方扩展点:如果认证库提供了自定义错误解析器、异常转换器等扩展机制,优先采用官方方案,这是最合规的集成方式;
  3. 配置化错误信息:将公司错误码、错误消息配置在application.yml或配置中心,避免硬编码,便于统一维护;
  4. 响应重置注意事项:使用ContentCachingResponseWrapper修改响应时,必须调用response.reset()清空原内容,避免原响应片段残留。

内容的提问来源于stack exchange,提问作者Marcin K.

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 06:15:13