Сообщение SOAP для веб-службы — код ответа HTTP: 403 для URL-адреса

Я пытаюсь отправить сообщение SOAP в файле XML веб-службе, а затем получить двоичный вывод и декодировать его. Конечная точка использует протокол HTTPS, поэтому я использовал TrustManager в своем коде, чтобы избежать PKIX проблем. Вы можете увидеть мой код здесь:

import javax.net.ssl.*;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.security.cert.X509Certificate;

public class Main{
    public static void sendSoapRequest() throws Exception {
        String SOAPUrl = "URL HERE";
        String xmlFile2Send = ".\\src\\request.xml";
        String responseFileName = ".\\src\\response.xml";
        String inputLine;

        TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
            public java.security.cert.X509Certificate[] getAcceptedIssuers() { return null; }
            public void checkClientTrusted(X509Certificate[] certs, String authType) { }
            public void checkServerTrusted(X509Certificate[] certs, String authType) { }

        } };

        SSLContext sc = SSLContext.getInstance("SSL");
        sc.init(null, trustAllCerts, new java.security.SecureRandom());
        HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());

        // Create all-trusting host name verifier
        HostnameVerifier allHostsValid = new HostnameVerifier() {
            public boolean verify(String hostname, SSLSession session) { return true; }
        };
        // Install the all-trusting host verifier
        HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);

        // Create the connection with http
        URL url = new URL(SOAPUrl);
        URLConnection connection = url.openConnection();
        HttpURLConnection httpConn = (HttpURLConnection) connection;
        FileInputStream fin = new FileInputStream(xmlFile2Send);
        ByteArrayOutputStream bout = new ByteArrayOutputStream();

        copy(fin, bout);
        fin.close();

        byte[] b = bout.toByteArray();
        StringBuffer buf=new StringBuffer();
        String s=new String(b);

        b=s.getBytes();

        // Set the appropriate HTTP parameters.
        httpConn.setRequestProperty("Content-Length", String.valueOf(b.length));
        httpConn.setRequestProperty("Content-Type", "text/xml; charset=utf-8");
        httpConn.setRequestProperty("SOAPAction", "");
        httpConn.setRequestMethod("POST");
        httpConn.setDoOutput(true);

        OutputStream out = httpConn.getOutputStream();
        out.write(b);
        out.close();

        // Read the response.
        httpConn.connect();
        System.out.println("http connection status :"+ httpConn.getResponseMessage());
        InputStreamReader isr = new InputStreamReader(httpConn.getInputStream());
        BufferedReader in = new BufferedReader(isr);

        while ((inputLine = in.readLine()) != null)
            System.out.println(inputLine);
        FileOutputStream fos=new FileOutputStream(responseFileName);
        copy(httpConn.getInputStream(),fos);
        in.close();
    }

    public static void copy(InputStream in, OutputStream out) throws IOException {

        synchronized (in) {
            synchronized (out) {
                byte[] buffer = new byte[256];
                while (true) {
                    int bytesRead = in.read(buffer);
                    if (bytesRead == -1)
                        break;
                    out.write(buffer, 0, bytesRead);
                }
            }
        }
    }

    public static void main(String args[]) throws Exception {
        sendSoapRequest();
    }
}

Я получаю следующий код ошибки, когда я выполняю это.

Исключение в потоке "main" java.io.IOException: сервер вернул код ответа HTTP: 403 для URL


person plaidshirt    schedule 31.07.2017    source источник
comment
Вы должны предоставить учетные данные для получения доступа. Заголовок аутентификации с логином/паролем в base64 для базовой аутентификации, самый простой вариант, но зависит от серверной части. Вы должны указать, какой сервер требуется.   -  person user1516873    schedule 31.07.2017
comment
@user1516873 user1516873: аутентификация на этом сервере не требуется.   -  person plaidshirt    schedule 31.07.2017
comment
403-запрещено означает, что запрос дошел до сервера и действителен, но сервер отказал в доступе к запрошенному ресурсу. Подводя итог, SSL-соединение в порядке, поэтому вы вызываете неправильную конечную точку или в заголовке SOAP отсутствуют учетные данные.   -  person pedrofb    schedule 15.08.2017
comment
@pedrofb: понятно, но когда я использую содержимое файлов XML в запросе SOAP с SoapUI, я получаю ответ от той же конечной точки.   -  person plaidshirt    schedule 16.08.2017
comment
Если содержимое одинаковое, проверьте и сравните заголовки, которые фактически отправляют SOAPUI, и ваше соединение. Возможно, сервер обнаруживает какую-то некорректность и интерпретирует ее как 403.   -  person pedrofb    schedule 16.08.2017
comment
Как проверить заголовок с помощью кода Java?   -  person plaidshirt    schedule 16.08.2017
comment
@pedrofb: есть ли другой способ отправить XML-файл на заданный веб-сервис и перехватить ответ?   -  person plaidshirt    schedule 16.08.2017
comment
Вы можете использовать локальный прокси-сервер, например fiddler, для проверки запросов, которые отправляются из вашего кода. Вы также можете использовать плагин Postman для Chrome, чтобы вызвать URL-адрес, предоставляющий контент, заголовки и т. д.   -  person pedrofb    schedule 16.08.2017
comment
@pedrofb: насколько я знаю, почтальон предназначен только для REST.   -  person plaidshirt    schedule 17.08.2017
comment
Запрос SOAP отправляется по HTTP с использованием запроса, подобного POST. Вы можете использовать почтальон идеально для этого   -  person pedrofb    schedule 18.08.2017
comment
@pedrofb: я попробовал это с Почтальоном с тем же содержимым, что и в файле request.xml, и получил ответ. Я отправил запрос как application/xml. Он двоичный, поэтому не смог его прочитать.   -  person plaidshirt    schedule 18.08.2017
comment
Если с почтальоном и SOAP UI работает хорошо, значит проблема в вашем Java-коде. Вы пытались использовать application/xml вместо text/xml в коде Java? Вы проверяли заголовки/полезную нагрузку с помощью прокси? Если вы не устанавливали прокси, попробуйте отправить запрос на конечную точку, созданную здесь: requestb.in.   -  person pedrofb    schedule 18.08.2017
comment
@pedrofb: я попробовал Requestb.in, но тело кажется пустым, оно говорит: «Нет». Я тоже пробовал с application/xml.   -  person plaidshirt    schedule 18.08.2017
comment
Возможно, вы отправляли пустое тело. Попробуйте удалить String s=new String(b); b=s.getBytes(); и добавить out.flush(); перед out.close()   -  person pedrofb    schedule 18.08.2017
comment
Я удалил и добавил упомянутые строки, но тот же вывод: статус http-соединения: Запрещено   -  person plaidshirt    schedule 18.08.2017
comment
Вы уверены, что URL-адрес, используемый SoapUI (из wsdl), и тот, который у вас есть в коде, одинаков (и не имеет забавных кодировок?)   -  person ThomasRS    schedule 21.08.2017


Ответы (4)


Ваша реализация в порядке, на самом деле проблема связана с вашим заголовком Content-Type.

Значение text/xml; charset=utf-8 — это значение Content-Type по умолчанию для SOAP 1.1, которое, вероятно, не соответствует вашей версии. SOAP 1.2 ожидает заголовок типа application/soap+xml; charset=utf-8, поэтому изменение строки кода на приведенную ниже заставит его работать:

httpConn.setRequestProperty("Content-Type", "application/soap+xml; charset=utf-8");

В SoapUI можно проверить заголовки, вызывающие запрос, и перейти на вкладку Заголовки в нижней части окна:

введите описание изображения здесь

Затем вы можете сравнить различия между конфигурациями вашего приложения и конфигурациями SoapUI.

person bosco    schedule 20.08.2017
comment
Я не могу найти заголовки в SoapUI и получаю точно такую ​​же ошибку с этим Content-Type. - person plaidshirt; 21.08.2017
comment
Заголовки находятся на стороне ответа, а не в запросе. Сделайте запрос и проверьте там. - person bosco; 21.08.2017
comment
Это application/timestamp-reply на стороне ответа. - person plaidshirt; 22.08.2017
comment
Можно ли поделиться конечной точкой? Или хотя бы сервисный проект на GitHub? Очень сложно угадать особенности вашей реализации. - person bosco; 22.08.2017
comment
Извините, но он доступен только из локальной сети. - person plaidshirt; 22.08.2017
comment
@bosco: это решило мою аналогичную проблему. Благодаря тонну!. - person BioLogic; 14.12.2020

Ошибка 403 может быть связана с отправкой ваших заголовков запроса мыла на сервер. Все действительные хосты позволят вашему Java-приложению доверять SSL-сертификату для URL-адреса. Проверьте, ожидает ли ваш сервер мыльный заголовок с именем пользователя/паролем. Если у вас есть доступ к этому серверу, вы можете проверить журналы веб-сервера, где ваш запрос не работает. Код ошибки указывает на отсутствие заголовка Soap, особенно заголовков Soap с именем пользователя и паролем.

person tx fun    schedule 21.08.2017
comment
Имя пользователя/пароль не требуются, они не заданы в текущем запросе SoapUI. - person plaidshirt; 22.08.2017

Интересно, содержит ли ваш SOAP-запрос какую-либо информацию об аутентификации в заголовках, таких как SAML. Один из вариантов — в приведенном выше коде, где вы читаете файл и отправляете данные на сервер, вместо того, чтобы отправлять его на сервер, вы сбрасываете его в другой файл. Дамп этого byteoutputstream. Затем скопируйте текст из этого файла, поместите его в пользовательский интерфейс SOAP и попробуйте запустить его. Это работает?

person Gautam    schedule 22.08.2017
comment
Почему лучше сделать еще один файл с тем же содержимым? - person plaidshirt; 22.08.2017
comment
Это особенно важно, если ваш запрос SOAP содержит SAML или какие-либо другие двоичные данные. Это нужно для того, чтобы увидеть, не повреждается ли он во время ввода-вывода файла. Ваш код содержит этот небольшой фрагмент: byte[] b = bout.toByteArray(); StringBuffer buf=new StringBuffer(); String s=new String(b); b=s.getBytes();, где вы конвертируете данные в строку и обратно в байты. Есть ли какая-то конкретная необходимость в этом? - person Gautam; 22.08.2017
comment
Как предложил @pedrofb, я ранее удалил эти строки из кода. - person plaidshirt; 22.08.2017
comment
Ох, хорошо. И смогли ли вы перехватить заголовки, отправляемые из пользовательского интерфейса SOAP, по сравнению с теми, которые были отправлены из вашего кода. Тоже такие. - person Gautam; 22.08.2017
comment
Да, я отправил запрос в Requestbin, но тело было пустым. Что-то не так с моим кодом Java? - person plaidshirt; 22.08.2017
comment
Не могу сказать о теле, так как ваш запрос передается через SSL, поэтому данные на самом деле не видны по проводу, кроме как что-то нечитаемое. Возможно, поэтому Requestbin его не отображает. Но чтобы быть уверенным, я попросил вас попробовать распечатать полезную нагрузку. Скопируйте его в файл или распечатайте в консоли, как хотите. - person Gautam; 22.08.2017
comment
В качестве запоздалой мысли попробуйте закрыть поток вывода в конце обработки. Я думаю, что столкнулся с аналогичной проблемой, когда данные не отправлялись по сети много лет назад. Я проверил, и он работает с Java 7, который у меня есть, но попробуйте, если можете. - person Gautam; 22.08.2017

В похожей ситуации мы уже были некоторое время назад, и пока попытка TrustManager не сработала должным образом, нам удалось преодолеть эту проблему, установив сертификат с сервера в хранилище ключей JVM (JVM использовалась для запуска приложения). Дополнительную информацию о том, как это сделать, вы можете найти в нескольких сообщениях, например Как импортировать сертификат .cer в хранилище ключей Java?

Я знаю, что это попытка заставить JVM принимать SSL-сертификаты, и эту функциональность было бы лучше использовать в контексте приложения, но пока мы создавали веб-приложение, которое работало на определенных серверах приложений, реализованное решение было принял один.

person lzagkaretos    schedule 22.08.2017