问题
In order to consume a RPC web service, first I have to call a web method 'Authentication' and then I am supposed to be able to call other web methods. I have tried to implement a spring web client to consume this service but something is going terribly wrong.
This service was originally designed for php users and the sample client code they provided is as following:
$soap = new SoapClient('http://www.2972.ir/wsdl?XML');
if ($soap->Authentication('user', '123456')) {
echo $soap->getCredit();
}
'authentication' method will return 1 when successful and -1 otherwise. 'getCredit' method return a float showing user current balance.
Now my implementation in Java:
@Service
public class MessageService implements IMessageService {
@Autowired
private IranSMSPanelPortType webService;
public String doSomething() {
int status = webService.authentication("user", "123456");
System.out.println("# status: "+status);
return String.valueOf(webService.getCredit());
}
The status code I get is equal to '1' but upon calling the second web method I get this exception:
javax.xml.ws.soap.SOAPFaultException: You are not authorized
It is clear that calling the 'authentication' method in php does not have the same effect as calling it in java. So I investigated a little more and found out that something is being set in the response cookies of 'authentication' web method.
Here is a glance at the response :
Headers
HTTP/1.1 200 OK
Date: Thu, 07 Aug 2014 17:26:49 GMT
Server: Apache/2
X-Powered-By: PHP/5.4.24
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Connection: close
Set-Cookie: PHPSESSID=m607671c9d8aeks5vm2r84n8r3; path=/
Vary: Accept-Encoding,User-Agent
Transfer-Encoding: chunked
Content-Type: text/xml; charset=utf-8
Envelope
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://www.iransmspanel.com/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>
<ns1:AuthenticationResponse>
<result xsi:type="xsd:int">1</result>
</ns1:AuthenticationResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
As you can see a PHPSESSID has been assigned a value! It is most likely that this parameter is used to handle the authorization.
Given the following Spring Web Service configuration how can I achieve the same result?
<bean id="smsWebService" class="org.springframework.remoting.jaxws.JaxWsPortProxyFactoryBean">
<property name="serviceInterface" value="com.iransmspanel.IranSMSPanelPortType" />
<property name="wsdlDocumentUrl" value="http://www.2972.ir/wsdl?XML" />
<property name="namespaceUri" value="http://www.iransmspanel.com/" />
<property name="serviceName" value="IranSMSPanel" />
<property name="portName" value="IranSMSPanelPort" />
</bean>
I would welcome other implementation of the web service client if it is necessary to handle the authentication.
回答1:
I finally managed to solve this problem using a custom SAOP-Handler and manually extracting PHPSESSID from message Http Headers.
Spring Configuration
<bean id="smsWebService" class="org.springframework.remoting.jaxws.JaxWsPortProxyFactoryBean">
...
<property name="handlerResolver" ref="credentialHandlerResolver"/>
</bean>
<bean id="credentialHandlerResolver" class="com.example.handlers.CredentialHandlerResolver"/>
CredentialHandlerResolver.java
import javax.xml.ws.handler.Handler;
import javax.xml.ws.handler.HandlerResolver;
import javax.xml.ws.handler.PortInfo;
...
public class CredentialHandlerResolver implements HandlerResolver {
private List<Handler> chain;
CredentialHandlerResolver() {
chain = new ArrayList<Handler>();
chain.add(new CredentialHandler());
}
public List<Handler> getHandlerChain(PortInfo portInfo) {
return chain;
}
}
CredentialHandler.java
public class CredentialHandler implements SOAPHandler<SOAPMessageContext> {
public static String PHPSESSID;
public boolean handleMessage(SOAPMessageContext context) {
Boolean isRequest = (Boolean) context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
try {
SOAPMessage message = context.getMessage();
SOAPBody body = message.getSOAPPart().getEnvelope().getBody();
if(!isRequest && body.getFirstChild().getLocalName().equals("AuthenticationResponse") && body.getFirstChild().getTextContent().equals("1")) {
// extracting PHPSESSID from HTTP headers
Map<String, List<String>> httpHeaders = (Map<String, List<String>>) context.get(MessageContext.HTTP_RESPONSE_HEADERS);
String session = httpHeaders.get("Set-Cookie").get(0);
PHPSESSID = session.substring(10, session.indexOf(';'));
}
else if (isRequest && PHPSESSID != null && !body.getFirstChild().getLocalName().equals("Authentication")){
// add PHPSESSID to HTTP headers
Map<String, List<String>> headers = new HashMap<String, List<String>>();
headers.put("Cookie", Arrays.asList("PHPSESSID=" + PHPSESSID));
context.put(MessageContext.HTTP_REQUEST_HEADERS, headers);
}
}
catch (SOAPException ex) {
ex.printStackTrace();
return false;
}
return true;
}
来源:https://stackoverflow.com/questions/25189129/spring-ws-client-set-cookie-for-authentication