Micronaut客户端使用无CA自签名证书的mTLS通信返回401未授权,但Postman与curl可正常工作
Micronaut客户端使用无CA自签名证书的mTLS通信返回401未授权,但Postman与curl可正常工作
我最近在配置Micronaut HTTP客户端,要和一个使用自签名证书的HTTPS服务器做双向TLS(mTLS)通信——这个服务器没有使用CA证书。我按照Micronaut的文档设置了客户端的密钥库和信任库,跳过了根CA证书,但请求一直返回401 Unauthorized错误,实在搞不懂哪里出问题了。
问题现象
- mTLS通信在Postman和终端curl中完全正常,但用Micronaut HTTP客户端调用就返回401 Unauthorized
环境信息
- Micronaut版本:3.9.6
客户端application.yml配置
micronaut: http: client: ssl: enabled: true key-store: path: classpath:certs/client.p12 password: changeit type: PKCS12 trust-store: path: classpath:certs/truststore.jks # 直接用服务器证书作为信任库 password: changeit type: JKS
预期行为
- Micronaut HTTP客户端应该正确发送客户端证书
- 请求不应该被401拒绝
- 既然Postman和curl都能正常工作,Micronaut也该有同样的表现
补充排查信息
抓包发现的异常
我用Wireshark做了网络抓包,发现客户端证书的长度存在异常,这可能是服务器拒绝请求的原因。
可正常运行的Express.js参考示例
require("dotenv").config(); const fs = require("fs"); const https = require("https"); const express = require("express"); const axios = require("axios"); const qs = require("qs"); const app = express(); const PORT = process.env.PORT || 3000; // 加载TLS证书和密钥 const options = { rejectUnauthorized: false, key: fs.readFileSync("./certs/transport.key"), cert: fs.readFileSync("./certs/transport.pem") }; const CLIENT_ID = process.env.CLIENT_ID; const CLIENT_SECRET = process.env.CLIENT_SECRET; const TOKEN_URL = process.env.TOKEN_URL || "https://as1.lab.openbanking.sa/token"; app.use(express.json()); app.use((req, res, next) => { console.log(`[${new Date().toISOString()}] ${req.method} ${req.path}`); next(); }); // 获取访问令牌的函数 const getAccessToken = async () => { try { const authHeader = `Basic ${Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`).toString("base64")}`; const requestBody = qs.stringify({ grant_type: "client_credentials", scope: "accounts openid", }); console.log("Requesting token from API..."); console.log(`Authorization: ${authHeader}`); console.log(`Request Body: ${requestBody}`); const response = await axios.post(TOKEN_URL, requestBody, { headers: { "Content-Type": "application/x-www-form-urlencoded", "Authorization": authHeader, "Accept": "application/json" }, httpsAgent: new https.Agent(options), }); console.log("Token Response:", response.data); return response.data; } catch (error) { console.error(" API Error:", error.response ? error.response.data : error.message); throw error; } }; app.post("/public", async (req, res) => { try { const tokenData = await getAccessToken(); res.json(tokenData); } catch (error) { res.status(500).json({ error: "Failed to retrieve token" }); } }); app.listen(PORT, (error) =>{ if(!error) console.log("Server is Successfully Running, and App is listening on port "+ PORT) else console.log("Error occurred, server can't start", error); } );
尝试原生方式配置SSL上下文,但问题依旧
我还试过用Java原生方式手动构建SSL上下文并配置到HttpClient,不过还是返回401错误:
public HttpResponse<String> postRequest() throws Exception { // 加载PKCS12格式的密钥库 KeyStore keyStore = KeyStore.getInstance("PKCS12"); try (FileInputStream keyFile = new FileInputStream("src/main/resources/certs/client.p12")) { keyStore.load(keyFile, "123456".toCharArray()); } // 初始化密钥管理器工厂 KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); kmf.init(keyStore, "123456".toCharArray()); // 信任所有证书(绕过SSL验证) TrustManager[] trustAllCertificates = new TrustManager[]{ new X509TrustManager() { public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {} public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) {} public java.security.cert.X509Certificate[] getAcceptedIssuers() { return null; } } }; // 禁用主机名验证 HostnameVerifier allHostsValid = (hostname, session) -> true; // 创建TLS 1.2的SSL上下文 SSLContext sslContext = SSLContext.getInstance("TLSv1.2"); sslContext.init(kmf.getKeyManagers(), trustAllCertificates, new SecureRandom()); // 用自定义SSL上下文创建HTTP客户端 HttpClient httpClient = HttpClient.newBuilder() .sslContext(sslContext) .version(HttpClient.Version.HTTP_2) .build(); String formData = "grant_type=client_credentials&scope=accounts%20openid"; String basicToken = Base64.getEncoder().encodeToString(("clientid" + ":" + "clientsecret").getBytes(StandardCharsets.UTF_8)); // 构建POST请求 HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://as1.lab.openbanking.sa/token")) .header("Authorization", "Basic " + basicToken) .header("Content-Type", "application/x-www-form-urlencoded") .POST(HttpRequest.BodyPublishers.ofString(formData)) .build(); HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); System.out.println("Response Code: " + response.statusCode()); System.out.println("Response Body: " + response.body()); return response; }
备注:内容来源于stack exchange,提问作者Vibhanshu Shekhar
相关产品推荐
相关产品推荐

