2025年4月15日 星期二

Java Axis 2 client 端呼叫 SOAP API (需要 Basic Authentication 時的 Bug 應對方式)

最近在工作上接到一個升級 client 端 Apache Axis 版本的任務,
碰到了一些問題及解決方法,在這邊紀錄一下怕之後忘記可以拿來參考。

Apache Axis 目前有兩個大版本,v1 含有一些漏洞與弱點 (CVE, Common Vulnerabilities and Exposures),所以建議升級改用 v2 版本的 Apache Axis 2

因為工作關係只用到 client 端,所以在這篇文章裡只涉及呼叫 SOAP API 的 client  部份,沒有 server 端的部份。

在升級時我碰到的最大問題是無法正常設定 Basic Authentication header,
因為我要呼叫的 SOAP Server API 需要 Basic Authentication header 驗證,
也就是要在 Http Header 上加入如下的 Header 做驗證,把 username 和 password 用冒號 (":") 接起來以後對其做 Base64 encode,再在前端加上 "Basic ",
將其將做 Value 搭配 "Authorization" 做 Key 設定到 Header 上 :

Authorization: Basic base64Encode(<username>:<password>)

根據官網這裡 Basic, Digest and NTLM Authentication 有寫到正常設定 Baisc Authentication 的程式寫法,
但也提到了目前版本有一個存在的問題導致正常寫法的方式不起作用,
其中寫到:

Note: Basic preemptive authentication requires a work around described in https://issues.apache.org/jira/browse/AXIS2-6055 until a proper fix is contributed by the community as we lack committers who use it.

其中給了一個連結: https://issues.apache.org/jira/browse/AXIS2-6055,
在連結中有提到問題及 work around 的暫時解法,
在這篇文中我們會參考在上面提到的解法。

下面直接上程式,相關說明也都寫在註解中:

首先是使用的 Maven Dependency (${axis2.version} 我是設定 2.0.0 版)

<!-- https://mvnrepository.com/artifact/org.apache.axis2/axis2-kernel -->
	<dependency>
	    <groupId>org.apache.axis2</groupId>
	    <artifactId>axis2-kernel</artifactId>
	    <version>${axis2.version}</version>
	</dependency>
	
	<!-- https://mvnrepository.com/artifact/org.apache.axis2/axis2-adb -->
	<dependency>
	    <groupId>org.apache.axis2</groupId>
	    <artifactId>axis2-adb</artifactId>
	    <version>${axis2.version}</version>
	</dependency>
	
	<!-- https://mvnrepository.com/artifact/org.apache.axis2/axis2-transport-http -->
	<dependency>
	    <groupId>org.apache.axis2</groupId>
	    <artifactId>axis2-transport-http</artifactId>
	    <version>${axis2.version}</version>
	</dependency>
	
	<!-- https://mvnrepository.com/artifact/org.apache.axis2/axis2-transport-local -->
	<dependency>
	    <groupId>org.apache.axis2</groupId>
	    <artifactId>axis2-transport-local</artifactId>
	    <version>${axis2.version}</version>
	</dependency>
	
	<!-- https://mvnrepository.com/artifact/org.apache.axis2/axis2-jaxws -->
	<dependency>
	    <groupId>org.apache.axis2</groupId>
	    <artifactId>axis2-jaxws</artifactId>
	    <version>${axis2.version}</version>
	</dependency>

再來是程式碼:

package test.soap;

import java.nio.charset.StandardCharsets;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;

import org.apache.axiom.om.OMAbstractFactory;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMFactory;
import org.apache.axiom.om.OMNamespace;
import org.apache.axis2.addressing.EndpointReference;
import org.apache.axis2.client.Options;
import org.apache.axis2.client.ServiceClient;
import org.apache.axis2.context.NamedValue;
import org.apache.axis2.kernel.http.HTTPConstants;

public class SoapAxis2Test {
	
	public static void main(String[] args) throws RemoteException {
		String username = "username";
		String password = "password";
		String namespace = "http://xxx.xxx/";
		
		// SOAP API 名稱 (operation)
		String operationName = "operationName"; 
		//依需要設定 SOAP Action,有些 Server 端需要有些不需要,
	    //可自行參考 WSDL 文檔 API 的 soapAction 節點部份
		String action = namespace + operationName;
		
		//對應的 WSDL 文檔位址會是 https://xxx.xxx.xxx?wsdl
		EndpointReference targetEPR = new EndpointReference("https://xxx.xxx.xxx");
	    Options options = new Options();
	    options.setTo(targetEPR);
      
	    //雖然官網說可以用以下寫法設置 Basic Authorization header,但目前實際上不行,
	    //問題似乎是出在 preemptive authentication 無法正常設定
//	    Authenticator auth = new Authenticator();
//      auth.setUsername(username);
//      auth.setPassword(password);
//      auth.setPreemptiveAuthentication(true);
//      auth.setAuthSchemes(List.of(Authenticator.BASIC));
//      options.setProperty(HTTPConstants.AUTHENTICATE, auth);
	    
	    //根據官網這裡 (https://axis.apache.org/axis2/java/core/docs/http-transport.html#Basic.2C_Digest_and_NTLM_Authentication)
	    //有寫到目前存在的問題
	    //Note: Basic preemptive authentication requires a work around described in https://issues.apache.org/jira/browse/AXIS2-6055 until a proper fix is contributed by the community as we lack committers who use it.
	    //其中給了一個連結: https://issues.apache.org/jira/browse/AXIS2-6055
	    //在連結中有提到問題及 work around 的暫時解法,其中一個方法如下,
	    //成功設置後,應可在 http request 上正確地設定 Basic Authorization header
	    List<NamedValue> authHeaders = new ArrayList<>();
	    String basicAuth = username + ":" + password;
	    authHeaders.add(new NamedValue(HTTPConstants.HEADER_AUTHORIZATION, "Basic " + new String(Base64.getEncoder().encode(basicAuth.getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8)));
	    options.setProperty(HTTPConstants.HTTP_HEADERS, authHeaders);
	    //依需要設定 SOAP Action
	    //options.setAction(action);
	    
	    ServiceClient client = new ServiceClient();
        client.setOptions(options);
        
	    OMFactory factory = OMAbstractFactory.getOMFactory();
	    
	    //依要呼叫的 SOAP API 自行建構 OMElement,供產生 xml 放在 http request body 中
	    //自行設定 SOAP API 的名稱 (operation)
        OMNamespace omNamespace = factory.createOMNamespace(namespace, "");
        OMElement operationElement = factory.createOMElement(operationName, omNamespace);

        //自行設定 SOAP API 所需的參數 (parameter)
        OMElement param1Element = factory.createOMElement("xxParameter1", omNamespace);
        param1Element.setText("xxx");
        operationElement.addChild(param1Element);
        
        OMElement param2Element = factory.createOMElement("xxParameter2", omNamespace);
        param2Element.setText("xxx");
        operationElement.addChild(param2Element);
        
        //Blocking invocation
        OMElement result = client.sendReceive(operationElement);
        //自行解析 SOAP API 回傳的結果
        String resultStr = result.getFirstElement().getText();
	    
	    System.out.println(resultStr);
	}
}

參考資料:

  1. Migrating from Apache Axis 1.x to Axis2
  2. HTTP transports – Apache Axis2 - Basic, Digest and NTLM Authentication
  3. Basic Auth credentials are missing in request
  4. Axis2 服务器未能识别 HTTP 头 SOAPAction 的值 的解决办法
  5. axis2客户端调用的三种方式-CSDN博客