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

Spring WebClient调用API返回MonoFlatMapMany及证书异常排查

Spring WebClient调用HTTPS接口异常修复方案

问题复现

通过Postman调用目标接口可正常获取如下响应:

{
    "loginAccounts": [
        {
            "name": "Oracle",
            "accountId": "16485679",
            "baseUrl": "https://demo.docusign.net/restapi/v2.1/accounts/16485679",
            "isDefault": "true",
            "userName": "Mahima Pandey",
            "userId": "e48dbc73-97df-42de-be9f-ea49c8b24712",
            "email": "mahima.pandey@oracle.com",
            "siteDescription": ""
        }
    ]
}

使用如下WebClient代码调用时无法得到正确结果:

public static Flux<LoginAccounts> retrieveDetails(){
        Flux<LoginAccounts> loginAccounts=null;
        WebClient webclient = WebClient.create();
        try {
        loginAccounts = webclient.get().uri(DocusignConstants.LOGIN_INFO_URL)
                .header("Accept", "application/json")
                .header("Authorization", "Bearer eyJ0eX...........")
                .retrieve()
                .bodyToFlux(LoginAccounts.class);
        System.out.println(loginAccounts);
        }
        catch(WebClientResponseException ex) {
            System.out.println(ex);
        }
        return loginAccounts;
    }

运行现象:

  • 直接打印返回的loginAccounts对象仅输出MonoFlatMapMany
  • 调用loginAccounts.toStream().forEach(data -> System.out.println(data));遍历结果时抛出异常,核心报错为org.springframework.web.reactive.function.client.WebClientRequestException,嵌套异常为javax.net.ssl.SSLHandshakeException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target,提示无法找到目标地址有效证书路径。

原有POJO定义如下:

@data
@Noargsconstructor
@Allargsconstructor
public class LoginAccounts {

    private String name;
    private String accountId;
    private String baseUrl;
    private Boolean isDefault;
    private String userName;
    private String userId;
    private String email;
    private String siteDescription;

}

问题根因

  • SSL信任库差异:Postman自带独立证书信任库,会自动信任公开合法证书;JVM默认使用自身jre/lib/security/cacerts作为信任库,未导入目标域名证书链时就会抛出SSL握手失败异常。
  • 响应结构不匹配:接口最外层是包装对象,loginAccounts是包装对象内部的数组字段,直接用bodyToFlux(LoginAccounts.class)无法正确解析嵌套结构。
  • 响应式API使用错误:WebClient返回的Flux/Mono是惰性执行流,仅拿到流对象不触发订阅时不会实际发起请求,直接打印对象只会得到流的内部实现类名称,不会返回实际响应;同时响应式流的异常在订阅执行时才会抛出,外层try-catch无法捕获。
  • POJO定义错误:Lombok注解大小写不符合规范,@data/@Noargsconstructor/@Allargsconstructor均为无效注解,不会生成getter/setter、构造方法;且接口返回的isDefault是字符串类型的"true",直接定义为Boolean类型会触发反序列化类型不匹配错误。

修复步骤

1. 修正POJO类定义

新增外层响应包装类,修正Lombok注解写法,调整字段类型适配接口返回值:

// 接口最外层包装对象
@Data
@NoArgsConstructor
@AllArgsConstructor
public class LoginAccountResponse {
    private List<LoginAccount> loginAccounts;
}

// 单条账户数据实体,类名用单数更符合语义
@Data
@NoArgsConstructor
@AllArgsConstructor
public class LoginAccount {
    private String name;
    private String accountId;
    private String baseUrl;
    // 接口返回字符串类型的"true"/"false",先用String接收,需要布尔值时可自行转换
    private String isDefault;
    private String userName;
    private String userId;
    private String email;
    private String siteDescription;
}

2. 解决SSL证书信任问题

开发测试环境可临时配置跳过SSL校验快速验证(生产环境严禁使用该配置,必须将目标域名正式证书导入JVM信任库):

// 开发测试用:构建跳过SSL校验的HTTP客户端
HttpClient httpClient = HttpClient.create()
        .secure(sslSpec -> {
            try {
                sslSpec.sslContext(
                        SslContextBuilder.forClient()
                                .trustManager(InsecureTrustManagerFactory.INSTANCE)
                                .build()
                );
            } catch (SSLException e) {
                throw new RuntimeException(e);
            }
        });

// 基于自定义客户端构建WebClient实例
WebClient webClient = WebClient.builder()
        .clientConnector(new ReactorClientHttpConnector(httpClient))
        .build();

生产环境正确处理方式:导出目标域名的完整证书链,使用keytool命令将证书导入JVM的cacerts信任库,保留默认SSL校验逻辑。

3. 修正接口调用逻辑

调整反序列化目标类型,使用响应式原生操作符处理流和异常:

public static Flux<LoginAccount> retrieveDetails(){
    // 开发测试环境用跳过SSL校验的客户端,生产环境替换为正常证书配置的WebClient
    HttpClient httpClient = HttpClient.create()
            .secure(sslSpec -> {
                try {
                    sslSpec.sslContext(
                            SslContextBuilder.forClient()
                                    .trustManager(InsecureTrustManagerFactory.INSTANCE)
                                    .build()
                    );
                } catch (SSLException e) {
                    throw new RuntimeException(e);
                }
            });
    WebClient webclient = WebClient.builder()
            .clientConnector(new ReactorClientHttpConnector(httpClient))
            .build();

    return webclient.get().uri(DocusignConstants.LOGIN_INFO_URL)
            .header("Accept", "application/json")
            .header("Authorization", "Bearer 替换为你的有效Token")
            .retrieve()
            // 先解析为外层包装对象
            .bodyToMono(LoginAccountResponse.class)
            // 拆出内部账户列表转为Flux流
            .flatMapMany(resp -> Flux.fromIterable(resp.getLoginAccounts()))
            // 用响应式操作符处理异常,不要在外层写try-catch
            .doOnError(WebClientResponseException.class, ex -> {
                System.out.println("请求失败,状态码:" + ex.getStatusCode());
                System.out.println("错误响应:" + ex.getResponseBodyAsString());
            });
}

// 调用方法时必须主动订阅,才会实际发起HTTP请求
public static void main(String[] args) {
    retrieveDetails()
            .subscribe(account -> {
                // 在这里处理拿到的账户数据
                System.out.println("用户名:" + account.getUserName());
                System.out.println("账户ID:" + account.getAccountId());
                System.out.println("接口基地址:" + account.getBaseUrl());
            });
}

4. 注意事项

  • 不要直接调用toStream()遍历Flux,会阻塞当前线程,违背响应式编程非阻塞设计原则,优先使用subscribe()、map()、flatMap()等非阻塞操作符处理数据。
  • 可以直接复制Postman中生成的完整请求头参数到代码中,避免Token、Content-Type等参数拼写错误、Token过期等问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 14:18:27