Pobranie username z wiadomości SOAP

0

Witam,

Tworze zabezpieczony Web Service przy użyciu XWS Security, z autoryzacją dla wielu użytkowników.
Wszystko działa, chciałbym natomiast dowiedzieć się, jak mogę odczytać nazwe użytkownika w klasie WebService.

Przykładowy SOAP wygląda tak:

INFO: ==== Sending Message Start ====
<?xml version="1.0" encoding="UTF-8"?>
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
<S:Header>
<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" S:mustUnderstand="1">
<wsse:UsernameToken xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="XWSSGID-1198013990123493230040">
<wsse:Username>Username</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">****</wsse:Password>
<wsse:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">/2qvu+vlFRV2HAhAM8rIFJpF</wsse:Nonce>
<wsu:Created>2007-12-18T21:39:50.342Z</wsu:Created>
</wsse:UsernameToken>
</wsse:Security>
</S:Header>
<S:Body>
<ns2:sayHello xmlns:ns2="http://ws/">
<arg0>Hello</arg0>
</ns2:sayHello>
</S:Body>
</S:Envelope>
==== Sending Message End  ====

Chodziło by mi o wartość wsse:Username

0

Wypunktowane tworzeni klienta ws w javie
http://forums.java.net/jive/thread.jspa?messageID=248018

Na podstawie wsdla z którego pochodzi SOAP wygeneruj klienta. W serwerach aplikacji zgodnych z JEE5 powinno to wyglądać mniej więcej tak:

> mkdir src
> mkdir bin
> wsimport -d ./bin -keep -s ./src plik.wsdl

Nie zapominaj że powinieneś też skonfigurować repozytorium kluczy.

0

Mam załóżmy prosty serwer, autoryzujący w oparciu o jedną nazwe użytkownika i hasło:

package ws.security;

import java.util.Set;
import javax.xml.namespace.QName;
import javax.xml.soap.SOAPMessage;
import javax.xml.ws.WebServiceException;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;
import java.io.FileInputStream;
import java.io.File;
import com.sun.xml.wss.ProcessingContext;
import com.sun.xml.wss.SubjectAccessor;
import com.sun.xml.wss.XWSSProcessorFactory;
import com.sun.xml.wss.XWSSProcessor;
import com.sun.xml.wss.XWSSecurityException;
import java.util.HashSet;

public class SecurityHandler implements SOAPHandler<SOAPMessageContext> {
	private final static String CFG_PATH = "../webapps/xhell/WEB-INF/server/cfg";
	private final static String CONFIG = "user-pass-authenticate-server.xml";
	private XWSSProcessor processor = null;

	public SecurityHandler() {
		FileInputStream serverConfig = null;

		try {
			serverConfig = new FileInputStream(new File(CFG_PATH+"/"+CONFIG));

			XWSSProcessorFactory factory = XWSSProcessorFactory.newInstance();
			processor = factory.createProcessorForSecurityConfiguration(serverConfig, new ServerSecurityEnvironmentHandler());

			serverConfig.close();
		} catch (Exception e) {
			throw new RuntimeException("[SERWER]: Błąd! Nie można utworzyć SecurityHandler:\n"+e.getMessage());
		}
	}

	public Set<QName> getHeaders(){
		QName securityHeader = new QName("http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", "Security", "");
		HashSet<QName> headers = new HashSet<QName>();
		headers.add(securityHeader);
		return headers;
	}

	public boolean handleFault(SOAPMessageContext messageContext) {
		return true;
	}

	public boolean handleMessage(SOAPMessageContext messageContext) {
		secureServer(messageContext);
		return true;
	}

	public void close(MessageContext messageContext) {
	}

	private void secureServer(SOAPMessageContext messageContext) {
		Boolean outMessageIndicator;
		outMessageIndicator= (Boolean)messageContext.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);

		SOAPMessage message = messageContext.getMessage();

		if (outMessageIndicator.booleanValue())
			return;

		try{
			ProcessingContext context =  processor.createProcessingContext(message);
			context.getExtraneousProperties().putAll(messageContext);
			context.setSOAPMessage(message);

			SOAPMessage verifiedMsg= null;
			verifiedMsg= processor.verifyInboundMessage(context);

			messageContext.setMessage(verifiedMsg);
		} catch (Exception ex) {
			throw new WebServiceException("[SERWER]: Błąd autoryzacji!");
		}

	}
}
package ws.security;

import java.io.IOException;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import com.sun.xml.wss.impl.callback.PasswordCallback;
import com.sun.xml.wss.impl.callback.PasswordValidationCallback;
import com.sun.xml.wss.impl.callback.UsernameCallback;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class ServerSecurityEnvironmentHandler implements CallbackHandler {
	private final static String USERNAME = "Username";
	private final static String PASSWORD = "PassworD";

	public void handle(Callback[] call) throws IOException, UnsupportedCallbackException {
		for(int i=0; i<call.length; i++) {

			if(call[i] instanceof PasswordValidationCallback) { //passwordValidation
				PasswordValidationCallback passCallback = (PasswordValidationCallback)call[i];
				if(passCallback.getRequest() instanceof PasswordValidationCallback.PlainTextPasswordRequest) {

					passCallback.setValidator(new PlainTextPasswordValidator());

				} else if(passCallback.getRequest() instanceof PasswordValidationCallback.DigestPasswordRequest) {

					PasswordValidationCallback.DigestPasswordRequest request;
					request = (PasswordValidationCallback.DigestPasswordRequest)passCallback.getRequest();

					String username = request.getUsername();
					if(USERNAME.equals(username)) {
						request.setPassword(PASSWORD);
						passCallback.setValidator(new PasswordValidationCallback.DigestPasswordValidator());
					}
				}
			} else {
				throw new UnsupportedCallbackException(call[i],"[SERWER]: Błąd! Nieobługiwane wywołanie zwrotne");
			}
		}
	}

	private class PlainTextPasswordValidator implements PasswordValidationCallback.PasswordValidator {
		public boolean validate(PasswordValidationCallback.Request request)
			throws PasswordValidationCallback.PasswordValidationException {
			PasswordValidationCallback.PlainTextPasswordRequest plain;
			plain = (PasswordValidationCallback.PlainTextPasswordRequest)request;

			if(USERNAME.equals(plain.getUsername()) && PASSWORD.equals(plain.getPassword()))
				return true;
			return false;
		}
	}
}
package ws;

import javax.jws.WebService;
import javax.jws.WebMethod;
import javax.jws.HandlerChain;

import javax.annotation.Resource;
import javax.xml.ws.WebServiceContext;
import javax.xml.ws.handler.MessageContext;

@WebService
@HandlerChain(file="../server/cfg/handler.xml")
public class XHello {
	@Resource
		private WebServiceContext wsContext;

	@WebMethod
	public String sayHello(String name) {
		return "Hello: "+name;
	}
}

Teraz chciałbym w metodzie sayHello, odczytać nazwę użytkownika, która była sprawdzana w ServerSecurityEnvironmentHandler.

Serwer działa pod Tomcatem

0

Zaraz... bo ja teraz nie do końca kapewu. Chodzi ci o to by w sayHello móc skorzystać z USERNAME = "Username";?

0

To akurat jest tylko przykład ze stałą nazwą użytkownika, ale mniej więcej tak.

chodzi mi o :

PasswordValidationCallback.DigestPasswordRequest request;
					request = (PasswordValidationCallback.DigestPasswordRequest)passCallback.getRequest();

String username = request.getUsername();

^ tą wartość username,
Na początku zamieściłem wiadomość, która jest wysyłana od klienta, zawiera ona tą wartość w tagu wsse:Username

0

Czyli klient musiał by to puścić przez maszynkę odszyfrowującą. Ważne jest by miał klucz publiczny nadawcy. Idę spać... jutro czeka mnei podobny problem chyba...

0

A może tak:
Jak na podstawie WebServiceContext, lub w jakiś inny sposób, wydobyć treść wiadomości SOAP (zwykły String XML) w klasie @WebService serwera ?

0

Chyba nijak, ponieważ klasa WebService nie ma dostępu do wiadomości SOAP, a wynika to z faktu że jest wykonywana przed stworzeniem komunikatu. Można próbować w ten sposób:

@WebService
public class FooService {
  @Resource
  WebServiceContext context;
  @WebMethod
  public void sayHelloTo(String name) {
    HeaderList hl = context.getMessageContext().get(JAXWSProperties.INBOUND_HEADER_LIST_PROPERTY);
    Header h = hl.get(MYHEADER);
  }
  private static final QName MYHEADER = new QName("myNsUri","myHeader");
}

ale to dotyczy wiadpmości przychodzącej, a nie wychodzącej.

0

Rozwiązałem problem, nazwe użytkownika mogę pobrać w SecurityHandler, ponieważ obsługuje on SOAPMessageContext:

...
public boolean handleMessage(SOAPMessageContext messageContext) {
	HttpServletRequest req = (HttpServletRequest)messageContext.get(MessageContext.SERVLET_REQUEST);
	HttpSession session = req.getSession();

	String username = getUsername(messageContext);

	secureServer(messageContext);

	session.putValue("USERNAME", username);

	return true;
}

private String getUsername(SOAPMessageContext messageContext) {
	try {
		NodeList nodeList = messageContext.getMessage().getSOAPHeader().getElementsByTagName("wsse:Username");
		if(nodeList.getLength() <= 0) return "";

		Element nodeElement = (Element)nodeList.item(0);
		NodeList value = nodeElement.getChildNodes();
		String username = String.valueOf( value.item(0).getNodeValue() );

		return username;
	}catch(Exception e) {
		return "";
	}
}
...

Wrzucam nazwe do sesji i po poprawnej autoryzacji mogę ją pobrać w klasie WebService:

@WebService
@HandlerChain(file="../server/cfg/handler.xml")
public class XHello {
	@Resource
		private WebServiceContext wsContext;

	@WebMethod
	public String sayHello(String name) {
		HttpServletRequest req = (HttpServletRequest)wsContext.getMessageContext().get(MessageContext.SERVLET_REQUEST);
		HttpSession session = req.getSession();

		return "Hello "+name+"\nTwój uid to: "+session.getAttribute("USERNAME");

	}
}

0

Niestety problem okazał się nieco bardziej złożony, gdyż przyjęte przeze mnie rozwiązanie nie działa dla zaszyfrowanych wiadomości...

W akcie desperacji zrobiłem tak:
Do CallBackHandlera (ServerSecurityEnvironmentHandler), wprowadziłem zmienną

private HttpSession session

oraz metode

protected void setSession(HttpSession session) {
	this.session = session;
}

Przed odpaleniem metody secureServer (wewnątrz klasy SecurityHandler), przypisuje sesje stworzoną na podstawie SOAPMessageContext do CallBackHandlera.

Teraz w momencie wystąpienia PasswordValidationCallback, pobieram username i przypisuje go do sesji.

if(session != null) {
	session.setAttribute("USERNAME", username);
	session = null;
}

Mam zatem pytanie.
Czy to rozwiązanie ma sens, tj. czy nie będzie konfliktów gdy będzie łączyć się naraz wielu użytkowników?

1 użytkowników online, w tym zalogowanych: 0, gości: 1