JAX-WS - 添加SOAP头部

38

我正在尝试创建一个独立的客户端来使用一些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是一种可怕的技术,它本不应该存在... - nephewtom
特别感谢@hello_earth,他指出了准确的位置,而其他人只给出了错误的线索。 - nephewtom
10个回答

45

可以使用@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标头的客户端,您需要使用-XadditionalHeaders:
wsimport -keep -Xnocompile -XadditionalHeaders -Xdebug http://12.34.56.78:8080/TestHeaders/somewsdl?wsdl -d /home/evgeny/DEVELOPMENT/JAVA/gen

如果不需要 @Oneway web service,你可以使用 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);

7
+1,在这种情况下,“-XadditionalHeaders”是一个重要的属性。 - Buhake Sindi
header=true 对我很有帮助。我复制了现有的存根,并在副本上设置了 header=true,以防止 Maven wsimport 覆盖生成的存根。 - Zeus
Maven插件中的-additionalHeader等效于什么? - Abdul Razak AK
1
@AbdulRazakAK <args>-XadditionalHeaders</args>: - apetrelli
1
对于使用wsdl2java而不是wsimport的人,与-XadditionalHeaders相当的是-exsh true(exsh代表扩展SOAP头绑定,'true'启用此功能),例如命令如下:.\wsdl2java.bat -exsh true -autoNameResolution <wsdl-url> - vab2048
你从哪个包中获取Header类? - SavinI

37

根据问题缺少一些详细信息,不能百分之百确定,但如果您正在使用JAX-WS RI,则可以查看添加SOAP头文件发送请求

这样做的可移植方式是创建一个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进行保护的。这与您最初的问题有些不同。无论如何,为了配置客户端发送用户名和密码,建议查看优秀文章实现基于Metro Web服务的WS安全性用户名令牌配置文件(跳转到第4步)。在此步骤中使用NetBeans可能会使事情变得更容易。

1
我无法让Eclipse导入com.sun.xml.internal.ws.developer.WSBindingProvider类。 - pihentagy
1
如果我们使用com.sum包中的类,会有任何可移植性问题吗? - james2611nov
2
WS-Security的帖子链接已失效。 - Søren Boisen
@pihentagy - 我有 import javax.xml.ws.BindingProvider; - JGlass

8
我添加这个答案是因为其他的答案对我都没有用。
我必须向代理添加一个“头处理程序”:
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) {
        //
    }
}

在代理中,我只需要添加处理程序:
BindingProvider bp =(BindingProvider)basicHttpBindingAuthentication;
bp.getBinding().getHandlerChain().add(new SOAPHeaderHandler(authenticatedToken));
bp.getBinding().getHandlerChain().add(new SOAPLoggingHandler());

1
请注意,以这种方式添加处理程序是行不通的,因为需要调用bp.getBinding().setHandlerChain(...) - 请参见rumberomelo的答案。 - hello_earth
1
这是一个与UsernameToken头部场景相关的更好的示例:https://www.ibm.com/docs/en/sc-and-ds/8.1.0?topic=client-soaphandler-example。请参考。 - hello_earth
谢谢@hello_earth,这两条评论正是需要揭示这个问题的。 - nephewtom

7
此外,如果您正在使用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

4

您可以将用户名和密码添加到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);

到目前为止,最简单和简单的解决方案。附注:使用Maven时我没有遇到上述任何问题,也不需要<args>-XadditionalHeaders</args>或额外的依赖项。 - FiruzzZ
2
如果需要UsernameToken,这种方法是行不通的——它修改的是HTTP头而不是SOAP消息头... - hello_earth
尽管在UsernameToken场景中也可能需要Basic身份验证头... - hello_earth

3

2

最好的选择(当然是对我来说)是自己动手做。这意味着您可以以编程方式修改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" );

我希望这能对你有所帮助。


0

我在这里遇到了所有答案的困难,从Pascal's solution开始,由于Java编译器不再默认绑定rt.jar(使用内部类使其特定于该运行时实现),因此变得更加困难。

edubriguenti的答案让我接近成功。然而,在最后一段代码中连接处理程序的方式对我没有用 - 它从未被调用。

最终,我使用了他的处理程序类的变体,但将其连接到javax.xml.ws.Service实例中,如下所示:

Service service = Service.create(url, qname); service.setHandlerResolver( portInfo -> Collections.singletonList(new SOAPHeaderHandler(handlerArgs)) );


0
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);

所以,将请求上下文中键为MessageContext.HTTP_REQUEST_HEADERSMap<String, List<String>>复制到SOAP标头中。 使用JAX-WS通过标头进行应用程序身份验证的示例

BindingProvider.USERNAME_PROPERTYBindingProvider.PASSWORD_PROPERTY键在HttpTransportPipe.addBasicAuth()中采用特殊方式处理,添加标准基本授权Authorization头。

另请参见JAX-WS中的消息上下文


-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());

这将把对象添加到头部。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接