Posts tagged webservices

Komunikace se zabezpecenymi webovymi sluzbami

0

Dnes jsem se trapil s tvorbou klienta, ktery se mel pripojit na vzdalenou webovou sluzbu s klientskym certifikatem a predevsim – autentikovat se pomoci jmena a hesla. Nakonec se chyba ukazala byt trochu jinde, nez jsem ji cekal, ale kdyz uz jsem s tim stravil cas, tak to tu trochu popisu, kdyby nahodou nekdo resil podobny problem.

Nejprve ona chyba – mam zdrojak pro klienta vygenerovany pomoci wsimportu a protoze jsem se snazil o univerzalni reseni, adresa koncoveho bodu je v .properties souboru. Lokalne mam ovsem ulozeny wsdl, na ktery se odkazuju pomoci anotace @WebServiceClient ve tride klienta:

@WebServiceClient(
    name = "myService",
    targetNamespace = "http://www.shmoula.cz/my/service",
    wsdlLocation = "META-INF/wsdl/myService.wsdl")
public class MyServiceClient extends Service{
 

No a tohle byl muj problem – toto lokalni wsdl se neshodovalo s deskriptorem na druhe strane (odstranil jsem policy cast), takze mi server porad odmital pozadavky:

javax.xml.ws.WebServiceException: Failed to access the WSDL at: https://shmoula.cz:8080/myService?wsdl. It failed with:
Connection refused.

Stacilo zmenit wsdlLocation na adresu deskriptoru na serveru a vse uz bezi v poradku. Hadam, ze kdyby lokalni wsdl presne odpovidalo tomu na serveru, fungovalo by to taky. Ted ale to podstatnejsi – jak autentikovat uzivatele?

Nejprve mejme vygenerovany a naimportovany certifikat v trusted certs, napr. pomoci keytool:

keytool -import -alias shmoula.cz -file certifikat.shmoula.cz.der -keystore <J2EE_HOME>/domains/domain1/config/cacerts.jks

Potom budeme potrebovat vlastni implementaci tridy Authenticator, kterou zajistime, aby byly autentikacni informace predavany pouze pri zabezpecenem spojeni:

package cz.shmoula.klient;

import java.net.Authenticator;
import java.net.PasswordAuthentication;

public class BasicHTTPAuthenticator extends Authenticator {
  private String userName;
  private String password;

  public BasicHTTPAuthenticator(String userName, String password){
    this.userName = userName;
    this.password = password;
  }

  @Override
  protected PasswordAuthentication getPasswordAuthentication(){
    if (this.getRequestingProtocol().equalsIgnoreCase("https"))
        return new PasswordAuthentication(userName, password.toCharArray());
    else
        return null;
  }

  public String getUserName() {
    return userName;
  }

  public void setUserName(String userName) {
    this.userName = userName;
  }

  public String getPassword() {
    return password;
  }

  public void setPassword(String password) {
    this.password = password;
  }
}

V aplikaci je potom nutne nejprve nastavit tento autentikator a teprve pote vytvorit instanci klienta:

URL baseUrl = cz.shmoula.klient.MyServiceClient.class.getResource(".");
URL url = new URL(baseUrl, "https://shmoula.cz:8080/myService?wsdl");
QName qName = new QName("http://www.shmoula.cz/myservice", "myService");

Authenticator.setDefault(new BasicHTTPAuthenticator("jmeno", "heslo"));
MyServiceIface service = new MyServiceClient(url, qName).getMyServicePort();

Pote je nutne jeste poupravit kontext pozadavku pomoci BindingProvidera a je mozne se pustit do volani vzdalenych metod, vse zabezpeceno:

BindingProvider bp = (BindingProvider) service;
bp.getRequestContext().put(BindingProvider.USERNAME_PROPERTY, "jmeno");
bp.getRequestContext().put(BindingProvider.PASSWORD_PROPERTY, "heslo");
MyServiceResponse resp = service.makeCall(foo, bar);

Abych pravdu rekl, prestava se mi to libit cim dal tim vic, asi se vratim k JSONu. Podstatne je, ze to mam z krku.

Validace vstupu webových služeb

0

Potřeboval jsem validovat vstup webové služby na základě xsd šablony – a to jak typy, tak patterny a potřeboval jsem to pokud možno obecně a jednoduše. Postupně jsem se dobral řešení, které se pokusím níže popsat.

Měl jsem zadaný wsdl dokument, k němu definici typů v xsd a příkaz použít jax-ws. Takže jsem provedl wsimport a začal zkoušet. Na komplexní typy byl pomocí jaxb vytvořen binding, takže mi vznikla trochu poschoďovější struktura, která ovšem nebrala v úvahu definované simple typy a prostě je převedla na podobné typy jazyka Java. Pokud jsem tento projekt spustil na serveru, patterny těchto typů prostě zmizely a vygenerované xsd bylo úplně jiné, wsdl se jakž-takž podobalo. Stačilo ovšem přidat parametr k anotaci WebService, který specifikoval cestu k originálnímu wsdl.

@WebService(
    name = &quot;myServiceSei&quot;,
    targetNamespace = &quot;http://www.shmoula.cz/schema/myservice&quot;,
    wsdlLocation = &quot;META-INF/wsdl/myService.wsdl&quot;
)

V tomto okamžiku jsem měl ovšem problém s bindingem, který jsem původně přiřazoval nějaké chybičce ve jmenných prostorech, a ten způsoboval, že mi služba předávala null hodnoty. Nakonec se ukázalo, že v zadaném wsdl se vyskytuje zpráva z názvem doSomeStuffRequest, který se kryl s elementem ve vlastním těle této zprávy (navíc jax-ws přidává postfix Request automaticky), takže stačilo tento název změnit a vše bylo v pořádku a já byl o krok blíže k validování.

První, co mě napadlo, bylo použití anotace SchemaValidation, tak jsem ji použil. Služba se ovšem chovala, jako by tam tato anotace vůbec nebyla a prošlo naprosto všechno. Tudíž nastoupilo řešení číslo dvě – použítí Marshalleru, který by měl při nesrovnalostech skončit s výjimkou. Končil, ovšem s výjimkou jinou, než jsem čekal.

unable to marshal type "cz.foo.bar.DoSomeStuffRequest" as an element because it is missing an @XmlRootElement annotation

Důvody jejího vzniku pěkně rozebral Kohsuke Kawaguchi ve svém blogpostu a protože jsem nechtěl do všech typů cpát XmlRootElement, řídil jsem se jí a dobral se funkčnosti – opravdu mi to validuje! Ještě jsem si s řešením trochu pohrál a především využil vygenerované ObjectFactory, abych nemusel vytvářet vlastní JAXBElement. Výsledek vypadá (zhruba) následovně a funguje na výbornou.

@WebMethod
public void doSomeStuff(DoSomeStuffRequest serviceParameters) {

    ObjectFactory of = new ObjectFactory();
    JAXBElement&lt;DoSomeStuffRequest&gt; element = of.createParcelImportRequest(serviceParameters);
    SchemaFactory sf = SchemaFactory.newInstance(javax.xml.XMLConstants.W3C_XML_SCHEMA_NS_URI);

    try {
        URLClassLoader cl = (URLClassLoader) getClass().getClassLoader();
        URL url = cl.findResource(&quot;META-INF/wsdl/myService.xsd&quot;);

        Schema schema = sf.newSchema(url);

        JAXBContext context = JAXBContext.newInstance(DoSomeStuffRequest.class);

        Marshaller marshaller = context.createMarshaller();
        marshaller.setSchema(schema);

        marshaller.marshal(element, new DefaultHandler());

    } catch (SAXException e) {
        e.printStackTrace();
    } catch (JAXBException e) {
        e.printStackTrace();
    }
}

PS. Nic to ovšem nemění na tom, že SOAP není mrtvý, ale nemrtvý (a vůbec že je to fuj)!

Go to Top