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:
- CxfConfig.java
- SecurityHandler.java
- 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:
- 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…)