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 &lt;wsdl-url&gt;【参考方案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没有上面提到的任何问题,不需要&lt;args&gt;-XadditionalHeaders&lt;/args&gt;,没有额外的依赖 如果需要 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&lt;String, List&lt;String&gt;&gt; 键为 MessageContext.HTTP_REQUEST_HEADERS 将被复制到 SOAP 标头。 Application Authentication with JAX-WS via headers的样本

BindingProvider.USERNAME_PROPERTYBindingProvider.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 标头的主要内容,如果未能解决你的问题,请参考以下文章

JAX-WS 标头身份验证

签署 JAX-WS SOAP 请求

如何使用JAX-WS 2.0模拟soap响应?

xml 转储jax-ws客户端的SOAP消息

如何将 HTTP 标头添加到 SOAP 客户端

如何使用 Savon 向 SOAP 标头添加属性?