JAX-WS使用Handler实现简单的WebService权限验证
WebService如果涉及到安全保密或者使用权限的时候,WS-Security通常是最优选择。WS-Security (Web服务安全)
包含了关于如何在WebService消息上保证完整性和机密性的规约,如何将签名和加密头加入SOAP消息。
不过WS-Security也有一些性能上的损耗,在信息保密要求不是很高的情况下,可以通过在SOAPHeader中添加简单的校验信息实现。
具体思路是客户端调用需要认证的服务时,在SOAPHeader中添加授权信息(如用户名、密码或者序列号等)。
服务端收到请求,在SOAPHeader中校验授权信息,校验通过则执行请求,校验不通过则返回错误提示。
客户端发起请求在SOAPHeader中添加的授权数据格式如下
<auth xmlns="http://schemas.xmlsoap.org/soap/actor/next"> <username>admin</username> <password>admin</password> </auth>
服务端
服务端授权校验 Handler
import java.util.Iterator; import java.util.Set; import javax.xml.namespace.QName; import javax.xml.soap.SOAPBody; import javax.xml.soap.SOAPConstants; import javax.xml.soap.SOAPElement; import javax.xml.soap.SOAPEnvelope; import javax.xml.soap.SOAPException; import javax.xml.soap.SOAPFault; import javax.xml.soap.SOAPHeader; import javax.xml.soap.SOAPMessage; import javax.xml.ws.handler.MessageContext; import javax.xml.ws.handler.soap.SOAPHandler; import javax.xml.ws.handler.soap.SOAPMessageContext; import org.apache.cxf.interceptor.Fault; import org.w3c.dom.NodeList; /** * * @author */ public class JaxServerAuthValidateHeader implements SOAPHandler<SOAPMessageContext> { @Override public void close(MessageContext context) { } @Override public boolean handleFault(SOAPMessageContext context) { return true; } @Override public boolean handleMessage(SOAPMessageContext context) { // 判断消息是输入还是输出 boolean isRequest = (Boolean) context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY); SOAPMessage soapMessage = context.getMessage(); if (!isRequest) { SOAPHeader soapHeader = null; try { SOAPEnvelope soapEnv = soapMessage.getSOAPPart().getEnvelope(); soapHeader = soapEnv.getHeader(); } catch (SOAPException e) { throw new Fault(new Exception("服务器异常!")); } if (soapHeader == null) { validateFail(soapMessage, "无 Soap Header 头信息!"); return false; } // add an node named "auth" QName qname = new QName(SOAPConstants.URI_SOAP_ACTOR_NEXT, "auth"); Iterator<?> iterator = soapHeader.getChildElements(qname); SOAPElement auth = null; if (iterator.hasNext()) { // 获取auth auth = (SOAPElement) iterator.next(); } // 如果授权信息元素不存在,提示错误 if (auth == null) { validateFail(soapMessage, "无授权信息!"); return false; } NodeList nameList = auth.getElementsByTagName("username"); NodeList pwdList = auth.getElementsByTagName("password"); if (nameList == null || nameList.getLength() <= 0 || pwdList == null || pwdList.getLength() <= 0) { validateFail(soapMessage, "授权信息格式错误!"); return false; } String username = nameList.item(0).getTextContent(); String password = pwdList.item(0).getTextContent(); if (!"admin".equals(username) || !"admin".equals(password)) { validateFail(soapMessage, "授权信息格式错误!"); return false; } } System.out.println(isRequest ? "服务端响应:" : "服务端接收:"); System.out.println("\r\n"); return true; } @Override public Set<QName> getHeaders() { return null; } /** * 授权校验失败,在SOAPBody中添加SOAPFault * * @param message */ private void validateFail(SOAPMessage soapMessage, String faultString) { try { SOAPEnvelope envelop = soapMessage.getSOAPPart().getEnvelope(); envelop.getHeader().detachNode(); envelop.addHeader(); envelop.getBody().detachNode(); SOAPBody body = envelop.addBody(); SOAPFault fault = body.getFault(); if (fault == null) { fault = body.addFault(); } fault.setFaultString(faultString); soapMessage.saveChanges(); } catch (SOAPException e) { e.printStackTrace(); } } }
服务端Handler配置文件handler-chain.xml
<?xml version="1.0" encoding="UTF-8"?> <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.server.handler.JaxServerAuthValidateHeader</javaee:handler-class> </javaee:handler> </javaee:handler-chain> </javaee:handler-chains>
服务端的Service中添加Handler配置文件
import javax.jws.WebMethod; import javax.jws.WebParam; import javax.jws.WebResult; import javax.jws.WebService; import javax.xml.bind.annotation.XmlSeeAlso; import javax.xml.ws.RequestWrapper; import javax.xml.ws.ResponseWrapper; /** * This class was generated by Apache CXF 3.2.1 * 2017-12-01T14:12:00.085+08:00 * Generated source version: 3.2.1 * */ @WebService(targetNamespace = "http://tempuri.org/", name = "AExampleDemoWebService") @HandlerChain(file="handler-chain.xml") public interface AExampleDemoWebService { @WebMethod @WebResult(name = "single", targetNamespace = "") public java.lang.String querySingle( @WebParam(name = "single", targetNamespace = "http://tempuri.org/") java.lang.String single ); }
服务端的Service 实现类
import java.util.ArrayList; import java.util.List; import cn.evun.iwm.receive.soap.service.AExampleDemoWebService; import cn.evun.iwm.receive.soap.struc.sample.InputParam; import cn.evun.iwm.receive.soap.struc.sample.OutputParam; @javax.jws.WebService(serviceName = "aexampleDemoWebService", portName = "Sample", targetNamespace = "http://tempuri.org/", endpointInterface = "cn.soap.service.AExampleDemoWebService") public class AExampleDemoWebServiceImpl implements AExampleDemoWebService { @Override public String querySingle(String input) { return "success"; } }
客户端
客户端添加授权Handler
import java.io.IOException; import java.util.Set; import javax.xml.namespace.QName; import javax.xml.soap.SOAPConstants; import javax.xml.soap.SOAPElement; import javax.xml.soap.SOAPException; import javax.xml.soap.SOAPHeader; import javax.xml.soap.SOAPMessage; import javax.xml.ws.handler.MessageContext; import javax.xml.ws.handler.soap.SOAPHandler; import javax.xml.ws.handler.soap.SOAPMessageContext; public class JaxWsClientHandler implements SOAPHandler<SOAPMessageContext> { @Override public boolean handleMessage(SOAPMessageContext context) { // 判断消息是请求还是响应 Boolean isRequest = (Boolean) context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY); SOAPMessage soapMessage = context.getMessage(); if (isRequest) { try { SOAPHeader soapHeader = soapMessage.getSOAPHeader(); if (soapHeader == null) { soapHeader = soapMessage.getSOAPPart().getEnvelope().addHeader(); } // add an node named "auth" QName qname = new QName(SOAPConstants.URI_SOAP_ACTOR_NEXT, "auth"); SOAPElement auth = soapHeader.addChildElement(qname); SOAPElement name = auth.addChildElement("username"); name.addTextNode("admin"); SOAPElement password = auth.addChildElement("password"); password.addTextNode("admin"); soapMessage.saveChanges(); // tracking soapMessage.writeTo(System.out); } catch (SOAPException e) { System.err.println(e); } catch (IOException e) { System.err.println(e); } } return true; } @Override public boolean handleFault(SOAPMessageContext context) { return false; } @Override public void close(MessageContext context) { } @Override public Set<QName> getHeaders() { return null; } }
客户端Handler配置文件handler-chain.xml
<?xml version="1.0" encoding="UTF-8"?> <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.client.handler.JaxWsClientHandler</javaee:handler-class> </javaee:handler> </javaee:handler-chain> </javaee:handler-chains>
客户端的Service中添加Handler配置文件
import java.net.MalformedURLException; import java.net.URL; import javax.xml.namespace.QName; import javax.xml.ws.WebEndpoint; import javax.xml.ws.WebServiceClient; import javax.xml.ws.WebServiceFeature; import javax.xml.ws.Service; /** * This class was generated by Apache CXF 3.2.1 * 2017-12-01T14:12:00.117+08:00 * Generated source version: 3.2.1 * */ @WebServiceClient(name = "aexampleDemoWebService", wsdlLocation = "http://localhost:8090/service/Sample?wsdl", targetNamespace = "http://tempuri.org/") @HandlerChain(file="handler-chain.xml") public class AexampleDemoWebServiceSoap extends Service { public final static URL WSDL_LOCATION; public final static QName SERVICE = new QName("http://tempuri.org/", "aexampleDemoWebService"); public final static QName Sample = new QName("http://tempuri.org/", "Sample"); static { URL url = null; try { url = new URL("http://localhost:8090/service/Sample?wsdl"); } catch (MalformedURLException e) { java.util.logging.Logger.getLogger(AexampleDemoWebServiceSoap.class.getName()) .log(java.util.logging.Level.INFO, "Can not initialize the default wsdl from {0}", "http://localhost:8090/service/Sample?wsdl"); } WSDL_LOCATION = url; } public AexampleDemoWebServiceSoap(URL wsdlLocation) { super(wsdlLocation, SERVICE); } public AexampleDemoWebServiceSoap(URL wsdlLocation, QName serviceName) { super(wsdlLocation, serviceName); } public AexampleDemoWebServiceSoap() { super(WSDL_LOCATION, SERVICE); } public AexampleDemoWebServiceSoap(WebServiceFeature ... features) { super(WSDL_LOCATION, SERVICE, features); } public AexampleDemoWebServiceSoap(URL wsdlLocation, WebServiceFeature ... features) { super(wsdlLocation, SERVICE, features); } public AexampleDemoWebServiceSoap(URL wsdlLocation, QName serviceName, WebServiceFeature ... features) { super(wsdlLocation, serviceName, features); } /** * * @return * returns AExampleDemoWebService */ @WebEndpoint(name = "Sample") public AExampleDemoWebService getSample() { return super.getPort(Sample, AExampleDemoWebService.class); } /** * * @param features * A list of {@link javax.xml.ws.WebServiceFeature} to configure on the proxy.
* Supported features not in the <code>features</code> parameter will have their default values. * @return * returns AExampleDemoWebService */ @WebEndpoint(name = "Sample") public AExampleDemoWebService getSample(WebServiceFeature... features) { return super.getPort(Sample, AExampleDemoWebService.class, features); } }
客户端发起请求
QName SERVICE_NAME = new QName("http://tempuri.org/", "aexampleDemoWebService"); URL url = new URL("http://localhost:8090/service/Sample?wsdl"); AexampleDemoWebServiceSoap ss = new AexampleDemoWebServiceSoap(wsdlURL, SERVICE_NAME); AExampleDemoWebService port = ss.getSample(); port.querySingle("1111");
@HandlerChain 注解 替代方式
客户端 通过 HandlerReolver 代替 @HandlerChain 注解 导入 Handler 配置文件
handler-chain配置文件对所有的请求都添加授权验证信息,有些时候不是所有的请求都需要添加授权验证,HandlerResolver提供了在编程时添加Handler的方法,可以用HandlerResolver给需要授权的接口添加Handler。
QName SERVICE_NAME = new QName("http://tempuri.org/", "aexampleDemoWebService"); URL url = new URL("http://localhost:8090/service/Sample?wsdl"); AexampleDemoWebServiceSoap ss = new AexampleDemoWebServiceSoap(wsdlURL, SERVICE_NAME); //通过HandlerResolver添加Handler ss.setHandlerResolver(new HandlerResolver(){ @Override @SuppressWarnings("rawtypes") public List<Handler> getHandlerChain(PortInfo portInfo) { List<Handler> handlerChain = new ArrayList<Handler>(); handlerChain.add(new JaxWsClientHandler()); return handlerChain; } }); AExampleDemoWebService port = ss.getSample(); port.querySingle("2222");
服务端 @HandlerChain 注解替代
服务发布时服务端通过继承至 Endpoint 实现类 EndpointImpl 的 setHandlers 方法添加头部信息 代替 @HandlerChain 注解 导入 Handler 配置文件
import java.util.ArrayList; import java.util.List; import javax.xml.ws.Endpoint; import javax.xml.ws.handler.Handler; import org.apache.cxf.Bus; import org.apache.cxf.bus.spring.SpringBus; import org.apache.cxf.interceptor.Interceptor; import org.apache.cxf.jaxws.EndpointImpl; import org.apache.cxf.message.Message; import org.apache.cxf.transport.servlet.CXFServlet; import org.springframework.boot.context.embedded.ServletRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class CXFConfig { @Bean public ServletRegistrationBean dispatcherServlet() { return new ServletRegistrationBean(new CXFServlet(), "/service/*"); } @Bean(name = Bus.DEFAULT_BUS_ID) public SpringBus springBus() { return new SpringBus(); } @Bean public AExampleDemoWebServiceImpl aExampleDemoWebServiceImpl() { return new AExampleDemoWebServiceImpl(); } @Bean public Endpoint endpointWebServiceSampleImpl() { EndpointImpl endpoint = new EndpointImpl(springBus(), aExampleDemoWebServiceImpl()); // SOAPHandler 方式 @SuppressWarnings("rawtypes") List<Handler> handlers = new ArrayList<>(); handlers.add(new JaxServerAuthValidateHeader()); endpoint.setHandlers(handlers); endpoint.publish("/Sample"); return endpoint; } }
当使用替代方式添加头部信息的时候就不需要使用 @HandlerChain 注解