SOAPコードを介してHTTPS Java Webサービスを呼び出そうとしています。
URL url = new URL("https://somehost:8181/services/"SomeService?wsdl");
QName qname = new QName("http://services.somehost.com/", "SomeService");
Service service = Service.create(url, qname);
SomeService port = service.getPort(SomeService .class);
port.doSomething();
しかし、例外を取得します。
threw an unexpected exception: javax.xml.ws.soap.SOAPFaultException: Security Requirements not met - No Security header in message
正しいリクエストサンプルを分析したとき、ヘッダーを含める必要があると判断しました。
<S:Header>
<To xmlns="http://www.w3.org/2005/08/addressing">http://somehost:8181/services/SomeService</To>
<Action xmlns="http://www.w3.org/2005/08/addressing">https://somehost:8181/services/"SomeService/doSomethingRequest</Action>
<ReplyTo xmlns="http://www.w3.org/2005/08/addressing">
<Address>http://www.w3.org/2005/08/addressing/anonymous</Address>
</ReplyTo>
<MessageID xmlns="http://www.w3.org/2005/08/addressing">uuid:3428539e-d645-72ae-adc0-5423c1e68942</MessageID>
<wsse:Security S:mustUnderstand="true">
<wsu:Timestamp wsu:Id="_1" xmlns:ns14="http://docs.oasis-open.org/ws-sx/ws-secureconversation/200512" xmlns:ns13="http://schemas.xmlsoap.org/soap/envelope/">
<wsu:Created>2013-01-15T16:36:30Z</wsu:Created>
<wsu:Expires>2014-01-15T14:06:30Z</wsu:Expires>
</wsu:Timestamp>
</wsse:Security>
それでは、このヘッダーをSOAPリクエストに追加する方法を教えてください。
私は個人的に2つのクラスを追加します:HeaderHandlerとHeaderHandlerResolver:
import Java.util.HashSet;
import Java.util.Set;
import javax.xml.namespace.QName;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPHeader;
import javax.xml.soap.SOAPMessage;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;
public class HeaderHandler implements SOAPHandler<SOAPMessageContext> {
public boolean handleMessage(SOAPMessageContext smc) {
Boolean outboundProperty = (Boolean) smc.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
if (outboundProperty.booleanValue()) {
SOAPMessage message = smc.getMessage();
try {
SOAPEnvelope envelope = smc.getMessage().getSOAPPart().getEnvelope();
SOAPHeader header = envelope.addHeader();
SOAPElement security =
header.addChildElement("Security", "wsse", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
SOAPElement usernameToken =
security.addChildElement("UsernameToken", "wsse");
usernameToken.addAttribute(new QName("xmlns:wsu"), "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");
SOAPElement username =
usernameToken.addChildElement("Username", "wsse");
username.addTextNode("test");
SOAPElement password =
usernameToken.addChildElement("Password", "wsse");
password.setAttribute("Type", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText");
password.addTextNode("test321");
//Print out the outbound SOAP message to System.out
message.writeTo(System.out);
System.out.println("");
} catch (Exception e) {
e.printStackTrace();
}
} else {
try {
//This handler does nothing with the response from the Web Service so
//we just print out the SOAP message.
SOAPMessage message = smc.getMessage();
message.writeTo(System.out);
System.out.println("");
} catch (Exception ex) {
ex.printStackTrace();
}
}
return outboundProperty;
}
public Set getHeaders() {
// The code below is added on order to invoke Spring secured WS.
// Otherwise,
// http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd
// won't be recognised
final QName securityHeader = new QName(
"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd",
"Security", "wsse");
final HashSet headers = new HashSet();
headers.add(securityHeader);
return headers;
}
public boolean handleFault(SOAPMessageContext context) {
//throw new UnsupportedOperationException("Not supported yet.");
return true;
}
public void close(MessageContext context) {
//throw new UnsupportedOperationException("Not supported yet.");
}
}
そして
import Java.util.ArrayList;
import Java.util.List;
import javax.xml.ws.handler.Handler;
import javax.xml.ws.handler.HandlerResolver;
import javax.xml.ws.handler.PortInfo;
public class HeaderHandlerResolver implements HandlerResolver {
public List<Handler> getHandlerChain(PortInfo portInfo) {
List<Handler> handlerChain = new ArrayList<Handler>();
HeaderHandler hh = new HeaderHandler();
handlerChain.add(hh);
return handlerChain;
}
}
HeaderHandlerクラスでは、必要な資格情報を追加できます。最終的にそれらを使用するには:
HeaderHandlerResolver handlerResolver = new HeaderHandlerResolver();
service.setHandlerResolver(handlerResolver);
@LaabidiRaissiが言及した手順に従っています。コードは正常に機能しますが、ヘッダーの下にセキュリティ要素を追加することはありません。アウトバウンドSOAP= System.outへのメッセージを出力することで確認しました。詳細な調査の後、更新されたメッセージヘッダーを反映するためにSOAPMessageを明示的に保存する必要があることがわかりました。
soapMessage.saveChanges();
詳細については、- このリンクを確認してください
Apache wss4jを使用してヘッダーを簡単に追加し、パスワードを暗号化することもできます。
import org.Apache.ws.security.WSConstants;
import org.Apache.ws.security.message.WSSecHeader;
import org.Apache.ws.security.message.WSSecUsernameToken;
import javax.xml.namespace.QName;
import javax.xml.soap.SOAPMessage;
import javax.xml.soap.SOAPPart;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;
import Java.io.ByteArrayOutputStream;
import Java.util.Set;
public class WSSecurityHeaderSOAPHandler implements SOAPHandler<SOAPMessageContext> {
private final String usernameText;
private final String passwordText;
public WSSecurityHeaderSOAPHandler(String usernameText, String passwordText) {
this.usernameText = usernameText;
this.passwordText = passwordText;
}
@Override
public boolean handleMessage(SOAPMessageContext context) {
Boolean outboundProperty = (Boolean) context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
if (outboundProperty.booleanValue()) {
try {
SOAPMessage soapMessage = context.getMessage();
soapMessage.removeAllAttachments();
SOAPPart soappart = soapMessage.getSOAPPart();
WSSecHeader wsSecHeader = new WSSecHeader();
wsSecHeader.insertSecurityHeader(soappart);
WSSecUsernameToken token = new WSSecUsernameToken();
token.setPasswordType(WSConstants.PASSWORD_DIGEST);
token.setUserInfo(usernameText, passwordText);
token.build(soappart, wsSecHeader);
soapMessage.saveChanges();
} catch (Exception e) {
throw new RuntimeException("Error on wsSecurityHandler: " + e.getMessage());
}
}
return true;
}
@Override
public boolean handleFault(SOAPMessageContext context) {
return false;
}
@Override
public void close(MessageContext context) {
}
@Override
public Set<QName> getHeaders() {
return null;
}
}
そして、次のようにリクエストを更新する必要があります。
// This is the block that apply the Ws Security to the request
BindingProvider bindingProvider = (BindingProvider) portType;
List<Handler> handlerChain = new ArrayList<>();
handlerChain.add(new WSSecurityHeaderSOAPHandler("username", "password"));
bindingProvider.getBinding().setHandlerChain(handlerChain);
Mavenの依存関係:
<dependency>
<groupId>org.Apache.ws.security</groupId>
<artifactId>wss4j</artifactId>
<version>1.6.19</version>
</dependency>
サンプルのメインクラス:
package test;
import Java.util.ArrayList;
import Java.util.List;
import javax.xml.ws.BindingProvider;
import javax.xml.ws.handler.Handler;
// next headers is generated from "NetBeans New Webservice Client"
import sk.firma.wstest.definitions.*;
import sk.firma.wstest.schemas.*;
/**
*
* @author Jan
*/
public class TestWSService {
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
try {
WsService service = new WsService();
Ws port = service.getWsKsSoap11();
// This is the block that apply the Ws Security to the request
BindingProvider bindingProvider = (BindingProvider) port;
@SuppressWarnings("rawtypes")
List<Handler> handlerChain = new ArrayList<Handler>();
handlerChain.add(new WSSecurityHeaderSOAPHandler("username", "password"));
bindingProvider.getBinding().setHandlerChain(handlerChain);
// Initialize and Run Service
InVal inVal = new InVal();
ReturnValue retVal = port.test(inVal);
} catch (Exception e) {
e.printStackTrace();
}
}
}
そして今、WS-Security Header SOAP Handler:
package test;
import Java.text.DateFormat;
import Java.text.SimpleDateFormat;
import Java.util.Date;
import Java.util.HashSet;
import Java.util.Set;
import Java.util.TimeZone;
import javax.xml.namespace.QName;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPHeader;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;
public class WSSecurityHeaderSOAPHandler implements SOAPHandler<SOAPMessageContext> {
private static final String URL_WSSE_NAMESPACE = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd";
private static final String URL_WSU_NAMESPACE = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd";
private final String usernameText;
private final String passwordText;
public WSSecurityHeaderSOAPHandler(String usernameText, String passwordText) {
this.usernameText = usernameText;
this.passwordText = passwordText;
}
public String getCurrentDateTime() {
/* e.g. 2001-10-13T09:00:00Z */
final SimpleDateFormat FORMATTER_DATETIME_NO_MS = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
DateFormat dfETC = FORMATTER_DATETIME_NO_MS;
dfETC.setTimeZone(TimeZone.getTimeZone("CET"));
StringBuffer dateETC = new StringBuffer(dfETC.format(new Date()));
dateETC.append('Z');
return dateETC.toString();
}
public String getCurrentDateTimePlusDelay(long delayInSeconds) {
/* e.g. 2001-10-13T09:00:00Z */
final SimpleDateFormat FORMATTER_DATETIME_NO_MS = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
DateFormat dfETC = FORMATTER_DATETIME_NO_MS;
dfETC.setTimeZone(TimeZone.getTimeZone("CET"));
Date date = new Date();
long timeInMsecs = date.getTime();
date.setTime(timeInMsecs + delayInSeconds*1000L);
StringBuffer dateETC = new StringBuffer(dfETC.format(date));
dateETC.append('Z');
return dateETC.toString();
}
@Override
public boolean handleMessage(SOAPMessageContext soapMessageContext) {
Boolean outboundProperty = (Boolean) soapMessageContext.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
if (outboundProperty) {
try {
SOAPEnvelope soapEnvelope = soapMessageContext.getMessage().getSOAPPart().getEnvelope();
SOAPHeader header = soapEnvelope.getHeader();
if (header == null) {
header = soapEnvelope.addHeader();
}
SOAPElement securityHeaderElement = header.addChildElement("Security", "wsse", URL_WSSE_NAMESPACE);
securityHeaderElement.addAttribute(soapEnvelope.createName("S:mustUnderstand"), "1");
// Add Timestamp element to "Security" soapHeaderElement
// Sample: <u:Timestamp>
// <u:Created>2011-10-13T08:20:01.183Z</u:Created>
// <u:Expires>2011-10-13T17:25:01.183Z</u:Expires>
// </u:Timestamp>
javax.xml.soap.Name timestampElementName = soapEnvelope.createName("Timestamp", "wsu", URL_WSU_NAMESPACE);
SOAPElement timestampSOAPElement = securityHeaderElement.addChildElement(timestampElementName);
String created = getCurrentDateTime();
String expires = getCurrentDateTimePlusDelay(60L*60L); /* 60 minutes delay */
// Add Created to Timestamp
SOAPElement createdSOAPElement = timestampSOAPElement
.addChildElement("Created"/* local name */, "wsu" /* prefix */, URL_WSU_NAMESPACE);
createdSOAPElement.addTextNode(created);
// Add Expires to Timestamp
SOAPElement expiresSOAPElement = timestampSOAPElement
.addChildElement("Expires"/* local name */, "wsu" /* prefix */,URL_WSU_NAMESPACE);
expiresSOAPElement.addTextNode(expires);
// Add usernameToken to "Security" soapHeaderElement
javax.xml.soap.Name usernameTokenElementName = soapEnvelope.createName("UsernameToken", "wsse",
URL_WSSE_NAMESPACE);
SOAPElement usernameTokenSOAPElement = securityHeaderElement.addChildElement(usernameTokenElementName);
// Add Username to usernameToken
SOAPElement userNameSOAPElement = usernameTokenSOAPElement
.addChildElement("Username"/* local name */, "wsse" /* prefix */, URL_WSSE_NAMESPACE);
userNameSOAPElement.addTextNode(this.usernameText);
// Add password to UsernameToken
javax.xml.soap.Name passwordElementName = soapEnvelope.createName("Password", "wsse", URL_WSSE_NAMESPACE);
SOAPElement passwordSOAPElement = usernameTokenSOAPElement.addChildElement(passwordElementName);
/* Add "Type" attribute to <Password> header element */
//passwordSOAPElement.addAttribute(soapEnvelope.createName("Type", "", ""),
// "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText");
passwordSOAPElement.addTextNode(this.passwordText);
} catch (Exception e) {
throw new RuntimeException("Error on wsSecurityHandler: " + e.getMessage());
}
}
return true;
}
@Override
public void close(MessageContext context) {
// TODO Auto-generated method stub
}
@Override
public boolean handleFault(SOAPMessageContext context) {
// TODO Auto-generated method stub
return true;
}
@Override
public Set<QName> getHeaders() {
// throw new UnsupportedOperationException("Not supported yet.");
final QName securityHeader = new QName("http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd",
"Security", "wsse");
final HashSet headers = new HashSet();
headers.add(securityHeader);
return headers;
}
}