第一天里说了如何用jax-ws去结合ssh框架。
在今天的教程中将会向大家详细讲述一个ws-security中的一个传统的”基于handler”来认证客户端传来的用户名密码的webservice.
客户端传过来一对用户名和密码,服务端进行认证。
当然,我们此处说这个用户名和密码的传送,那可不是用下面的这种形式来传送的哦: http://xxx.do?username=xxx&password=xxx。
我们这个用户名和密码是带在soap报文中的, jax-ws用一个handler专门用于处理soap报文的。
package ctsjavacoe.ws.fromjava; import java.util.*; import javax.servlet.http.HttpServletRequest; import javax.xml.namespace.QName; import javax.xml.soap.*; import javax.xml.ws.handler.MessageContext; import javax.xml.ws.handler.soap.*; import javax.xml.ws.soap.SOAPFaultException; import org.apache.cxf.transport.http.*; public class AuthValidationHandler implements SOAPHandler<SOAPMessageContext> { public Set<QName> getHeaders() { // TODO Auto-generated method stub return null; } public void close(MessageContext context) { } public boolean handleFault(SOAPMessageContext context) { return false; } public boolean handleMessage(SOAPMessageContext context) { HttpServletRequest request = (HttpServletRequest) context .get(AbstractHTTPDestination.HTTP_REQUEST); // if (request != null) { System.out.println("Client IP:" + request.getRemoteAddr()); } else { System.out.println("get client ip is null>>>>>>>>>"); } Boolean outbound = (Boolean) context .get(MessageContext.MESSAGE_OUTBOUND_PROPERTY); if (!outbound.booleanValue()) { SOAPMessage soapMessage = context.getMessage(); try { SOAPEnvelope soapEnvelope = soapMessage.getSOAPPart() .getEnvelope(); SOAPHeader soapHeader = soapEnvelope.getHeader(); if (soapHeader == null) generateSoapFault(soapMessage, "No Message Header..."); Iterator it = soapHeader .extractHeaderElements(SOAPConstants.URI_SOAP_1_2_ROLE_NEXT); if (it == null || !it.hasNext()) generateSoapFault(soapMessage, "No Header block for role next"); Node node = (Node) it.next(); String value = node == null ? null : node.getValue(); if (value == null) generateSoapFault(soapMessage, "No authation info in header blocks"); String[] infos = value.split("&"); return authValidate(infos[0], infos[1]); } catch (SOAPException e) { e.printStackTrace(); } } return false; } private boolean authValidate(String userName, String password) { if (userName == null || password == null) { return false; } if ("admin".equals(userName) && "admin".equals(password)) { return true; } return false; } private void generateSoapFault(SOAPMessage soapMessage, String reasion) { try { SOAPBody soapBody = soapMessage.getSOAPBody(); SOAPFault soapFault = soapBody.getFault(); if (soapFault == null) { soapFault = soapBody.addFault(); } soapFault.setFaultString(reasion); throw new SOAPFaultException(soapFault); } catch (SOAPException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } |
上面这段代码看似很长,其实逻辑很简单,注意:
publicboolean handleMessage(SOAPMessageContext context)这个方法。
Handler会先检查,这个soap是in还是out,当然,对于我们来说要验证客户端提交上来的用户名和密码,因该是in。
因此,如果这个soap是in,并且含有soapheader(我们的用户名密码是含在soap header中的,可不是通过url以http://xxx.xxx.xxx/xxx.do?username=xxx&password=xxx这样的形式传输的哦,这样做是彻头彻尾的作弊,不对的作法,和掩耳盗铃没啥区别),并且含有soap header,这个soap header中会提取出“username&password”这样的一个字符串,然后进行用户名和密码的比对与校验。
我们有了Handler类,还需要有一个handler的xml文件,对该类进行描述,这个xml文件和handler类放在同一层java的package下,该XML名为:handlers.xml,内容如下:
<?xml version="1.0" encoding="UTF-8"?> <handler-chains xmlns="http://java.sun.com/xml/ns/javaee"> <handler-chain> <handler> <handler-name>authHandler</handler-name> <handler-class> ctsjavacoe.ws.fromjava.AuthValidationHandler </handler-class> </handler> </handler-chain> </handler-chains> |
package ctsjavacoe.ws.fromjava; import javax.jws.WebMethod; import javax.jws.WebService; import javax.jws.soap.SOAPBinding; import javax.jws.soap.SOAPBinding.Style; @WebService @SOAPBinding(style = Style.RPC) public interface AuthorServer { @WebMethod public String sayHello(String name); } |
package ctsjavacoe.ws.fromjava; import javax.jws.HandlerChain; import javax.jws.WebService; @WebService(endpointInterface = "ctsjavacoe.ws.fromjava.AuthorServer") @HandlerChain(file = "handlers.xml") public class AuthorServerImpl implements AuthorServer { public String sayHello(String name) { return "Hello: " + name; } } |
核心是这一行:@HandlerChain(file= "handlers.xml")。
把它编译成webservice,然后发布工程到tomcat中去吧。
得到wsdl: http://localhost:8080/JaxWSSample/AuthorServerService?wsdl
我们用SOAPUI来调用这个webservice试试效果:
大家可以看到入到的output是:<faultstring>No Header blockfor role next</faultstring>
根据wsdl先得到相关的stub。
package ctsjavacoe.ws.fromjava; import java.util.Set; 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; public class ClientAuthenticationHandler implements SOAPHandler<SOAPMessageContext> { public Set<QName> getHeaders() { // TODO Auto-generated method stub return null; } public void close(MessageContext arg0) { // TODO Auto-generated method stub } public boolean handleFault(SOAPMessageContext arg0) { // TODO Auto-generated method stub return false; } public boolean handleMessage(SOAPMessageContext ctx) { Boolean request_p = (Boolean) ctx .get(MessageContext.MESSAGE_OUTBOUND_PROPERTY); if (request_p) { try { SOAPMessage msg = ctx.getMessage(); SOAPEnvelope env = msg.getSOAPPart().getEnvelope(); SOAPHeader hdr = env.getHeader(); if (hdr == null) hdr = env.addHeader(); QName qname_user = new QName("http://fromjava.ws.ctsjavacoe/", "AuthorServerImplService"); SOAPHeaderElement helem_user = hdr.addHeaderElement(qname_user); helem_user.setActor(SOAPConstants.URI_SOAP_1_2_ROLE_NEXT); helem_user.addTextNode("admin&admin1"); msg.saveChanges(); //msg.writeTo(System.out); return true; } catch (Exception e) { e.printStackTrace(); } } return false; } } |
该handler类主要的核心方法为:
public booleanhandleMessage(SOAPMessageContext ctx)方法
该方法中需要注意的是:
QName qname_user = new QName("http://fromjava.ws.ctsjavacoe/", "AuthorServerImplService"); SOAPHeaderElement helem_user = hdr.addHeaderElement(qname_user); helem_user.setActor(SOAPConstants.URI_SOAP_1_2_ROLE_NEXT); helem_user.addTextNode("admin&admin1"); msg.saveChanges(); |
我们通过上述的语句就已经知道我们会在客户端传过去一对用户名与密码,它们是:
username=admin
password=admin1
package ctsjavacoe.ws.fromjava; import java.util.*; import javax.xml.ws.handler.Handler; import javax.xml.ws.handler.HandlerResolver; import javax.xml.ws.handler.PortInfo; public class AuthorServerClient { public static void main(String[] args) { AuthorServerImplService service = new AuthorServerImplService(); service.setHandlerResolver(new HandlerResolver() { public List<Handler> getHandlerChain(PortInfo arg0) { List<Handler> handlerList = new ArrayList<Handler>(); handlerList.add(new ClientAuthenticationHandler()); return handlerList; } }); AuthorServer authorService = service.getAuthorServerImplPort(); String msg = authorService.sayHello("MK"); System.out.println("rtn msg=====" + msg); } } |
运行后我们得到了如下的输出:
出错了?为什么?
我们回过头来看两个handler,一个是server端的handler,其中:
if ("admin".equals(userName) && "admin".equals(password)) { return true; } |
一个是client的handler类,其中:
helem_user.addTextNode("admin&admin1"); |
啊。。。因为server端的密码要求是”admin”不是”admin1”,于是我们把client端的密码也改成”admin”,再次运行我们的客户端,得到输出: