某家网络身份认证公共服务意见征集这个新闻公布前几天公司的政公部门就发了对接的需求过来,经过一些填表操作终于在上周通过了审核,并发来了详细的对接文档。

踩坑记录

第一个坑

发过来的PDF文档和服务端接口相关的有两个文档,修订日期分别为2024年1月2023.12,文档中接收参数接口地址分别为http://auth.ctdid.cn:10002/https://几个ip:6444,本着用新不用旧的原则按2024年1月的版本文档做了实现,然后提示了第一个错误customerNo值不正确

经过咨询官方人员,改调用应急接入网关的地址,并且需要去掉sign签名参数。(也就是按照2023.12的文档去对接)

第二个坑

官方给文档时候同时给了几个tls使用到证书文件:

  1. all.pfx pkcs12 格式的客户端证书及私钥,支持Openssl 1.0.2k 及以上版本,默认口令 123456;
  2. cert.cer Pem格式的客户端证书;
  3. prikey.pem Pem格式,明文RSA PKCS8私钥文件,需妥善保管,避免遗失,不要泄露;
  4. downstream_root.cer Pem格式的根证书.

根据应急接入网关的地址这个文档给的java实现demo结合2023.12这份文档去重新实现之后碰到第二个坑Server returned HTTP response code: 400 for URL: https://36.102.210.76:6444/uentrance/interf/auth/request,证书导入POSTMAN请求可以成功。

尝试ChatGPT、Google无果,再次请教官方人员,给了另外一段demo代码,证书从pkcs12改为jks。请求的接口终于通了

转换证书格式会用到的一些命令:

1
2
3
4
  openssl pkcs12 -export -out client.pfx -inkey prikey.pem -in cert.cer -passout pass:123456
  keytool -list -v -keystore client.pfx -storetype PKCS12
  keytool -importcert -file downstream_root.cer -keystore ca.jks  -storepass 123456
  keytool -importkeystore -srckeystore client.pfx -destkeystore client.jks -srcstoretype pkcs12 -deststoretype jks -srcstorepass 123456

最终代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
    private static final String CLIENT_NAME = "client.jks"; // jks格式密钥库路径
    private static final String CA_NAME = "ca.jks"; // CA证书路径
    /**
     * 发送http请求
     *
     * @param req 请求数据
     * @return
     */
    @SneakyThrows
    private String http(String req) {
        // Load client keystore
        KeyStore keyStore = KeyStore.getInstance("JKS");
        try (InputStream keyStoreStream = getClass().getResourceAsStream("/" + CLIENT_NAME)) {
            keyStore.load(keyStoreStream, KEY_STORE_PASSWORD.toCharArray());
        }

        // Load CA keystore
        KeyStore trustStore = KeyStore.getInstance("JKS");
        try (InputStream trustStoreStream = getClass().getResourceAsStream("/" + CA_NAME)) {
            trustStore.load(trustStoreStream, KEY_STORE_PASSWORD.toCharArray());
        }

        // Initialize SSL context with keystore and truststore
        SSLContext sslContext = SSLContextBuilder.create()
                                                 .loadKeyMaterial(keyStore, KEY_PASSWORD.toCharArray())
                                                 .loadTrustMaterial(trustStore, new TrustSelfSignedStrategy())
                                                 .build();

        // Create HttpClient with custom SSL context
        try (CloseableHttpClient httpClient = HttpClients.custom()
                                                         .setSSLContext(sslContext)
                                                         .build()) {
            // Create a POST request with the provided URL
            HttpPost httpPost = new HttpPost(API);
            httpPost.setEntity(new StringEntity(req));
            httpPost.setHeader("Content-Type", "application/json");

            // Execute the request
            try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
                // Read the response
                HttpEntity entity = response.getEntity();
                return entity != null ? EntityUtils.toString(entity) : null;
            }
        }
    }

第三个坑

接口成功调用之后的返回值,用户的标识在文档中写的是第1-40字节: 网络身份应用标识,最终拿到这个网络身份应用标识的代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
    new Base64().encodeToString(subBytes(new Base64().decode(pid), 1, 40));
    /**
     * 截取字节数组
     *
     * @param src   源字节数组
     * @param begin 起始位置
     * @param count 截取长度
     * @return
     */
    private static byte[] subBytes(byte[] src, int begin, int count) {
        var result = new byte[count];
        System.arraycopy(src, begin, result, 0, count);
        return result;
    }

最佳实践

  1. 参考 应急安全接入服务平台接口说明书(网络身份认证)(V2.0).pdf 中接口地址+接口入参
  2. 使用应急接入平台接入及鉴权证书使用说明v1.23.pdf中提到的自签名证书的部分使用方式结合第二个坑中代码
  3. 参考国家网络身份认证公共服务接口说明书(网络身份认证).pdf中接口返回值的说明文档
  4. 参考第三个坑中的代码拿到用户唯一标识