JAX-WS - 添加 SOAP 标头
Posted
技术标签:
【中文标题】JAX-WS - 添加 SOAP 标头【英文标题】:JAX-WS - Adding SOAP Headers 【发布时间】:2011-01-20 08:17:48 【问题描述】:我正在尝试创建一个独立的客户端来使用一些 Web 服务。我必须将我的用户名和密码添加到 SOAP 标头。我尝试按如下方式添加凭据:
OTSWebSvcsService service = new OTSWebSvcsService();
OTSWebSvcs port = service.getOTSWebSvcs();
BindingProvider prov = (BindingProvider)port;
prov.getRequestContext().put(BindingProvider.USERNAME_PROPERTY, "myusername");
prov.getRequestContext().put(BindingProvider.PASSWORD_PROPERTY, "mypassword");
...
当我在服务上调用方法时,出现以下异常:
com.ibm.wsspi.wssecurity.SoapSecurityException: WSEC5048E: One of "SOAP Header" elements required.
我做错了什么?如何将这些属性添加到 SOAP 标头?
已编辑:我使用的是 JDK6 中包含的 JAX-WS 2.1。我现在正在使用 JAX-WS 2.2。我现在得到以下异常:
com.ibm.wsspi.wssecurity.SoapSecurityException: WSEC5509E: A security token whose type is [http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#UsernameToken] is required.
我该如何创建这个令牌?
【问题讨论】:
目前正在使用 SOAP 并遇到同样的问题。在阅读了这个问题的所有答案之后,我有一种想要退出编程的感觉,我喜欢这个。老实说,我理解为什么现在每个人都在使用 REST,以及为什么 SOAP 是一种本不应该存在的可怕技术...... 特别感谢@hello_earth,他指出了其他人只会提供错误线索的确切地方。 【参考方案1】:将对象添加到标题我们使用这里使用的示例,但我会完成
ObjectFactory objectFactory = new ObjectFactory();
CabeceraCR cabeceraCR =objectFactory.createCabeceraCR();
cabeceraCR.setUsuario("xxxxx");
cabeceraCR.setClave("xxxxx");
使用对象工厂,我们创建了要求传递标头的对象。添加到标题中的
WSBindingProvider bp = (WSBindingProvider)wsXXXXXXSoap;
bp.setOutboundHeaders(
// Sets a simple string value as a header
Headers.create(jaxbContext,objectFactory.createCabeceraCR(cabeceraCR))
);
我们使用 WSBindingProvider 来添加标头。对象直接使用会报错所以我们使用方法
objectFactory.createCabeceraCR(cabeceraCR)
此方法将在对象工厂上创建一个像这样的 JAXBElement
@XmlElementDecl(namespace = "http://www.creditreport.ec/", name = "CabeceraCR")
public JAXBElement<CabeceraCR> createCabeceraCR(CabeceraCR value)
return new JAXBElement<CabeceraCR>(_CabeceraCR_QNAME, CabeceraCR.class, null, value);
而我们得到的jaxbContext是这样的:
jaxbContext = (JAXBRIContext) JAXBContext.newInstance(CabeceraCR.class.getPackage().getName());
这会将对象添加到标题中。
【讨论】:
【参考方案2】:可以使用@WebParam(header = true) 在 SOAP 标头 (JaxWS) 中传输数据:
@WebMethod(operationName = "SendRequest", action = "http://abcd.ru/")
@Oneway
public void sendRequest(
@WebParam(name = "Message", targetNamespace = "http://abcd.ru/", partName = "Message")
Data message,
@WebParam(name = "ServiceHeader", targetNamespace = "http://abcd.ru/", header = true, partName = "ServiceHeader")
Header serviceHeader);
如果要生成带有 SOAP Headers 的客户端,则需要使用 -XadditionalHeaders:
wsimport -keep -Xnocompile -XadditionalHeaders -Xdebug http://12.34.56.78:8080/TestHeaders/somewsdl?wsdl -d /home/evgeny/DEVELOPMENT/JAVA/gen
如果不需要@Oneway 网络服务,可以使用Holder:
@WebMethod(operationName = "SendRequest", action = "http://abcd.ru/")
public void sendRequest(
@WebParam(name = "Message", targetNamespace = "http://abcd.ru/", partName = "Message")
Data message,
@WebParam(name = "ServiceHeader", targetNamespace = "http://abcd.ru/", header = true, partName = "ServiceHeader")
Holder<Header> serviceHeader);
【讨论】:
+1,-XadditionalHeaders
在这种情况下是一个重要的属性。
header=true 对我有用。我已经复制了现有的存根并在副本上设置了 header = true 以便 maven wsimport 不要覆盖生成的存根。
maven 插件中 -additionalHeader 的等价物是什么?
@AbdulRazakAK -XadditionalHeaders
对于那些使用wsdl2java
而不是wsimport
的人,-XadditionalHeaders
的等价物是-exsh true
(exsh 代表扩展soap 标头绑定,'true' 启用此功能)带有示例命令存在:.\wsdl2java.bat -exsh true -autoNameResolution <wsdl-url>
【参考方案3】:
最好的选择(当然对我来说)是自己动手。这意味着您可以通过编程方式修改 SOAP 消息的所有部分
Binding binding = prov.getBinding();
List<Handler> handlerChain = binding.getHandlerChain();
handlerChain.add( new ModifyMessageHandler() );
binding.setHandlerChain( handlerChain );
ModifyMessageHandler 源可以是
@Override
public boolean handleMessage( SOAPMessageContext context )
SOAPMessage msg = context.getMessage();
try
SOAPEnvelope envelope = msg.getSOAPPart().getEnvelope();
SOAPHeader header = envelope.addHeader();
SOAPElement ele = header.addChildElement( new QName( "http://uri", "name_of_header" ) );
ele.addTextNode( "value_of_header" );
ele = header.addChildElement( new QName( "http://uri", "name_of_header" ) );
ele.addTextNode( "value_of_header" );
ele = header.addChildElement( new QName( "http://uri", "name_of_header" ) );
ele.addTextNode( "value_of_header" );
...
希望对你有帮助
【讨论】:
【参考方案4】:我从Pascal's solution 开始在这里的所有答案中苦苦挣扎,随着Java 编译器不再默认绑定rt.jar
(并且使用内部类使其特定于该运行时实现),这变得越来越困难。
The answer from edubriguenti 把我拉近了。不过,处理程序在最后一段代码中的连接方式对我不起作用——它从未被调用过。
我最终使用了他的处理程序类的变体,但将其连接到 javax.xml.ws.Service
实例,如下所示:
Service service = Service.create(url, qname);
service.setHandlerResolver(
portInfo -> Collections.singletonList(new SOAPHeaderHandler(handlerArgs))
);
【讨论】:
【参考方案5】:不能 100% 确定,因为问题缺少一些细节,但如果您使用的是 JAX-WS RI,请查看 Adding SOAP headers when sending requests:
这样做的便携方式是 你创建了一个
SOAPHandler
并且一团糟 与 SAAJ,但 RI 提供了一个 更好的方法。当您创建代理或调度时 对象,他们实现
BindingProvider
接口。当你 使用 JAX-WS RI,您可以向下转换为WSBindingProvider
定义了一个 仅由 JAX-WS RI。此界面可让您设置 任意数量的 Header 对象, 每个代表一个 SOAP 标头。你 如果你可以自己实现它 想要,但很可能你会使用其中之一 定义的工厂方法
Headers
类创建一个。import com.sun.xml.ws.developer.WSBindingProvider; HelloPort port = helloService.getHelloPort(); // or something like that... WSBindingProvider bp = (WSBindingProvider)port; bp.setOutboundHeader( // simple string value as a header, like <simpleHeader>stringValue</simpleHeader> Headers.create(new QName("simpleHeader"),"stringValue"), // create a header from JAXB object Headers.create(jaxbContext,myJaxbObject) );
相应地更新您的代码,然后重试。如果您没有使用 JAX-WS RI,请更新您的问题并提供更多上下文信息。
更新:您要调用的 Web 服务似乎受到 WS-Security/UsernameTokens 的保护。这与您最初的问题有点不同。无论如何,要配置您的客户端以发送用户名和密码,我建议查看伟大的帖子Implementing the WS-Security UsernameToken Profile for Metro-based web services(跳转到第 4 步)。在这一步中使用 NetBeans 可能会轻松很多。
【讨论】:
我无法让 eclipse 导入这个com.sun.xml.internal.ws.developer.WSBindingProvider
类。
如果我们使用 com.sum 包中的类会不会有任何可移植性问题?
关于 WS-Security 的帖子链接已失效。
@pihentagy - 我有import javax.xml.ws.BindingProvider;
【参考方案6】:
您可以将用户名和密码添加到 SOAP 标头
BindingProvider prov = (BindingProvider)port;
prov.getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY,
"your end point"));
Map<String, List<String>> headers = new HashMap<String, List<String>>();
prov.getRequestContext().put(BindingProvider.USERNAME_PROPERTY, "myusername");
prov.getRequestContext().put(BindingProvider.PASSWORD_PROPERTY, "mypassword");
prov.getRequestContext().put(MessageContext.HTTP_REQUEST_HEADERS, headers);
【讨论】:
到目前为止,最简单的解决方案。 PD:我使用Maven没有上面提到的任何问题,不需要<args>-XadditionalHeaders</args>
,没有额外的依赖
如果需要 UsernameToken 这将不起作用 - 这会修改 HTTP 标头,而不是 SOAP 消息标头...
虽然在 UsernameToken 场景中也可能需要基本身份验证标头......【参考方案7】:
在jaxws-rt-2.2.10-ources.jar!\com\sun\xml\ws\transport\http\client\HttpTransportPipe.java
:
public Packet process(Packet request)
Map<String, List<String>> userHeaders = (Map<String, List<String>>) request.invocationProperties.get(MessageContext.HTTP_REQUEST_HEADERS);
if (userHeaders != null)
reqHeaders.putAll(userHeaders);
因此,来自 requestContext 的 Map<String, List<String>>
键为 MessageContext.HTTP_REQUEST_HEADERS
将被复制到 SOAP 标头。
Application Authentication with JAX-WS via headers的样本
BindingProvider.USERNAME_PROPERTY
和BindingProvider.PASSWORD_PROPERTY
密钥在HttpTransportPipe.addBasicAuth()
中以特殊方式处理,添加标准基本授权Authorization
标头。
另见Message Context in JAX-WS
【讨论】:
【参考方案8】:此外,如果您使用 Maven 构建项目,则需要添加以下依赖项:
<dependency>
<groupId>com.sun.xml.ws</groupId>
<artifactId>jaxws-rt</artifactId>
<version>currentversion/version>
</dependency>
这为您提供了 com.sun.xml.ws.developer.WSBindingProvider
类。
链接:https://mvnrepository.com/artifact/com.sun.xml.ws/jaxws-rt
【讨论】:
【参考方案9】:我添加这个答案是因为其他人都没有为我工作。
我必须向代理添加一个Header Handler:
import java.util.Set;
import java.util.TreeSet;
import javax.xml.namespace.QName;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPFactory;
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 SOAPHeaderHandler implements SOAPHandler<SOAPMessageContext>
private final String authenticatedToken;
public SOAPHeaderHandler(String authenticatedToken)
this.authenticatedToken = authenticatedToken;
public boolean handleMessage(SOAPMessageContext context)
Boolean outboundProperty =
(Boolean) context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
if (outboundProperty.booleanValue())
try
SOAPEnvelope envelope = context.getMessage().getSOAPPart().getEnvelope();
SOAPFactory factory = SOAPFactory.newInstance();
String prefix = "urn";
String uri = "urn:xxxx";
SOAPElement securityElem =
factory.createElement("Element", prefix, uri);
SOAPElement tokenElem =
factory.createElement("Element2", prefix, uri);
tokenElem.addTextNode(authenticatedToken);
securityElem.addChildElement(tokenElem);
SOAPHeader header = envelope.addHeader();
header.addChildElement(securityElem);
catch (Exception e)
e.printStackTrace();
else
// inbound
return true;
public Set<QName> getHeaders()
return new TreeSet();
public boolean handleFault(SOAPMessageContext context)
return false;
public void close(MessageContext context)
//
在代理中,我只是添加了Handler:
BindingProvider bp =(BindingProvider)basicHttpBindingAuthentication;
bp.getBinding().getHandlerChain().add(new SOAPHeaderHandler(authenticatedToken));
bp.getBinding().getHandlerChain().add(new SOAPLoggingHandler());
【讨论】:
请注意,以这种方式添加处理程序是行不通的,因为需要调用 bp.getBinding().setHandlerChain(...) - 请参阅 rumberomelo 的回答 还有一个专门针对 UsernameToken 标头场景的更好示例:ibm.com/docs/en/sc-and-ds/… 感谢 @hello_earth,这 2 个 cmets 正是解决这个问题所需要的。【参考方案10】:使用 maven 和插件 jaxws-maven-plugin。这将生成一个 Web 服务客户端。确保将 xadditionalHeaders 设置为 true。这将生成带有标题输入的方法。
【讨论】:
以上是关于JAX-WS - 添加 SOAP 标头的主要内容,如果未能解决你的问题,请参考以下文章