preface

Before, due to some reasons, we needed to build webService interface for testing. Found that the blog on the Internet to write a lot of good, write a mess of many, and search rankings can be very front. Here to build their own WebService interface summary and record, convenient for later use when a reference.

Abstract

This blog post mainly shows a demo of webService interface using CXF to implement user name and password authentication under the Spring Boot framework. It’s not production code. In addition, some of the potholes encountered during development will be mentioned.

note

For the JAR package version, see jar package dependencies. The implementation code can be seen below.

DEMO

Here’s the code:

Jar package dependency

<? xml version="1.0" encoding="UTF-8"? ><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.3.9. RELEASE</version>
		<relativePath/> <! -- lookup parent from repository -->
	</parent>
	<groupId>com.liuy</groupId>
	<artifactId>ws</artifactId>
	<version>0.0.1 - the SNAPSHOT</version>
	<name>ws</name>
	<description>Demo project for webservice</description>
	<properties>
		<java.version>1.8</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter</artifactId>
		</dependency>

		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
			<exclusions>
				<exclusion>
					<groupId>org.junit.vintage</groupId>
					<artifactId>junit-vintage-engine</artifactId>
				</exclusion>
			</exclusions>
		</dependency>

		<dependency>
			<groupId>org.apache.cxf</groupId>
			<artifactId>cxf-spring-boot-starter-jaxws</artifactId>
			<version>3.2.8</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.ws</groupId>
			<artifactId>spring-ws-security</artifactId>
		</dependency>

		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-validator</artifactId>
			<version>6.0.18. The Final</version>
		</dependency>

		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>fastjson</artifactId>
			<version>1.2.56</version>
		</dependency>

		<dependency>
			<groupId>org.apache.ws.security</groupId>
			<artifactId>wss4j</artifactId>
			<version>1.6.19</version>
		</dependency>

		<dependency>
			<groupId>org.apache.cxf</groupId>
			<artifactId>cxf-rt-ws-security</artifactId>
			<version>3.2.8</version>
		</dependency>
		<dependency>
			<groupId>org.apache.cxf</groupId>
			<artifactId>cxf-rt-frontend-jaxws</artifactId>
			<version>3.2.8</version>
		</dependency>
		<dependency>
			<groupId>org.apache.cxf</groupId>
			<artifactId>cxf-rt-transports-http</artifactId>
			<version>3.2.8</version>
		</dependency>

	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
				<configuration>
					<excludes>
						<exclude>
							<groupId>org.projectlombok</groupId>
							<artifactId>lombok</artifactId>
						</exclude>
					</excludes>
				</configuration>
			</plugin>
		</plugins>
	</build>

</project>
Copy the code

Webservice interface and implementation

1, interfaces,

package com.liuy.ws.service;

import com.alibaba.fastjson.JSONObject;

import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebService;

@WebService(name = "helloService", targetNamespace = "http://service.ws.liuy.com/")
public interface HelloService {

    @WebMethod
    String sayHello(@WebParam(name = "msg") String msg);

    @WebMethod
    String getUser(@WebParam(name = "msg") String msg);

    @WebMethod
    JSONObject getJson(@WebParam(name = "msg") String msg);

}
Copy the code
package com.liuy.ws.service;

import javax.jws.WebMethod;
import javax.jws.WebService;

@WebService( targetNamespace = "http://service.webservice.yinhai.com/" )
public interface HelloService2 {

    /** * Obtain the doctor id source details **@param inputStr
     * @return* /
    @WebMethod( operationName = "getUser" )
    String getUser( String inputStr );

}

Copy the code

2. Implementation classes

package com.liuy.ws.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.liuy.ws.service.HelloService;
import org.springframework.stereotype.Service;

@Service
public class HelloServiceImpl implements HelloService {

    @Override
    public String sayHello(String msg) {
        return "hello: " + msg;
    }

    @Override
    public String getUser(String msg) {
        StringBuffer sb = new StringBuffer(msg);
        for (int i = 0; i < 100000; i++) {
            sb.append(msg);
        }
        return sb.toString();
    }

    @Override
    public JSONObject getJson(String msg) {
        JSONObject json = new JSONObject();
        json.put("msg", msg);
        returnjson; }}Copy the code
package com.liuy.ws.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.liuy.ws.service.PatientWebservice;
import org.opensaml.xml.signature.J;
import org.springframework.stereotype.Service;

import javax.jws.HandlerChain;

@HandlerChain(file = "/handle-chain.xml")
@Service
public class PatientWebserviceImpl implements PatientWebservice {

    @Override
    public String getUser(String inputStr) {
        JSONObject obj = JSONObject.parseObject(inputStr);
        returnobj.toJSONString(); }}Copy the code

Third, the server code

There are three classes on the server:

  1. CxfConfig.java
  2. SecurityHandler.java
  3. WsClinetAuthHandler.java
package com.liuy.ws.server;

import com.liuy.ws.client.PasswordHandler;
import com.liuy.ws.service.HelloService;
import com.liuy.ws.service.PatientWebservice;
import org.apache.cxf.Bus;
import javax.xml.ws.Endpoint;

import org.apache.cxf.binding.soap.saaj.SAAJOutInterceptor;
import org.apache.cxf.jaxws.EndpointImpl;
import org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor;
import org.apache.ws.security.WSConstants;
import org.apache.ws.security.handler.WSHandlerConstants;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.ws.soap.security.wss4j2.Wss4jSecurityInterceptor;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

@Configuration
public class CxfConfig {
	@Autowired
	private Bus bus;
	@Autowired
	private HelloService helloService;
	@Autowired
	private HelloService2 helloService2;
	@Autowired
	AuthInterceptor authInterceptor;
	@Bean
	public Endpoint endpoint() {
		EndpointImpl endpoint = new EndpointImpl(bus, helloService);
		endpoint.publish("/helloService");
// endpoint.getInInterceptors().add(authInterceptor);
		/ / access connections: http://localhost:8080/services/helloService? wsdl
		return endpoint;
	}

	@Bean
	public Endpoint endpoint2() {
		EndpointImpl endpoint = new EndpointImpl(bus, helloService2);
		endpoint.publish("/helloService2");
		Map<String.Object> outProps = new HashMap<String.Object> (); outProps.put(WSHandlerConstants.ACTION, WSHandlerConstants.USERNAME_TOKEN); outProps.put(WSHandlerConstants.USER,"admin");
		outProps.put(WSHandlerConstants.PASSWORD_TYPE, WSConstants.PW_TEXT);
		outProps.put(WSHandlerConstants.PW_CALLBACK_CLASS, WsClinetAuthHandler.class.getName());
		ArrayList list = new ArrayList();
		// Add CXF security verification interceptor, required
		list.add(new SAAJOutInterceptor());
		list.add(new WSS4JOutInterceptor(outProps));
		endpoint.getOutInterceptors().addAll(list);
		/ / access connections: http://localhost:8080/services/helloService2? wsdl
		returnendpoint; }}Copy the code
package com.liuy.ws.server;

import javax.xml.namespace.QName;
import javax.xml.soap.*;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;
import javax.xml.ws.soap.SOAPFaultException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

public class SecurityHandler implements SOAPHandler<SOAPMessageContext> {
 
	@Override
	public boolean handleMessage(SOAPMessageContext messageContext) {
		// TODO Auto-generated method stub
		System.out.println("To handle SOAP message...");
		boolean responseFlag = (Boolean) messageContext
				.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
 
		// for response message only, true for outbound messages, false for
		// inbound
		System.out.println("responseFlag=" + responseFlag);
		if(! responseFlag) { SOAPMessage soapMessage = messageContext.getMessage();try {
				 SOAPEnvelope soapEnvelope = soapMessage.getSOAPPart()
				 .getEnvelope();
				 SOAPHeader soapHeader = soapEnvelope.getHeader();
				 if (null == soapHeader) {
					 System.out.println("No SOAP message header");
					 generateSOAPFault(soapMessage, "No SOAP message header");
					 return false;
				 }
				 Iterator iterator = soapHeader
				 .extractHeaderElements(SOAPConstants.URI_SOAP_ACTOR_NEXT);
				 if (null == iterator || null == iterator.next()) {
					 System.out.println("No header block for role next.");
					 generateSOAPFault(soapMessage,
					 "No header block for role next.");
					 return false;
				 }

				 Node next = (Node) iterator.next();
				 String value = (next == null)?null : next.getValue();
				 if (null == value) {
					 System.out
					 .println("No authentication info in header block.");
					 generateSOAPFault(soapMessage,
					 "No authentication info in header block.");
					 return false;
				 }

				 // As long as there is a header, it can be accessed
				 return true;
			
			 } catch (Exception e) {
			 	 // TODO: handle exceptione.printStackTrace(); }}return true;
	}
 
	private void generateSOAPFault(SOAPMessage soapMessage, String reason) {
		// TODO Auto-generated method stub
		try {
			SOAPBody soapBody = soapMessage.getSOAPBody();
			SOAPFault soapFault = soapBody.getFault();
			soapFault.setFaultString(reason);
			throw new SOAPFaultException(soapFault);
		} catch (SOAPException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
 
	@Override
	public boolean handleFault(SOAPMessageContext context) {
		// TODO Auto-generated method stub
		return false;
	}
 
	@Override
	public void close(MessageContext context) {
		// TODO Auto-generated method stub
 
	}
 
	@Override
	public Set<QName> getHeaders() {
		// TODO Auto-generated method stub
		HashSet<QName> headers = new HashSet<QName>();
		QName securityHeader = new QName(
				"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"."Security");
		headers.add(securityHeader);
		QName addressingHeader = new QName(
				"http://www.w3.org/2005/08/addressing"."To");
		headers.add(addressingHeader);
		returnheaders; }}Copy the code
package com.liuy.ws.server;

import org.apache.wss4j.common.ext.WSPasswordCallback;

import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import java.io.IOException;

/**
 * 测试用
 */
public class WsClinetAuthHandler implements CallbackHandler {

   @Override
    public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
       for (int i = 0; i < callbacks.length; i++) {
           WSPasswordCallback pc = (WSPasswordCallback) callbacks[i];
           System.out.println("identifier: " + pc.getIdentifier());
           pc.setPassword("111111");/ / password
           pc.setIdentifier("222222"); }}}Copy the code

There is also a config file:

  1. handle-chain.xml

The path of the configuration file must be mentioned here. I have referred to some previous blog posts, but there is no explicit description of how to configure the path of this XML file. I personally tested it. In the Spring Boot2 framework, it is possible to put it in the Resources directory and use/as the root path beginning in the annotation of the implementation class, @handlerchain (file = “/ handle-chchain.xml “) so that no errors are reported. If file = “handle-chain-xml” is used directly, you need to put the file in the directory where the implementation class resides during packaging; otherwise, the file will not be found.

<? xml version="1.0" encoding="UTF-8" standalone="yes"? ><javaee:handler-chains
        xmlns:javaee="http://java.sun.com/xml/ns/javaee"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <javaee:handler-chain>
        <javaee:handler>
            <javaee:handler-class>com.liuy.ws.server.SecurityHandler</javaee:handler-class>
        </javaee:handler>
    </javaee:handler-chain>
</javaee:handler-chains>
Copy the code

Iv. Client code

package com.liuy.ws.client;

import com.alibaba.fastjson.JSONObject;
import org.apache.cxf.endpoint.Client;
import org.apache.cxf.jaxws.endpoint.dynamic.JaxWsDynamicClientFactory;
import org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor;
import org.apache.ws.security.WSConstants;
import org.apache.ws.security.handler.WSHandlerConstants;

import javax.xml.namespace.QName;
import javax.xml.soap.SOAPElement;
import java.util.HashMap;
import java.util.Map;

public class ClientDemo {

    public static void main(String[] args) {
        JaxWsDynamicClientFactory dcf = JaxWsDynamicClientFactory.newInstance();

// Client client = dcf.createClient("http://localhost:8082/services/helloService? wsdl");
        Client client = dcf.createClient("http://localhost:8082/services/helloService2? wsdl");
        
// If the user name and password are not authenticated, ↓ can be omitted
        Map<String.Object> props = new HashMap<String.Object> (); props.put(WSHandlerConstants.ACTION, WSHandlerConstants.USERNAME_TOKEN);// Password type Plain text :PasswordText Ciphertext: PasswordDigest
        props.put(WSHandlerConstants.PASSWORD_TYPE, WSConstants.PW_TEXT);
        / / user name
        props.put(WSHandlerConstants.USER, "admin");
        // Pass the class name of PasswordHandler to the server and use PasswordHandler to configure the username and password
        props.put(WSHandlerConstants.PW_CALLBACK_CLASS, PasswordHandler.class.getName());
        WSS4JOutInterceptor wssOut = new WSS4JOutInterceptor(props);
        client.getOutInterceptors().add(wssOut);
// If the user name and password are not authenticated, you can omit ↑
        
        Object[] objects;

        JSONObject input = new JSONObject();
        input.put( "name"."Zhang" );
        input.put( "Age"."18" );
        input.put( "Gender"."LGBT" );
        String inputStr = input.toString();

        try {
            // This is used to specify the server interface's targetNamespace. If the client interface package path is the same as the server's targetNamespace, you can pass in the method name string without using QName.
            QName name = new QName("http://service.ws.liuy.com/"."getUser");
            objects = client.invoke(name, inputStr);
// objects = client.invoke("getUser", inputStr);
            String result = String.valueOf(objects[0]);
            System.out.println(result.length());
            System.out.println(result);

        } catch (Exception e) {
            // TODO Auto-generated catch blocke.printStackTrace(); }}}Copy the code
package com.liuy.ws.client;

import org.apache.wss4j.common.ext.WSPasswordCallback;

import javax.security.auth.callback.CallbackHandler;

public class PasswordHandler implements CallbackHandler {

    @Override
    public void handle(javax.security.auth.callback.Callback[] callbacks) {  
        for (int i = 0; i < callbacks.length; i++) {  
            WSPasswordCallback pc = (WSPasswordCallback)callbacks[i];
            pc.setPassword("111111");
            pc.setIdentifier("222222"); }}}Copy the code

conclusion

Instead of too many languages, it’s more intuitive to just run through the code. If anyone finds any mistakes or omissions in this post, please point them out.

Afterword.

At work, a colleague encountered an error with a long string. The error message is as follows:

Severe: Servlet service ()for servlet [XXMobileAction] in context with path [/xxapp] threw exception [Request processing failed; nested exception is javax.xml.ws.soap.SOAPFaultException: Error reading XMLStreamReader.] with root cause
com.ctc.wstx.exc.WstxEOFException: Unexpected EOF in prolog
 at [row,col {unknown-source}]: [1.0]
Copy the code

However, the framework used by my colleague is the old framework of Spring + Struts2. I did not find this error in the demo mentioned in this blog post.

This post borrows from many other bloggers and is a stitch freak, but also adds some of my own. Wen Dao has successively, the skill industry has specialized, thanks to the knowledge output of each elder. (Disdain for those who don’t know…)