Handlers are like message interceptors for inbound (incoming) and outbound (outgoing) messages to perform extra processing on them like auditing, authentication etc. JAX-WS provides two types of handlers
1. Logical Handlers which cannot change\access any protocol specific parts like SOAP headers. They can act only on payload of messages.
2. SOAP handlers can access complete SOAP messages and change or access protocol specific details.
Authenticating through SOAP handlers is common technique used by setting "username" and "password" in SOAP headers. SOAP handler will intercept incoming request message, retrieve credentials from message and can authenticate.
We will add SOAP Handlers to GlobalWeatherService which we created in previous tutorial. Once done, we will modify client to set credential details in request message.
Let's create handler-chain.xml file at GlobalWeatherService\src\main\java\handler-chain.xml
1. Logical Handlers which cannot change\access any protocol specific parts like SOAP headers. They can act only on payload of messages.
2. SOAP handlers can access complete SOAP messages and change or access protocol specific details.
Authenticating through SOAP handlers is common technique used by setting "username" and "password" in SOAP headers. SOAP handler will intercept incoming request message, retrieve credentials from message and can authenticate.
We will add SOAP Handlers to GlobalWeatherService which we created in previous tutorial. Once done, we will modify client to set credential details in request message.
Let's create handler-chain.xml file at GlobalWeatherService\src\main\java\handler-chain.xml
1 2 3 4 5 6 7 8 9 10 | <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <javaee:handler-chains xmlns:javaee="http://java.sun.com/xml/ns/javaee" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <javaee:handler-chain> <javaee:handler> <javaee:handler-class>in.blogspot.ashish4java.GlobalWeatherService.handler.AuthHandler </javaee:handler-class> </javaee:handler> </javaee:handler-chain> </javaee:handler-chains> |
Create SOAP Handler class AuthHandler
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | package in.blogspot.ashish4java.GlobalWeatherService.handler; import java.io.IOException; import java.util.List; import java.util.Map; import java.util.Set; import javax.servlet.http.HttpServletResponse; import javax.xml.namespace.QName; import javax.xml.ws.handler.MessageContext; import javax.xml.ws.handler.soap.SOAPHandler; import javax.xml.ws.handler.soap.SOAPMessageContext; public class AuthHandler implements SOAPHandler<SOAPMessageContext> { @Override public boolean handleMessage(SOAPMessageContext context) { String userName = ""; String password = ""; Boolean direction = (Boolean) context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY); if (!direction) { Map<String, List<String>> reqheaders = (Map<String, List<String>>) context .get(MessageContext.HTTP_REQUEST_HEADERS); List<String> uName = reqheaders.get("UserName"); List<String> pWord = reqheaders.get("Password"); if (uName != null && !uName.isEmpty()) { userName = uName.get(0); } if (pWord != null && !pWord.isEmpty()) { password = pWord.get(0); } if (userName.equals("Ashish") && password.equals("Password123")) { return true; } else { HttpServletResponse response = (HttpServletResponse) context.get(MessageContext.SERVLET_RESPONSE); try { response.sendError(HttpServletResponse.SC_UNAUTHORIZED); } catch (IOException e) { System.out.println("Exception in setting error in response :" +e.getMessage()); } return false; } } return false; } @Override public boolean handleFault(SOAPMessageContext context) { System.out.println("inside handleFault"); return true; } @Override public void close(MessageContext context) { System.out.println("inside close"); } @Override public Set<QName> getHeaders() { return null; } } |
Method handleMessage method does authentication work. Same method is called on inbound as well as outbound messages. MessageContext.MESSAGE_OUTBOUND_PROPERTY helps to decide whether message is inbound or outbound.
After that, handler retrieves username and password from SOAP headers and authenticate it. In real world, this authentication will be done using some RDBMS or directory services like LDAP. If authentication fails, handler sets the response code as unauthorised (401).
One last step remaining is to configure this handler in Service Implementation Class as done using annotation @HandlerChain.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | package in.blogspot.ashish4java.GlobalWeatherService; import javax.jws.HandlerChain; import javax.jws.WebMethod; import javax.jws.WebService; import in.blogspot.ashish4java.GlobalWeatherService.exception.CityNotFoundException; /** * @author admin * */ @WebService(endpointInterface = "in.blogspot.ashish4java.GlobalWeatherService.CityTemperature") @HandlerChain(file="../../../../handler-chain.xml") public class CityTemperatureImpl implements CityTemperature { @Override public final String getCityTemperature(final String cityName) throws CityNotFoundException { String temp = null; if ( cityName != null && "Sydney".equals(cityName) ) { temp = "15 Degree Celsius"; } else { throw new CityNotFoundException("City name is not recognised"); } return temp; } @Override @WebMethod(exclude = true) public final String getRainDetails(final String cityName) { return "This operation is not implemented yet and that is why excluded from this service"; } } |
Now, modify client to set username\password in request message headers
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | package in.blogspot.ashish4java.globalweatherservice.client; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.xml.ws.BindingProvider; import javax.xml.ws.handler.MessageContext; import in.blogspot.ashish4java.globalweatherservice.CityNotFoundException_Exception; import in.blogspot.ashish4java.globalweatherservice.CityTemperature; import in.blogspot.ashish4java.globalweatherservice.CityTemperatureImplService; public class MainClient { public static final String city = "Sydney"; public MainClient() { } public static void main(String[] args) { final CityTemperatureImplService cityTempService = new CityTemperatureImplService(); CityTemperature client = cityTempService.getCityTemperatureImplPort(); Map<String, Object> reqContext = ((BindingProvider) client).getRequestContext(); Map<String, List<String>> authHeaders = new HashMap<String, List<String>>(); authHeaders.put("UserName", Collections.singletonList("Ashish")); authHeaders.put("Password", Collections.singletonList("Password123")); reqContext.put(MessageContext.HTTP_REQUEST_HEADERS, authHeaders); reqContext.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, "http://localhost:8080/GlobalWeatherService/CityTemperature?wsdl"); try { String temp = client.getTemperatureOfCity(city); int statusCode = (int) ((BindingProvider) client).getResponseContext() .get(MessageContext.HTTP_RESPONSE_CODE); System.out.println("Status code = " + statusCode); System.out.println("Temperature of " + city + " is - " + temp); } catch (CityNotFoundException_Exception e) { System.out.println("Exception thrown - " + e.getMessage()); } } } |
Output of client is
1 2 | Status code = 200 Temperature of Sydney is - 15 Degree Celsius |
All inbound/outbound messages can be monitored through TCP/IP Monitor plugin in eclipse which will show credentials are set in SOAPHeaders in inbound message and status code is in outbound message.