如何使用 REST API 与 java 连接 JIRA?

How connection with JIRA using RESTAPI with java?

我的任务是使用 RESTAPI 在 java 中建立与 JIRA 的连接。我遇到 SSL 安全证书错误。我已经尝试了很多次并查看了 google,但我没有找到任何解决我的问题的方法。谁能帮我解决这个错误?

APOD.java

package com.jiraconnection;
import com.fasterxml.jackson.annotation.JsonProperty;

public class APOD {
    public final String expand;
    public final String id;
    public final String key;
    public final String self;

    public APOD(@JsonProperty("expand") String expand,
        @JsonProperty("id") String id,
        @JsonProperty("key") String key,
        @JsonProperty("self") String self) {
        this.expand = expand;
        this.id = id;
        this.key = key;
        this.self = self;

    }
}

JavaHttpURLConnectionDemo.java

import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;

public class JavaHttpURLConnectionDemo {

   public static void main(String[] args) throws IOException {

    // Create a neat value object to hold the URL
    URL url = new URL("https://jira.atlassian.com/rest/api/latest/issue/JRA-9");

    // Open a connection(?) on the URL(?) and cast the response(??)
    HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();

    // Now it's "open", we can set the request method, headers etc.
    connection.setRequestProperty("accept", "application/json");

    // This line makes the request
    InputStream responseStream = connection.getInputStream();

    // Manually converting the response body InputStream to APOD using Jackson
    ObjectMapper mapper = new ObjectMapper();
    APOD apod = mapper.readValue(responseStream, APOD.class);

    // Finally we have the response
    System.out.println(apod.expand);

   }

}

错误

    Exception in thread "main" javax.net.ssl.SSLHandshakeException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
        at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:131)
        at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:324)
        at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:267)
        at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:262)
        at java.base/sun.security.ssl.CertificateMessage$T13CertificateConsumer.checkServerCerts(CertificateMessage.java:1340)
        at java.base/sun.security.ssl.CertificateMessage$T13CertificateConsumer.onConsumeCertificate(CertificateMessage.java:1215)
        at java.base/sun.security.ssl.CertificateMessage$T13CertificateConsumer.consume(CertificateMessage.java:1158)

HttpsURLConnection 默认使用 JDK 可信证书来验证服务器证书是否已知和可信。如果它在那边,它不会抛出 SSLHandshakeException

Jira 当前具有以下证书链:

您可以通过在初始化 HttpsURLConnection 后添加断点轻松验证您的 HttpsURLConnection 是否具有受信任的证书,请参见下面的示例:

所以我的假设是,在您的情况下,JDK 信任库中不存在证书。你可以做的是提取 Jira 证书,就像这里显示的那样:Using openssl to get the certificate from a server 后记要么将它导入到 cacert 文件中:$JAVA_HOME/lib/security/cacerts。但我认为为您的 HttpsUrlConnection 提供自定义 ssl 配置会更好,因为我认为它更易于维护。但是这两个选项都可以。

因此,以下代码片段适用于使用自定义 trustore 时的选项 2:

选项 2

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.KeyStore;
import java.util.Objects;
import java.util.stream.Collectors;

public class App {

    public static void main(String[] args) throws Exception {
        Path trustStorePath = Paths.get("/path/to/your/truststore.jks");
        char[] trustStorePassword = "my-password".toCharArray();

        KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
        try(InputStream inputStream = Objects.requireNonNull(Files.newInputStream(trustStorePath))) {
            trustStore.load(inputStream, trustStorePassword);
        }

        TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        trustManagerFactory.init(trustStore);

        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
        SSLSocketFactory socketFactory = sslContext.getSocketFactory();

        URL url = new URL("https://jira.atlassian.com/rest/api/latest/issue/JRA-9");
        HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
        connection.setSSLSocketFactory(socketFactory);
        connection.setRequestProperty("accept", "application/json");

        try(InputStream inputStream = connection.getInputStream();
            InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
            BufferedReader bufferedReader = new BufferedReader(inputStreamReader)) {

            String responseBody = bufferedReader.lines().collect(Collectors.joining(System.lineSeparator()));
            System.out.println(responseBody);
        }

    }

}

或者您可以在没有文件系统中的自定义信任库的情况下内联执行所有操作,如下所示。通过这种方式,您将根 ca 作为 pem,即 DigiCert High Assurance EV ROOT CA 并以编程方式将其加载到您的内存信任库中。

选项 3

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.security.KeyStore;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.util.Arrays;
import java.util.Base64;
import java.util.UUID;
import java.util.stream.Collectors;

public class App {

    public static void main(String[] args) throws Exception {
        String atlassian = ""
                + "MIIFdzCCBP6gAwIBAgIQAgzZlKL4HKlpT4RkDXUi8TAKBggqhkjOPQQDAzBWMQsw"
                + "CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMTAwLgYDVQQDEydEaWdp"
                + "Q2VydCBUTFMgSHlicmlkIEVDQyBTSEEzODQgMjAyMCBDQTEwHhcNMjIwNTEwMDAw"
                + "MDAwWhcNMjMwNjEwMjM1OTU5WjBuMQswCQYDVQQGEwJBVTEYMBYGA1UECBMPTmV3"
                + "IFNvdXRoIFdhbGVzMQ8wDQYDVQQHEwZTeWRuZXkxGjAYBgNVBAoTEUF0bGFzc2lh"
                + "biBQdHkgTHRkMRgwFgYDVQQDDA8qLmF0bGFzc2lhbi5jb20wWTATBgcqhkjOPQIB"
                + "BggqhkjOPQMBBwNCAAR7p4KtlAjEKMIH66rdbCXtkR5nO20hqZco8B/L+EuJ9mqJ"
                + "PT4dmaDR8OZWzlLXfqiKKhtxuPckC5dtwns4kXyAo4IDlDCCA5AwHwYDVR0jBBgw"
                + "FoAUCrwIKReMpTlteg7OM8cus+37w3owHQYDVR0OBBYEFMw8XjgJmgk4VGCBWOyG"
                + "Lr/FMrRZMCkGA1UdEQQiMCCCDyouYXRsYXNzaWFuLmNvbYINYXRsYXNzaWFuLmNv"
                + "bTAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMC"
                + "MIGbBgNVHR8EgZMwgZAwRqBEoEKGQGh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9E"
                + "aWdpQ2VydFRMU0h5YnJpZEVDQ1NIQTM4NDIwMjBDQTEtMS5jcmwwRqBEoEKGQGh0"
                + "dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRMU0h5YnJpZEVDQ1NIQTM4"
                + "NDIwMjBDQTEtMS5jcmwwPgYDVR0gBDcwNTAzBgZngQwBAgIwKTAnBggrBgEFBQcC"
                + "ARYbaHR0cDovL3d3dy5kaWdpY2VydC5jb20vQ1BTMIGFBggrBgEFBQcBAQR5MHcw"
                + "JAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBPBggrBgEFBQcw"
                + "AoZDaHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VExTSHlicmlk"
                + "RUNDU0hBMzg0MjAyMENBMS0xLmNydDAJBgNVHRMEAjAAMIIBgQYKKwYBBAHWeQIE"
                + "AgSCAXEEggFtAWsAdwCt9776fP8QyIudPZwePhhqtGcpXc+xDCTKhYY069yCigAA"
                + "AYCt4OEbAAAEAwBIMEYCIQC37AW4L7CCrKn0+kTydWf3zn6tdkFOg/ZI3mUU4/P3"
                + "CwIhAOnIlT0eX2nzpr6+d3GReTVlVf5+coiyYsSOJWOANM2ZAHcANc8ZG7+xbFe/"
                + "D61MbULLu7YnICZR6j/hKu+oA8M71kwAAAGAreDg7wAABAMASDBGAiEAj67xO2t2"
                + "OVtwSjLdsD8RknexjRqu+Ifwp5wO/2p8a84CIQDw9OKwzRnQ4cxYPKPrIYGm5hbH"
                + "KfVwcBMmo0u0XQ2YlQB3ALNzdwfhhFD4Y4bWBancEQlKeS2xZwwLh9zwAw55NqWa"
                + "AAABgK3g4SEAAAQDAEgwRgIhAP/l3SLBl5/9RHQvd5GjApgGAne4J/XnA68l/vQp"
                + "x7jHAiEAxWnEzde1lf4a1kMuFUyc6fBUE88GVb+zC9rjv4KCDcQwCgYIKoZIzj0E"
                + "AwMDZwAwZAIwFcy1X+o/HkXrM8rFdcjBGCieA0oBeRSSifale32U36xquKPBtSvm"
                + "2g/HAZh2N3DDAjBM4zmAiD0WTA0o3Fnh03mIwP/98RqXvjiDUL/bzovejseo8eRp"
                + "FDjNl90IcJuAoGc=";

        String digicertCa = ""
                + "MIIEFzCCAv+gAwIBAgIQB/LzXIeod6967+lHmTUlvTANBgkqhkiG9w0BAQwFADBh"
                + "MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3"
                + "d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD"
                + "QTAeFw0yMTA0MTQwMDAwMDBaFw0zMTA0MTMyMzU5NTlaMFYxCzAJBgNVBAYTAlVT"
                + "MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxMDAuBgNVBAMTJ0RpZ2lDZXJ0IFRMUyBI"
                + "eWJyaWQgRUNDIFNIQTM4NCAyMDIwIENBMTB2MBAGByqGSM49AgEGBSuBBAAiA2IA"
                + "BMEbxppbmNmkKaDp1AS12+umsmxVwP/tmMZJLwYnUcu/cMEFesOxnYeJuq20ExfJ"
                + "qLSDyLiQ0cx0NTY8g3KwtdD3ImnI8YDEe0CPz2iHJlw5ifFNkU3aiYvkA8ND5b8v"
                + "c6OCAYIwggF+MBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFAq8CCkXjKU5"
                + "bXoOzjPHLrPt+8N6MB8GA1UdIwQYMBaAFAPeUDVW0Uy7ZvCj4hsbw5eyPdFVMA4G"
                + "A1UdDwEB/wQEAwIBhjAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwdgYI"
                + "KwYBBQUHAQEEajBoMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5j"
                + "b20wQAYIKwYBBQUHMAKGNGh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdp"
                + "Q2VydEdsb2JhbFJvb3RDQS5jcnQwQgYDVR0fBDswOTA3oDWgM4YxaHR0cDovL2Ny"
                + "bDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0R2xvYmFsUm9vdENBLmNybDA9BgNVHSAE"
                + "NjA0MAsGCWCGSAGG/WwCATAHBgVngQwBATAIBgZngQwBAgEwCAYGZ4EMAQICMAgG"
                + "BmeBDAECAzANBgkqhkiG9w0BAQwFAAOCAQEAR1mBf9QbH7Bx9phdGLqYR5iwfnYr"
                + "6v8ai6wms0KNMeZK6BnQ79oU59cUkqGS8qcuLa/7Hfb7U7CKP/zYFgrpsC62pQsY"
                + "kDUmotr2qLcy/JUjS8ZFucTP5Hzu5sn4kL1y45nDHQsFfGqXbbKrAjbYwrwsAZI/"
                + "BKOLdRHHuSm8EdCGupK8JvllyDfNJvaGEwwEqonleLHBTnm8dqMLUeTF0J5q/hos"
                + "Vq4GNiejcxwIfZMy0MJEGdqN9A57HSgDKwmKdsp33Id6rHtSJlWncg+d0ohP/rEh"
                + "xRqhqjn1VtvChMQ1H3Dau0bwhr9kAMQ+959GG50jBbl9s08PqUU643QwmA==";

        String digicertRootCa = ""
                + "MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh"
                + "MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3"
                + "d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD"
                + "QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT"
                + "MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j"
                + "b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG"
                + "9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB"
                + "CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97"
                + "nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt"
                + "43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P"
                + "T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4"
                + "gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO"
                + "BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR"
                + "TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw"
                + "DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr"
                + "hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg"
                + "06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF"
                + "PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls"
                + "YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk"
                + "CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=";

        KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
        trustStore.load(null);

        for (String certificateContent : Arrays.asList(atlassian, digicertCa, digicertRootCa)) {
            byte[] decodedCertificate = Base64.getDecoder().decode(certificateContent);

            try(ByteArrayInputStream certificateAsInputStream = new ByteArrayInputStream(decodedCertificate);
                BufferedInputStream bufferedCertificateStream = new BufferedInputStream(certificateAsInputStream)) {

                CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
                Certificate certificate = certificateFactory.generateCertificate(bufferedCertificateStream);
                trustStore.setCertificateEntry(UUID.randomUUID().toString(), certificate);
            }
        }

        TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        trustManagerFactory.init(trustStore);

        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
        SSLSocketFactory socketFactory = sslContext.getSocketFactory();

        URL url = new URL("https://jira.atlassian.com/rest/api/latest/issue/JRA-9");
        HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
        connection.setSSLSocketFactory(socketFactory);
        connection.setRequestProperty("accept", "application/json");

        try(InputStream inputStream = connection.getInputStream();
            InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
            BufferedReader bufferedReader = new BufferedReader(inputStreamReader)) {

            String responseBody = bufferedReader.lines().collect(Collectors.joining(System.lineSeparator()));
            System.out.println(responseBody);
        }
    }

}

最后的手段是禁用证书验证,但我不推荐这样做。下面是一个例子:

选项 4

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Socket;
import java.net.URL;
import java.security.cert.X509Certificate;
import java.util.stream.Collectors;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509ExtendedTrustManager;

public class App {

    public static void main(String[] args) throws Exception {
        X509ExtendedTrustManager unsafeTrustManager = new X509ExtendedTrustManager() {
            @Override
            public void checkClientTrusted(X509Certificate[] x509Certificates, String s, Socket socket) {}

            @Override
            public void checkServerTrusted(X509Certificate[] x509Certificates, String s, Socket socket) {}

            @Override
            public void checkClientTrusted(X509Certificate[] x509Certificates, String s, SSLEngine sslEngine) {}

            @Override
            public void checkServerTrusted(X509Certificate[] x509Certificates, String s, SSLEngine sslEngine) {}

            @Override
            public void checkClientTrusted(X509Certificate[] x509Certificates, String s) {}

            @Override
            public void checkServerTrusted(X509Certificate[] x509Certificates, String s) {}

            @Override
            public X509Certificate[] getAcceptedIssuers() {
                return new X509Certificate[0];
            }
        };

        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, new TrustManager[]{ unsafeTrustManager }, null);
        SSLSocketFactory socketFactory = sslContext.getSocketFactory();

        URL url = new URL("https://jira.atlassian.com/rest/api/latest/issue/JRA-9");
        HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
        connection.setSSLSocketFactory(socketFactory);
        connection.setRequestProperty("accept", "application/json");

        try(InputStream inputStream = connection.getInputStream();
            InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
            BufferedReader bufferedReader = new BufferedReader(inputStreamReader)) {

            String responseBody = bufferedReader.lines().collect(Collectors.joining(System.lineSeparator()));
            System.out.println(responseBody);
        }
    }

}