SOAP XML Request with Basic Authentication in PHP – Stack Overflow

Soap xml request with basic authentication in php

I am trying to setup a SOAP request in PHP using HTTP basic Authentication. For some reason I keep getting, HTTP/1.1 401 Unauthorized error.

This is an example of the request I’m trying to create:

POST https://url/someurl/soap HTTP/1.1
Accept-Encoding: gzip,deflate
Content-Type: text/xml;charset=UTF-8
SOAPAction: "SomeSoapAction"
User-Agent: SomeUser Client-HttpClient/3.1
Content-Length: 1503
Authorization: Basic dGsomebasicreyteryheyp0ZXN0ZXI=
Host: somehost.com  

This is a snippet of my code:

ini_set('display_errors',1);
ini_set("soap.wsdl_cache_enabled", "0");
error_reporting(E_ALL);
$wsdl = "urltosomewsdlfile.wsdl";
$url = "somerl";
$username = "username";
$password = "password";
$encodedstuff = base64_encode("{$username}:{$password}");

$client = new SoapClient($wsdl, array('exceptions' => true,'trace' => true,'login' => 'somelogin', 'password' => 'somepw'));
$header = new SoapHeader($url, 'Authorization: Basic', $encodedstuff);
$client->__setSoapHeaders($header);

try {
    $result = $client->SomeSoapAction();
    } catch (SoapFault $soapFault) {
        var_dump($soapFault);
        echo "<br />Request :<br />", htmlentities($client->__getLastRequest()), "<br />";
        echo "<br />Response :<br />", htmlentities($client->__getLastResponse()), "<br />";
    }

If I take the username and password out of the new SoapClient section it throws up Uncaught SoapFault exception: [WSDL] SOAP-ERROR: Parsing WSDL: Couldn’t load from…

If I put in the base64 encoded variable to the new SoapClient section instead of the username/pw it throws the same Parsing error as above:
$client = new SoapClient($wsdl, array( ‘authentication’ => “Basic $encodedstuff”, ‘exceptions’ => true,’trace’ => true,));

The username and password work when loading the URL directly.

Can anyone point me in the right direction? Thanks for any help.


Update 12/18/18: I’ve been trying this as a new method suggested, but still receive the same 401 error message:

$opts = array( 'http'=>array( 'method'=>"POST", 'header' => "Authorization: Basic " . base64_encode("{$user}:{$pw}") ) ); 
$context = stream_context_create($opts); 
$client = new SoapClient($wsdl, array('stream_context' => $context,'exceptions' => true,'trace' => true,)); 
$header = new SoapHeader($url, 'Authorization: Basic', $encodedstuff); 
$client->__setSoapHeaders($header);

Update 15/01/19:
I’m still having issues with this.

Using SOAPUI I can make a successful request and gain authentication. I can’t see what the difference is between the SOAPUI request and my own PHP request.

I’ve noticed that the request header created by my PHP code alters the POST and Host endpoint URLs. The first part of the POST URL has the domain removed and the Host also has the first section of the URL removed.

SOAPUI request header (correct returns 200):

POST https://test-example.com/file/SOAP HTTP/1.1
Host: test-example.com

PHP request header (returns a 401):

POST /file/SOAP HTTP/1.1
Host: example.com

This seems to be causing the problem as it looks as if I’m pointing to the wrong endpoint. Does anyone have any advice on what might be wrong with my PHP code to break this request? Thanks.

Wsdl to php with basic auth

i’ve been trying to resolve this issue, but from what i understand, soap client connections to ssl httpauth web services are more pain. I’ve googled and from what i understand, with my problem being solved, you can use the example below to solve your problem too(by using HttpAuth infos in both url and soapClient setup).

$username="test";
$password="test";
$url = "https://".urlencode($username).":".urlencode($password)."@example.com/service.asmx?WSDL";

$context = stream_context_create([
'ssl' => [
// set some SSL/TLS specific options
'verify_peer' => false,
'verify_peer_name' => false,
'allow_self_signed' => true,
]]);

$client = new SoapClient($url, [
'location' => $url,
'uri' => $url,
'stream_context' => $context,
'login' => $username,
'password' => $password
]);

$params=array(
'operation'=>’arguments',
'and’=>'other bull',
'goes’=>'here'
);

$response = $client->__soapCall('OperationName', array($params)); 

Добавляем basic auth в soap запрос средствами ksoap2-android

Так получилось, что в рамках своей работы я связался с проектом по разработке приложения для общения Android и 1С. Быстрый поиск в интернете дал достаточно четкие инструкции и куски кода, которые очень быстро оформились в готовую программу, но запускаться она не хотела. Здесь я расскажу основные тонкости и способы их решения.

Первоисточники кода расписаны здесь и тут. Необходимо только подключить стороннюю библиотеку ksoap2-android, чтобы стали доступны классы SoapObject и HttpTransportSE. Старый проверенный способ скачать jar с официального репозитория и положить его в app/libs почему-то не увенчался успехом, и я стал смотреть, как подключить библиотеку используя современный Gradle. Почти на всех ресурсах было написано, что простое добавление

dependencies {
    compile 'com.google.code.ksoap2-android:ksoap2-android:3.6.1'
}

должно «подтягивать» нужные ресурсы. Но этого не произошло, т.к. сам ресурс не находится в стандартных репозиториях. Значит, надо указать, откуда его скачивать. Делается это добавлением в Gradle файл проекта следующих строк:

buildTypes {
    repositories {
        maven { url 'https://oss.sonatype.org/content/repositories/ksoap2-android-releases' }
    }
} 

После этих простых действий просто синхронизируем проект, и Gradle закачает все, что нужно.

Вот код, который начинает работать после вышеописанных процедур:

public class DataLoader extends AsyncTask<Void, Void, String> {
    private static final String NAMESPACE = "namespace";
    private static final String URL = "http://host/wsdlAcceptor?wsdl";
    private static final String SOAP_ACTION = "http://host/wsdlAcceptor";
    private static final String METHOD_NAME = "testOperation";

    @Override
    protected void onPreExecute() {
        super.onPreExecute();
    }

    @Override
    protected String doInBackground(Void... params) {
        try {
            SoapObject request = new SoapObject(NAMESPACE, METHOD_NAME);
            SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(SoapEnvelope.VER10);
            request.addProperty("IsFirstRequest", true);
            envelope.setOutputSoapObject(request);
            HttpTransportSE androidHttpTransport = new HttpTransportSE(URL);
            androidHttpTransport.debug = true;
            try {
                androidHttpTransport.call(SOAP_ACTION, envelope);
                SoapObject resultsRequestSOAP = (SoapObject) envelope.bodyIn;
                System.out.println("Response::" resultsRequestSOAP.toString());
            } catch (Exception e) {
                e.printStackTrace();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "";
    }

    @Override
    protected void onPostExecute(String result) {
        super.onPostExecute(result);
    }
}

Не секрет, что работа с любой сетевой активностью должна делаться из отдельной асинхронной задачи. doInBackground – это стандартный метод AsyncTask. Если еще не знакомы, то

вот здесь

про него написано просто и очень понятно.

Сама MainActivity выглядит следующим образом:

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        DataLoader dl = new DataLoader();
        dl.execute();
    }
}

На этом приключения не заканчиваются. В моем случае запрос к серверу был закрыт Basic Auth, поэтому потребовалось куда-то вводить логин и пароль. И вот на этот вопрос однозначного ответа в интернете я не нашел. Во многих статьях был упомянут некий класс HttpTransportBasicAuthSE, в конструктор которого и передается логин с паролем. Но его было не найти в ksoap2-android, пришлось искать в интернете. Нашелся

здесь

. Привожу полный текст:

public class HttpTransportBasicAuthSE extends HttpTransportSE {
    private String username;
    private String password;

    /**
     * Constructor with username and password
     *
     * @param url
     *            The url address of the webservice endpoint
     * @param username
     *            Username for the Basic Authentication challenge RFC 2617
     * @param password
     *            Password for the Basic Authentication challenge RFC 2617
     */
    public HttpTransportBasicAuthSE(String url, String username, String password) {
        super(url);
        this.username = username;
        this.password = password;
    }

    public ServiceConnection getServiceConnection() throws IOException {
        ServiceConnectionSE midpConnection = new ServiceConnectionSE(url);
        addBasicAuthentication(midpConnection);
        return midpConnection;
    }

    protected void addBasicAuthentication(ServiceConnection midpConnection) throws IOException {
        if (username != null && password != null) {
            StringBuffer buf = new StringBuffer(username);
            buf.append(':').append(password);
            byte[] raw = buf.toString().getBytes();
            buf.setLength(0);
            buf.append("Basic ");
            org.kobjects.base64.Base64.encode(raw, 0, raw.length, buf);
            midpConnection.setRequestProperty("Authorization", buf.toString());
        }
    }
}

Полагаю, комментарии к коду излишни. Уточню только, что для применения HttpTransportBasicAuthSE достаточно просто изменить строку в DataLoader

HttpTransportSE androidHttpTransport = new HttpTransportSE(URL);

на

HttpTransportBasicAuthSE androidHttpTransport = new HttpTransportBasicAuthSE(URL, "basicLogin", "authPassword");

и все начинает работать с авторизацией очень четко!

P.S.: не забывайте добавлять в Manifest разрешение на Internet

<uses-permission android:name="android.permission.INTERNET" />

Обновление библиотеки openssl

Метод собирать «из исходников» и засорять систему — не наш метод. Поэтому лучший вариант — найти готовый пакет, и я его нашел в репозитории axivo:

Правка и перекомпиляция php

Основная проблема заключается в том, что для того чтобы OpenSSL использовала файл конфигурации по умолчанию (именно там у нас прописаны настройки для алгоритмов ГОСТ) перед ее использованием необходимо вызвать функцию OPENSSL_config(NULL). В расширении PHP OpenSSL этого не сделано, поэтому модуль PHP-SOAP при использовании SSL-соединения не видит алгоритмов ГОСТ. Кстати, тоже самое касается и других библиотек, например curl. Ее тоже нужно патчить если вы собираетесь с ней работать.

Итак приступим. Для того чтобы нам поправить OpenSSL необходимо перекомпилировать весь PHP, так как в CentOS расширение OpenSSL сделано не модулем.


Подготавливаем среду для сборки пакетов:

# yum install rpm-build redhat-rpm-config
# mkdir /root/rpmbuild
# cd /root/rpmbuild
# mkdir BUILD RPMS SOURCES SPECS SRPMS
# mkdir RPMS/{i386,i486,i586,i686,noarch,athlon}

Качаем исходники PHP и устанавливаем их:

Преобразуем выданный ключ и сертификат в формат, понятный openssl

Для этого нам понадобится Windows машина с установленным CryptoPRO CSP 3.6. Я его слил с сайта разработчика (там дается демонстрационный режим режим на 3 месяца).

Первое что делаем — экспортируем ключевой контейнер в реестр средствами КриптоПро. Для этого открываем КриптоПро из панели управления Windows. Вставляем наш ключевой носитель в компьютер. Для обычной флешки убеждаемся, что у нас среди считывателей на вкладке «Оборудование» есть «Все съемные диски» и «Реестр».

Далее переходим на вкладку «Сервис» и нажимаем «Копировать», нажимаем «Обзор» и выбираем среди списка свой ключевой контейнер. Если его там вдруг не находим — то возвращаемся к предыдущему параграфу и все проверяем. Далее вводим осмысленное имя нового ключевого контейнера, нажимаем «Готово» и в качестве носителя в появившемся окне выбираем «Реестр».

Новый пароль на контейнер не ставим. Теперь нам нужно установить сертификат в новый контейнер. На вкладке «Сервис» в КриптоПро нажимаем кнопку «Установить личный Сертификат», указываем файл с нашим сертификатом, нажимаем «Далее», выбираем только что созданный нами контейнер и не забываем галочку «Установить сертификат в контейнер».

Для того чтобы экспортировать данный сертификат в формате PKSC#12 вместе с закрытым ключом — нам понадобится сторонняя утилита. Которую можно либо купить здесь либо найти на просторах интернета по имени p12fromgostcsp. Запускаем данную утилиту, выбираем наш сертификат, пароль оставляем пустым и сохраняем его в файл mycert.p12.

# openssl pkcs12 -in mycert.p12 -nodes


На запрос пароля просто нажимаем Enter. И смотрим наличие строк BEGIN CERTIFICATE и BEGIN PRIVATE KEY. Если строки есть то все в порядке. Преобразуем полученный сертификат в формат PEM:

# openssl pkcs12 -in mycert.p12 -out mycert.pem -nodes -clcerts

Если вам нужна не только авторизация по клиентскому сертификату, но и также проверка валидности самого сервера — вам понадобится корневой сертификат удостоверяющего центра. Его можно просто через Windows открыть и сохранить в формате DER в кодировке X.

# openssl x509 -inform DER -in cacert.cer -outform PEM -out cacert.pem

Где cacert.cer — имя исходного файла с корневым сертификатом. Проверить коннект с сервером с использованием сертификатов можно через OpenSSL командой:

Похожее:  Basics of authentication - GitHub Docs

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *