VIISP tapatybės nustatymo paslauga

Sveiki :)
Šį kartą norėčiau aprašyti apie fainą dalykėlį iš VIISP (elektroniniai valdžios vartai, anksčiau vadinosi VAIISIS), kurį naudoja daug žmonių, įskaitant rajonų, apylinkių ir miestelių svetaines – tai yra tapatybės nustatymo ir patvirtinimo paslauga kuri vyksta identifikavusis per bankus, asmens tapatybės kortele ir panašiai. Daug yra panaudojimo galimybių…
Istorijos kodėl aš su šita sistema užsiėmiau nepasakosiu, tiesiog papasakosiu kaip man pavyko įdiegti šią paslaugą.
ĮSPĖJIMAS: neatsakau už padarytus pakeitimus IVPK projektuose, nuorodose ar oficialioje dokumentacijoje kurie buvo padaryti po straipsnio publikavimo datos.

Viską suskirstau į grupes. Papasakosiu apie tapatybės nustatymo paslaugą, reikalingą medžiagą, resursus, pavyzdžius iš VIISP ir pateiksiu savo pavyzdinį kodą su PHP :)
Visą techninį VIISP aprašymą galima pasiskaityti čia.

Taigi, pradėkime. Štai pristatomas filmukas:

Pati paslauga yra nemokama, tik reikia pasirašyti sutartį. Naudojantis šia paslauga galima gauti tokius duomenis kaip vardas, pavardė, gimimo data, asmens kodas…

Šioje skiltyje galima rasti informacijos kokios mums reikės.
Tapatybės nustatymo specifikacijoje aprašyta viskas kaip veikia, kas ką perduoda ir kaip ką siųsti, o čia šioks toks tutorial’as parašytas pačių IVPK žmonių, kuris mano nuomone, galėtų būti išsamesnis. Prisegtas yra ir Java projektas, skirtas parašo generavimui kuris yra pagrindinis dalykas šitam reikale, apie jį kiek žemiau aprašysiu.

Panagrinėkime šiek tiek specifikaciją.

vaiisis_1

Žiūrint į šitą diagramą, paimtą iš dokumento, matosi ką reikia siųsti ir kas yra grąžinama, ir matosi kad visos užklausos yra daromos per POST’ą. Tačiau man daug nervų kainavo bandant siuntinėti bilietą per POST’ą kol buvo pasiūlyta tiesiog naudoti GET’ą, kas mano atveju suveikė puikiai. Vis vien yra siuntinėjama unikalus bilietas tai koks skirtumas kokiu būdu siųsi…

Iš diagramos gal nesimato, tačiau identifikacija vyksta dviem etapais. Pirmu etapu yra inicijuojama identifikacija, siunčiama XML’u pilnai suformuota užklausa, su parašu ir kitokia informacija. Jei pasitvirtina, gaunamas XML’o atsakymas su mums taip reikalingu unikaliu bilietu kuris galioja neilgai. Tą bilietą ištraukus iš gauto XML’o reikia siųsti į VIISP ir čia prasideda antras etapas. Vartotoją redirect’inam kartu su bilietu GET’e, o toliau tik laukiam atsakymo.
Kai vartotojas identifikuojasi ir Valdžios vartuose paspaudžia “Patvirtinti”, jį nukreipia į mūsų nustatytą Postback URL kartu su to žmogaus duomenimis XML formate.

Dabar būtų geras metas parsipūsti Eclipse jei dar neturi ir įsikelti Java projektą minėtą aukščiau.
Projektas generuoja pasirašytą XML’ą pagal turimą sertifikatą ir privatų raktą kurie įdedami į “Java Keystore’ą”, paprastai tariant į konteinerį su spyna kur saugoma sertifikatas ir privatus raktas, bet tai padaryti nėra tiesiogiai paprasta kaip pamatysime vėliau.

Taigi atsidarom AuthGenerator.java klasę, kuri generuoja parašą pirmam etapui ir matom:

private void generateRequest() throws Exception {
	AuthenticationRequestXml request = new AuthenticationRequestXml();
	request.setId(SIGNED_NODE_ID);
	request.setPid(PID);
	request.setCustomData("correlationData");
	request.setPostbackUrl("https://localhost");
	request.getAuthenticationAttribute().add(AuthenticationAttribute.LT_PERSONAL_CODE);
	request.getAuthenticationAttribute().add(AuthenticationAttribute.LT_COMPANY_CODE);
	request.getAuthenticationProvider().add(AuthenticationProviderXml.AUTH_PROVIDER_LT_IDENTITY_CARD);
	request.getAuthenticationProvider().add(AuthenticationProviderXml.AUTH_PROVIDER_LT_BANK);
	request.getAuthenticationProvider().add(AuthenticationProviderXml.AUTH_PROVIDER_SIGNATURE);
	request.getAuthenticationProvider().add(AuthenticationProviderXml.AUTH_PROVIDER_LOGIN_PASS);
//		request.setServiceTarget(ServiceTargetXml.SERVICE_TARGET_BUSINESS);
	request.getUserInformation().add(UserInformation.FIRST_NAME);
	request.getUserInformation().add(UserInformation.LAST_NAME);
	request.getUserInformation().add(UserInformation.COMPANY_NAME);

	Document doc = (Document) marshal(request);
	String xml = getSignedXml(doc.getFirstChild(), "#" + request.getId());

	System.out.println("Auth request XML, which goes directly into SOAP body (Note: whitespace is important!):\n");
	System.out.println(StringUtils.substringAfter(xml, "?>"));
}

Labai daug prirašyta, tad paaiškinsiu kokios eilutės ką daro.
Pirma request.setId(SIGNED_NODE_ID); nustato jūsų užklausos ID. Jis visai procedūrai įtakos neturi, tai gali vadintis kaip patogiau. Po default’u vadinasi "uniqueNodeId".

Toliau request.setPid(PID); nustato jūsų identifikacinį numerį. Testavimo metu naudojamas VSID000000000113, tačiau pasirašius sutartį bus suteikiamas unikalus kurį reikės įrašyti toje vietoje.

request.setCustomData("correlationData"); priima jūsų Koreliacinius duomenis. Paprastai sakant, čia galima įrašyti tai kas turėtų būti gražinama kartu su atsakymu XML’e. Generuojant parašą įrašote kokią tai kintamojo reikšmę ir po to gaunate tą pačią reikšmę atgal. Tai patogus dalykėlis kai reikia kažką pasižymėti darant užklausą kad kai gražinama žinotumėt kas ir kur. Nes po redirect’o jūsų skriptas pasibaigia…

request.setPostbackUrl("https://localhost"); va čia yra tas PostBack URL. Čia nustatyti reikia kur vartotojas bus gražinamas po to kai paspaus “Patvirtinti” valdžios vartuose. Turėkit omenį kad galima nurodyti ir URL be failo, kaip kad be index.php tarkime, tokiu būdu galėsite naudoti PHP funkcijas, tačiau setPostbackUrl() funkcijoje URL turi baigtis su slash’u (/), kitaip užklausa nenukeliaus tinkamai.

Toliau eina vartotojo atributai kuriuos norite gauti. Šiaip užtenka tik palikti request.getAuthenticationAttribute().add(AuthenticationAttribute.LT_PERSONAL_CODE); ir gausite asmens kodą.

Toliau, request.getAuthenticationProvider().add() nustato kokiais būdais leisite vartotojui identifikuotis. Mūsų projektui užteko tik bankų, tai ir palikome tik

request.getAuthenticationProvider().add(AuthenticationProviderXml.AUTH_PROVIDER_LT_BANK);

request.getUserInformation().add() prideda kokius norite duomenis gauti apie vartotoją. Vėlgi mūsų projektui pakako vardo, pavardės ir gimimo datos, plius asmens kodas. Todėl palikome:
request.getUserInformation().add(UserInformation.FIRST_NAME);
request.getUserInformation().add(UserInformation.LAST_NAME);
request.getUserInformation().add(UserInformation.BIRTHDAY);

Toliau kodas šioje klasėje neaktualus. Sugeneruota XML užklausa (mano atveju) atrodo taip (suformatuota dėl geresnio skaitomumo):

<authentication:authenticationRequest xmlns:authentication="http://www.epaslaugos.lt/services/authentication" xmlns:dsig="http://www.w3.org/2000/09/xmldsig#" xmlns:ns3="http://www.w3.org/2001/10/xml-exc-c14n#" id="uniqueNodeId">
<authentication:pid>VSID000000000113</authentication:pid>
<authentication:authenticationProvider>auth.lt.bank</authentication:authenticationProvider>
<authentication:authenticationAttribute>lt-personal-code</authentication:authenticationAttribute>
<authentication:userInformation>firstName</authentication:userInformation>
<authentication:userInformation>lastName</authentication:userInformation>
<authentication:userInformation>birthday</authentication:userInformation>
<authentication:postbackUrl>http://localhost/</authentication:postbackUrl>
<authentication:customData/>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
    <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
    <InclusiveNamespaces xmlns="http://www.w3.org/2001/10/xml-exc-c14n#" PrefixList="authentication"/>
</CanonicalizationMethod>
<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
<Reference URI="#uniqueNodeId">
    <Transforms>
        <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
        <Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
        <InclusiveNamespaces xmlns="http://www.w3.org/2001/10/xml-exc-c14n#" PrefixList="authentication"/>
    </Transform>
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<DigestValue>Qo6o54pUoVFMTLGgDIuHdF2K+54=</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>OVLdU2Pi54KHYqqtzlqHl6eNL8O3Zx7DguvnLY9HO/HkpUHZGiMgp5Lg/k60LWyynN7XK3bYcgZv
mTnXDduSVikYOhGp91Eh4xaWuolnn8e2o5kdAZK5PqiRbJ7eTGoyc7vU1Vv2WT81SH38sSAR4/h2
bNPc56+oVTbHAGyD3L++YZHWDrdaMZdJWvre/HF5grzBfma0qMWTRm5PdHIKDCuzrkvUWI6A4Mv3
FXAUQZPTLEaXrSP3yzQwV/4rtCzPGxJ0vdvHzm6mn4Q+IQUutqtxpT2Fxl7E7FnoGMj45OReW4p/
GZnpo4Lx2Jt75B/0j5BRbpHfdtbo+NkX3FZaRA==</SignatureValue>
<KeyInfo>
    <KeyValue>
        <RSAKeyValue>
            <Modulus>i+rh6NJ7Z6Q8XiMSVK/Z8DYXIyk5j7N9GUX8AOSKONabse4us7/ogR0x7OOf0FsrdxAhQls59Wn1
            vDxujSVOu3v1JhML/v/WK8glcxM433oEEpb0C56XRHlt27Qkbsn6v3njC1z0NGyDFdAtg5PaMx7Y
            mjyWR6ezMKj9wR5cK4CRZ7idm2PwzQaLUDFm7wUFXudZNkQ6pb60OvDw4ey1t68EVCPtq4nGdHG+
            3jlSDTTJc/03qk50pa6Nb/t5+EWsE3jFt/uhHim1rC2pMf5UrT26FL6/DjA0PxQFecc76zeuv3xb
            GSP7B7ubpG8fyatGb4oLB4eU0ceCJvqljGMP0w==</Modulus>
            <Exponent>AQAB</Exponent>
        </RSAKeyValue>
    </KeyValue>
</KeyInfo>
</Signature>
</authentication:authenticationRequest>

Atkreipkit dėmesį, kad užklausoje neturi būti XML header’io (<?xml version="1.0"?> ir pan.)
Antro etapo klasėje AuthDataRequestGenerator.java nieko ypatingo, mus domina tik dataRequest.setTicket(TICKET); kur nusistato unikalus ID (vadinamas “bilietu” (ticket)) kuris buvo gautas iš serviso pirmu etapu.

Trečioji ir paskutinė klasė yra BaseAuthRequestGenerator.java kurią extend’ina kitos dvi. Čia vykdomas kodas tiek pat svarbus, tad atsidarius matome:
public static final String SIGNED_NODE_ID = "uniqueNodeId"; nusistato Node ID, jis įtakos neturi, tai galima palikti kaip yra. Čia tam kad patys atpažintumėte gauto XML’o node’ą.

private static final char[] PASSWORD = "testtest".toCharArray(); akivaizdu kad nustato slaptažodį keystore’o.

Ir galiausiai

keyStore.load(FileUtils.openInputStream(FileUtils.toFile(AuthGenerator.class.getResource("/testKeystore.jks"))), PASSWORD);

yra kur naudojamas jūsų Keystore’as.

Tiek iš to Java projekto. Mūsų mažam projektui labai tiko ir Jar’as kviečiamas generuoti parašui, tai mes ir pasinaudojome kas jau buvo duota, nereikėjo perrašinėti kad būtų native PHP. Jeigu pas jus projekto apsukos labai didelės, žinoma kviesti kas kartą Jar’ą būtų didelis resursų ir laiko švaistymas, tačiau gal būt PHP-to-Java bridge’ą eitų panaudoti šiam reikalui.
Beje, tam kad veiktų šis projektas jis turi būti leidžiamas su Java7u21 (update 21) arba žemesniu, kitais atvejais tiesiog gausite URIReferenceException’ą:
javax.xml.crypto.dsig.XMLSignatureException: javax.xml.crypto.URIReferenceException: com.sun.org.apache.xml.internal.security.utils.resolver.ResourceResolverException: Cannot resolve element with ID uniqueNodeId
Parsisiųsti reikiamą JDK versiją galite iš Oracle svetainės, o kad išvengti tos kvailos registracijos, naudokite svetainę bugmenot.com

Dabar pakalbėkime apie “Java Keystore’ą”. Plačiau oficialioje dokumentacijoje.
Norint naudotis paslauga jums reikės susigeneruoti savo serverio sertifikatą ir privatų raktą, bei naudoti SSL. Pasirašant sutartį jūsų šito reikalaus vis tiek… tuos du dalykėlius reikia sudėti į Keystore’ą, tačiau tai nėra taip paprasta, nes Java keytool’as tokios funkcijos nepalaiko, kaip bebūtų gaila. Aš praleidau daug laiko kol išsiaiškinau kaip tai padaryti, kol galiausiai radau pora įrankių kurie padeda tai padaryti.
Pora svetainių kurios aprašė šį metodą yra čia ir čia. Šiaip testuojant jau yra duotas jums testinis keystore’as, kuris yra sudarytas iš testavimui skirto sertifikato ir privataus rakto, kuriuos galima rasti čia.
Testinio keystore’o alias’as yra “key”, o slaptažodis “testtest”.

Dabar tarkime kad norim pasidaryti savo Keystore’ą. Pirma reik įsirašyti OpenSSL ir perdaryti *.pem failus į *.der failus, tai padaroma su dviem komandom:
openssl pkcs8 -topk8 -nocrypt -in key.pem -inform PEM -out key.der -outform DER
openssl x509 -in cert.pem -inform PEM -out cert.der -outform DER
konvertavus jau galima ir dėti į keystore’ą, įrankius kuriuos reik naudoti galima gauti iš čia. Aš pats naudojau ImportKey.java, kitas irgi veikia taip pat gerai. Kad tas Java kodas pasileistų jį reikės sukompiliuoti, šią dalį paliksiu jums išsiaiškinti ;)
Atkreipkit dėmesį kad slaptažodis turi būti TIK iš mažųjų raidžių ir greičiausiai be skaičių. Atsitiktinumo keliu supratau kad keystore’as nors ir tinkamas, jo Java projektas kažkodėl negali tinkamai atidaryti kai slaptažodyje yra didžiųjų raidžiu ir/arba skaičių, tai kainavo man daug kantrybės kol supratau…

Sugeneravus keystore’ą galima jį redaguoti toliau jei to reikia, gal norite pakeisti slaptažodį ar alias’ą… tam galima naudoti įrankį Portacle, kuris yra tikrai geras dalykas tokiems darbams.

Na ką gi, kad jau turime viską ko reikia, galima siuntinėti užklausas. Kol vyksta bandymai, siuntinėjama į nuorodas su -test prierašu, kaip kad:
Web servisas: https://www.epaslaugos.lt/portal-test/authenticationServices/auth
Autentifikacijos URL: https://www.epaslaugos.lt/portal-test/external/services/authentication/v2/
Normaliai siuntinėjama į tas pačias nuorodas tik be -test.
Beje jei bandote naudotis bankais testavimo metu, tai galima daryti tik su SEB ir Nordea, kiti tiesiog negražins atgal į postBackURL().

Atrodo kaip ir viskas, einam prie pabaigos. Galima visą teoriją perkelti į paprastą PHP kodą. Tam parašiau atskirus failus, generateSignatureJava.php kuri sugeneruoja ir gražina parašą, bei vaiisis_test.php kuris yra visa logika. Juos abu galima parsisiųsti iš čia.
PHP kodas parašo generavimui naudoja modifikuotą Java projektą kurį pakeičiau savo reikmėms kaip vėliau pamatysite. Jeigu reikės galėsiu įkelt pavyzdį :)

Kas jų viduje?
generateSignatureJava.php atrodo taip:

<?php

/**
    Funkcija skirta sugeneruoti užklausos XML kuris bus siunčiamas
    pradinėje stadijoje inicijuojant autentifikavimą.

    @return:
        String signedReqXML;
*/
function generateReqXML() {
    exec('"C:\Program Files\Java\jdk1.7.0_21\bin\java.exe" -jar vaiisis.jar auth', $out);
    // header('Content-Type:text/xml');

    $signedXML = '';
    foreach ($out as $line) {
        $signedXML = $signedXML.$line;
    }

    return $signedXML;
}

/**
    Funkcija skirta sugeneruoti užklausos XML skirtą duomenų pasiėmimui
    antrajai stadijai, kai vartotojas autentifikuojasi ir būna nukreipiamas
    į kitur.

    @params:
        String ticket;
    @return:
        String signedReqDataXML;
*/
function generateDataReqXML($ticket) {
    exec('"C:\Program Files\Java\jdk1.7.0_21\bin\java.exe" -jar vaiisis.jar authdata '.$ticket, $out);
    // header('Content-Type:text/xml');

    $signedXML = '';
    foreach ($out as $line) {
        $signedXML = $signedXML.$line;
    }

    // echo $signedXML; die();

    return $signedXML;
}

?>

generateReqXML() funkcija generuoja parašą pirmam etapui kviesdama mano Jar’ą su Java7u21 per exec(). Žinoma exec() nėra geras dalykas saugumo atžvilgiu, bet ką jau… :P
generateDataReqXML($ticket) funkcija atlieka tą patį, tik kad nurodomas yra bilietas. Čia antram etapui, generuoja parašą gauti duomenims iš serviso.

Pagrindinis failas vaiisis_test.php:

<?php
//========= KONSTANTOS =========
    $WEB_SERVISAS = "https://www.epaslaugos.lt/portal-test/authenticationServices/auth";
    $AUTHENTICATION_URL = "https://www.epaslaugos.lt/portal-test/external/services/authentication/v2/";
    $SOAP_REQ = '<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:aut="http://www.epaslaugos.lt/services/authentication" xmlns:xd="http://www.w3.org/2000/09/xmldsig#">
    <soapenv:Header/>
    <soapenv:Body>
      %s
    </soapenv:Body>
    </soapenv:Envelope>';
    $TICKET_REGEX = "#<authentication:ticket>.+?</authentication:ticket>#";
//==============================
/**
  Funkcija skirta ištraukti bilietą iš atsakymo nusiuntus atitinkamą užklausą.
*/
function extractTicket($data) {
    global $TICKET_REGEX;

    preg_match($TICKET_REGEX, $data, $matches);
    $rawTicket = $matches[0];
    $rawTicket = str_replace("<authentication:ticket>", "", $rawTicket);
    $ticket = str_replace("</authentication:ticket>", "", $rawTicket);
    return $ticket;
}
//==============================
/**
  Funkcija skirta gražiai suformatuoti vardą, pavardę bei gimimo datą iš XML'o
  kuris gaunamas paskutiniu etapu.
*/
function formatName($rawXml) {
    $rtnStr = '';
    
    //sukuriam objektą ir užkraunam dokumentą paduotą per funkcijos argumentą
    $domDoc = new DOMDocument();
    $domDoc->loadXml($rawXml);

    $nodeSet = $domDoc->getElementsByTagName('userInformation'); // gaunam visus blokus pagal šitą tagname'ą - vardas ir pavardė
    $vardas = $nodeSet->item(0)->getElementsByTagName('stringValue')->item(0)->nodeValue;
    $pavarde = $nodeSet->item(1)->getElementsByTagName('stringValue')->item(0)->nodeValue;
    $gimimoData = $nodeSet->item(2)->getElementsByTagName('dateValue')->item(0)->nodeValue;
    $gimimoData = substr($gimimoData, 0, -1); // pašalinam Z raidę nuo galo

    $nodeSet = $domDoc->getElementsByTagName('authenticationAttribute'); // gaunam asmens kodo bloką
    $asmensKodas = $nodeSet->item(0)->getElementsByTagName('value')->item(0)->nodeValue;

    // gražiai suformatuojam išvedimą
    $rtnStr = 'Vardas: '.$vardas.'<br />Pavardė: '.$pavarde.'<br />Gimimo data: '.$gimimoData.'<br />Asmens kodas: '.$asmensKodas;

    return $rtnStr;
}
//==============================
/**
  Funkcija skirta pimam etapui, inicijuoti autentifikaciją.
  Gaunama $soap_req pilna SOAP'o užklausa, kurią siunčiame
  per POST'ą į $authentication_url iš kur gaunamas SOAP'o atsakymas
  su ticket'u. Iš čia ištraukiamas ticket'as ir siunčiamas į $web_servisas
  per GET'ą. Nusiuntus vartotojas būna nukreipiamas į VAIISIS sistemą.
*/
function authenticate($soap_req, $web_servisas, $authentication_url) {
  $contextData = array ('Connection' => 'Keep-Alive',
                        'Accept-Encoding' => 'gzip,deflate',
                        'Content-Type' => 'text/xml;charset=UTF-8',
                        'SOAPAction' => 'http://www.epaslaugos.lt/services-test/authenticationServiceProvider/initAuthentication',
                        'Content-Length' => strlen($soap_req),
                        'Host' => 'www.epaslaugos.lt',
                        'User-Agent' => 'Apache-HttpClient/4.1.1 (java 1.5)');

  $soap_do = curl_init();
  curl_setopt($soap_do, CURLOPT_URL, $web_servisas);
  curl_setopt($soap_do, CURLOPT_CONNECTTIMEOUT, 10);
  curl_setopt($soap_do, CURLOPT_TIMEOUT, 10);
  curl_setopt($soap_do, CURLOPT_RETURNTRANSFER, true);
  curl_setopt($soap_do, CURLOPT_SSL_VERIFYPEER, false);
  curl_setopt($soap_do, CURLOPT_SSL_VERIFYHOST, false);
  curl_setopt($soap_do, CURLOPT_POST, true);
  curl_setopt($soap_do, CURLOPT_POSTFIELDS, $soap_req);
  curl_setopt($soap_do, CURLOPT_HTTPHEADER, $contextData);
  $resp = curl_exec($soap_do);
  $ticket = extractTicket($resp);
  // echo 'Location: '.$authentication_url.'?ticket='.$ticket; die();
  header('Location: '.$authentication_url.'?ticket='.$ticket);
}
//==============================
/**
  Funkcija skirta antram etapui, gauti vartotojo po autentifikacijos duomenis.
  Gaunama $soap_req pilna SOAP'o užklausa, kurią siunčiame
  per POST'ą į $authentication_url iš kur gaunamas SOAP'o atsakymas
  su vartotojo duomenimis ir vykdoma skriptas toliau.
*/
function getUserData($soap_req, $web_servisas, $authentication_url) {
  $contextData =  array ('Connection' => 'Keep-Alive',
                        'Accept-Encoding' => 'gzip,deflate',
                        'Content-Type' => 'text/xml;charset=UTF-8',
                        'SOAPAction' => 'https://www.epaslaugos.lt/services-test/authenticationServiceProvider/getAuthenticationData',
                        'Content-Length' => strlen($soap_req),
                        'Host' => 'www.epaslaugos.lt',
                        'User-Agent' => 'Apache-HttpClient/4.1.1 (java 1.5)');

  $soap_do = curl_init();
  curl_setopt($soap_do, CURLOPT_URL, $web_servisas);
  curl_setopt($soap_do, CURLOPT_CONNECTTIMEOUT, 10);
  curl_setopt($soap_do, CURLOPT_TIMEOUT, 10);
  curl_setopt($soap_do, CURLOPT_RETURNTRANSFER, true);
  curl_setopt($soap_do, CURLOPT_SSL_VERIFYPEER, false);
  curl_setopt($soap_do, CURLOPT_SSL_VERIFYHOST, false);
  curl_setopt($soap_do, CURLOPT_POST, true);
  curl_setopt($soap_do, CURLOPT_POSTFIELDS, $soap_req);
  curl_setopt($soap_do, CURLOPT_HTTPHEADER, $contextData);
  $resp = curl_exec($soap_do);
  header('Content-Type:text/xml');
  echo $resp;
}
//==============================

include('generateSignatureJava.php');
if (isset($_POST['ticket'])) {
  $reqDataXml = sprintf($SOAP_REQ, generateDataReqXML($_POST['ticket']));
  getUserData($reqDataXml, $WEB_SERVISAS, $AUTHENTICATION_URL);
} else {
  $reqXml = sprintf($SOAP_REQ, generateReqXML());
  authenticate($reqXml, $WEB_SERVISAS, $AUTHENTICATION_URL);
}

?>

Čia jau yra į ką žiūrėti.

$SOAP_REQ konstanta nustato tai į ką reikia sudėti gautą parašą kad būtų paruošta siuntimui, dedama tarp tag’o.

extractTicket($data) ištraukia bilietą su regex iš atsakymo XML’e.

formatName($rawXml) suformatuoja išvestį iš atsakymo. Testavimo sumetimais tiesiog gražiai parašo vardą, pavardę, gimimo datą ir asmens kodą. Beje gimimo data gaunama su Z (galbūt ir kitokia) raide ant galo kažkodėl, tad ją reikia pašalinti.

authenticate($soap_req, $web_servisas, $authentication_url) funkcija pirmam etapui, inicijuoja autentifikaciją. Šitoje funkcijoje užklausos header’yje turi būti nustatoma SOAPAction interfeisas. Kadangi siunčiama visais atvejais ten pat, tai SOAPAction nurodo kokį interfeisą naudoti serveriui, tai nurodoma yra:
'SOAPAction' => 'http://www.epaslaugos.lt/services-test/authenticationServiceProvider/initAuthentication'
Naudojant normaliai, -test dalis turi būti pašalinta, o daugiau tai standartinis siuntimas su CURL’u. Toje pačioje vietoje $ticket = extractTicket($resp); ištraukiamas bilietas iš atsakymo ir header('Location: '.$authentication_url.'?ticket='.$ticket); nuvaro vartotoją autentifikuotis.

getUserData($soap_req, $web_servisas, $authentication_url) tiesiog gauna visus duomenis kurių mums reikia ir juos atvaizduoja. Čia jau header’yje turi nusirodyti:
'SOAPAction' => 'https://www.epaslaugos.lt/services-test/authenticationServiceProvider/getAuthenticationData'

Ir paskutinė dalis (eilutės 116-122) atlieka logiką kokią funkciją kada vykdyti, kadangi aš postBackURL() esu nusistatęs tą patį puslapį, tai vartotoją gražina ten pat iš kur išėjo. Kad aptikti kada vartotojas ateina jau po autentifikacijos, galima tikrinti ar $_POST[‘ticket’] yra užstatytas – jei ne, tuomet atėjo pirmą kartą.

Tiek to viso kodo. Trumpiau kas apskritai vyksta tai tiesiog:

  1. Vartotojas ateina į mūsų puslapį
  2. Sugeneruojama ir siunčiama užklausa
  3. Serveriui patikrinus, jeigu viskas atitinka, gaunamas bilietas
  4. Bilietas siunčiamas per GET’ą į serverį kartu nukreipiant vartotoją
  5. Vartotojas atlieka procedūrą ir paspaudžia “Patvirtinti” valdžios vartuose
  6. Vartotojui patvirtinus jis nukreipiamas į postBackURL() kartu su bilietu per POST’ą, tuo pačiu kuris buvo gautas pirmą kartą
  7. Gautą bilietą įkišame į užklausos XML’ą ir siunčiame į serverį
  8. Jeigu galutinis bilieto patvirtinimas serveryje sutampa, gaunamas XML’o atsakymas kuriame yra visi reikalingi duomenys. Jeigu patvirtinti nepavyko (bilieto galiojimas baigėsi arba jis ne validus) tada gaunama atitinkamas klaidos pranešimas

Tai va tiek to panaudojimo. Nėra labai sudėtinga kai supranti kas, kada, kur ir kaip turi būti siunčiama. Jeigu stojasi kokie nors klausimai, rašykite į komentarus, bandysiu atsakyti :)

13 comments

  1. Arturas says:

    Sveiki,

    Is kur butu galima gauti jar testiniai versijai

    Aciu labaiii :)

  2. Kulverstukas says:

    Labas,

    JAR failą reikia susikompiliuot pačiam iš testinio projekto kurio nuoroda yra straipsnyje, tačiau dabar pamačiau, kad nuorodos nebegalioja. Reiks pakeist kurią dieną :P

  3. Arturas says:

    Labas vakaras,

    Aciu uz atsakyma. Galbut galetumete pasidalinti savo modifikuotu Java projekteliu testiniai versijai ? Nes man kazkodel kompiliuojant projekta atsiusta is Viisp svetaines su Eclipse stringa 62 eiluteje AuthGenerator.java faile :(, esu tik pradedantis dirbant su java ir Eclipse, galbut kazka darau netaip importuojant projekta, gal galite pasakyti kaip teisingai kompiuoti Jar faila is projekto.

    LABAI ACIU JUMS,

  4. Kulverstukas says:

    Ok, pakeičiau nuorodas į veikiančias.

    Būtų gerai pamatyti klaidą kurią meta bandant paleist projektą. Kadangi pakeitimai buvo padaryti 10 dienų po straipsnio publikavimo datos, tai gal kažkas pasikeitė. Mano atveju būtinai reikėjo naudoti JDK7u21 (rašiau straipsnyje).
    Jeigu parašytum klaidą, galbūt galėčiau padėt, kai turėsiu laiko pats pabandysiu pažėt kame reikalas su tuo nauju pavyzdžiu.

    Dėl JAR failo – galiu duoti, bet tai bus tik šiam kartui. Kai reikės tikrojo (ne testinio) visvien teks pačiam eksportuoti, kad būtų sudėtas ir keystore’as į JAR’ą kuriame bus tikrasis serverio sertifikatas. Geriau pasidaryt kad pačiam veiktų :)

  5. Arturas says:

    BUTU NUOSTABU gauti dabar laikina testini jar faila, galeciau kolkas judeti su PHP puse, bent jau matysiu kaip veikia php kodas su testinia aplinka, is to galbut viskas bus aiskiau. Po to as jums prodysiu vaizdziai visa seka kaip as kompiliuoju Jar.

    ACIU JUMS BEGALO UZ PAGALBA…

  6. Kulverstukas says:

    Šiuo metu neturiu testinio ir neturiu laiko pagamint. Visvien rekomenduoju pirmiausia suprast kodėl neveikia, kad vėliau galėtum koreguot Java kodą pagal PHP savo tikslams.

  7. Art says:

    Sveiki,

    Viskas pavyko darant “rankiniu” budu, java projektas generuoja xml’us reikiamus ir sekmingai galima issiusti uzklausa ir gauti tikcet’a, po to rankiniu budu iterpti tiket’a i java projketa (AuthDataRequestGenerator.java) ir generuoti xml bei siusti sugeneruota xml su tiket’u, po kurio gaunami sekmingai vardas, pavarde, asmens kodas. Taciau reitku tai visa automaatizuoti. Glabut jus dar turite savo modifikuota java projekta kuris suprastu gautus parametrus kaip “auth”, nes dabartinis duotas java projetas tokiu argumentu nesupranta. Is to as jau pasidarysiu jar faila.

    Aciu labai,

  8. Kulverstukas says:

    Šaunu kad galiausiai pavyko :) jei gali parašyk kaip ištaisei tą klaidą.
    Kas dėl projekto – manoji yra pasenusi, aš neatnaujinau kai žmonės iš IVPK išleido naują versija. Tačiau modifikuoti tą projektą tikrai nėra sudėtinga net ir pradedančiajam – tiesiog reikia padaryti kad main() priimtų String argumentą ar jų masyvą ir atitinkamai iškviestų reikiamą metodą.

    Mano main.java atrodo taip:

    public static void main(String[] args) {
        for (String s: args) {
            if (s.equals("auth")) {
                if (args.length == 2) {
                    try {
                        String rtn = new AuthGenerator().generateRequest(args[1]).replace("<?xml version=\"1.0\" encoding=\"UTF-8\"?>", "");
                        System.out.println(rtn);
                        break;
                    } catch (Exception e) {
                        System.out.println("Exception generating request!!\n");
                        e.printStackTrace();
                    }
                }
            } else if (s.equals("authdata")) {
                try {
                    for (String a: args) {
                        if (a.contains("-")) {
                            String rtn = new AuthDataRequestGenerator().generateRequest(a).replace("<?xml version=\"1.0\" encoding=\"UTF-8\"?>", "");
                            System.out.println(rtn);
                        }
                    }
                } catch (Exception e) {
                    System.out.println("Exception generating authentication request!!\n");
                    e.printStackTrace();
                }
            }
        }
    }
    

    Atitinkamai reikia pakeisti ir naudojamus metodus.

  9. Daniil says:

    We fix bug running JAR from PHP on Unix

    In BaseAuthRequestGenerator.java

    Change this string:
    //keyStore.load(FileUtils.openInputStream(FileUtils.toFile(AuthGenerator.class.getResource(“/testKeystore.jks”))), PASSWORD);

    String fname = “testKeystore.jks”;
    keyStore.load(FileUtils.openInputStream(FileUtils.getFile(fname)),PASSWORD);

  10. Paulius says:

    Sveiki,

    Niekada neteko dirbti su Java

    Java projekta subuildinau, gavau jar faila, ji paleidinejant gaunu klaida:
    Exception in thread “main” java.lang.NoClassDefFoundError: org/apache/commons/io
    /FileUtils
    at lt.epaslaugos.test.auth.BaseAuthRequestGenerator.(BaseAuthReque
    stGenerator.java:55)
    at lt.epaslaugos.test.auth.AuthGenerator.(AuthGenerator.java:17)
    at lt.epaslaugos.test.auth.AuthGenerator.main(AuthGenerator.java:63)
    Caused by: java.lang.ClassNotFoundException: org.apache.commons.io.FileUtils
    at java.net.URLClassLoader$1.run(Unknown Source)
    at java.net.URLClassLoader$1.run(Unknown Source)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(Unknown Source)
    at java.lang.ClassLoader.loadClass(Unknown Source)
    at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
    at java.lang.ClassLoader.loadClass(Unknown Source)

    Gal Jums tekos susidurti su ja? kaip ja spresti?

  11. Kulverstukas says:

    Susidurti neteko, bet JAR’as neranda FileUtils klasės esančios org.apache.commons.io pakete. Galbūt netinkamas JDK įrašytas?

  12. Studentas says:

    Neissamu. Aciu.

  13. Kulverstukas says:

    Kuri dalis buvo neišsami?

Leave a Reply

Your email address will not be published. Required fields are marked *