Web Service學習-CXF開發Web Service的許可權控制(二)
Web Service如何進行許可權控制?
解決思路:伺服器端要求input訊息總是攜帶有使用者名稱,密碼資訊,如果沒有使用者名稱和密碼資訊,直接拒絕呼叫
解決方案:攔截器
為了讓程式設計師能訪問,並修改CXF框架所生成的SOAP訊息,CXF提供了攔截器
CXF(Celtix +XFire)說明:
如果不用CXF等框架,SOAP訊息的生成,解析都是由程式設計師負責。無論是新增使用者名稱,密碼資訊還是提取使用者名稱,密碼資訊,都可由程式設計師程式碼完成。
如果使用CXF等框架,SOAP訊息的生成,解析都是由CXF等框架來完成。
總的來說,CXF對釋出WebService進行了封裝,簡化了我們的操作。
攔截器:
服務端新增攔截器:
1,獲取Endpoint的publish的方法返回值。
2,呼叫該物件的getInInterceptors,getOutInterceptors方法來獲取In,Out攔截器列表,接下來就可以新增攔截器了
package com.tgb.client; import javax.xml.ws.Endpoint; import org.apache.cxf.interceptor.LoggingInInterceptor; import org.apache.cxf.interceptor.LoggingOutInterceptor; import org.apache.cxf.jaxws.EndpointImpl; import com.tgb.service.HelloWorld; import com.tgb.service.impl.HelloWorldImpl; public class ServerMain { public static void main(String[] args){ HelloWorld hw=new HelloWorldImpl(); //呼叫endpoint的publish方法,來發布web service // Endpoint.publish("http://192.168.24.215:8889/hjy",hw); EndpointImpl ep=(EndpointImpl)Endpoint.publish("http://192.168.24.215:8899/hjy",hw); //新增In攔截器 ep.getInInterceptors().add(new LoggingInInterceptor()); //新增Out攔截器 ep.getOutInterceptors().add(new LoggingOutInterceptor()); System.out.println("Web Service暴露成功"); } }
輸出內容:
客戶端新增攔截器:
1,新增相應的CXF的jar包
2,呼叫ClientProxy的getClient方法,呼叫該方法以遠端Web Service的代理為引數
3,呼叫Client物件的getInInterceptors,getOutInterceptors方法來獲取In,Out攔截器列表,接下來就可以新增攔截器了
package hjy; import java.util.List; import org.apache.cxf.endpoint.Client; import org.apache.cxf.frontend.ClientProxy; import org.apache.cxf.interceptor.LoggingInInterceptor; import org.apache.cxf.interceptor.LoggingOutInterceptor; import com.tgb.service.Cat; import com.tgb.service.HelloWorld; import com.tgb.service.User; import com.tgb.service.impl.HelloWorldImpl; public class ClientMain { public static void main(String[] args){ HelloWorldImpl factory=new HelloWorldImpl(); //此處返回的只是遠端Web Service的代理 HelloWorld hw=factory.getHelloWorldImplPort(); Client client=ClientProxy.getClient(hw); client.getInInterceptors().add(new LoggingInInterceptor()); client.getOutInterceptors().add(new LoggingOutInterceptor()); System.out.println(hw.sayHi("hejingyuan")); System.out.println("--------------------------"); User user=new User(); user.setId(20); user.setName("孫悟空"); user.setPass("111"); user.setAddress("花果山"); List<Cat> cats=hw.getCatsByUser(user); for(Cat cat:cats){ System.out.println(cat.getName()); } System.out.println("--------------------------"); System.out.println(hw.getAllCats().getEntry().get(0).getKey()); System.out.println(hw.getAllCats().getEntry().get(0).getValue().getName()); } }
列印內容為:
自定義攔截器
實現效果:當輸入使用者名稱密碼時,才可以呼叫我們的服務。即我們需要在服務端新增輸入攔截,在客戶端新增輸出攔截
自定義攔截器,需要實現Interceptor介面,實際上,我們一般會繼承AbstractPhaseInterceptor
服務端程式碼:
package com.tgb.client;
import javax.xml.ws.Endpoint;
import org.apache.cxf.interceptor.LoggingInInterceptor;
import org.apache.cxf.interceptor.LoggingOutInterceptor;
import org.apache.cxf.jaxws.EndpointImpl;
import com.tgb.auth.AuthInterceptor;
import com.tgb.service.HelloWorld;
import com.tgb.service.impl.HelloWorldImpl;
public class ServerMain {
public static void main(String[] args){
HelloWorld hw=new HelloWorldImpl();
//呼叫endpoint的publish方法,來發布web service
// Endpoint.publish("http://192.168.24.215:8889/hjy",hw);
EndpointImpl ep=(EndpointImpl)Endpoint.publish("http://192.168.24.215:8891/hjy",hw);
//新增In攔截器,該AuthInterceptor就會負責檢查使用者名稱,密碼是否正確
ep.getInInterceptors().add(new AuthInterceptor());
//新增Out攔截器
// ep.getOutInterceptors().add(new LoggingOutInterceptor());
System.out.println("Web Service暴露成功");
}
}
AuthInterceptor :
package com.tgb.auth;
import java.util.List;
import org.apache.cxf.binding.soap.SoapMessage;
import org.apache.cxf.headers.Header;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
//通過PhaseInterceptor,可以指定攔截器在哪個階段起作用
public class AuthInterceptor extends AbstractPhaseInterceptor<SoapMessage>{
//由於AbstractPhaseInterceptor無無引數構造器,使用繼承的方式,需要顯示呼叫父類有引數的構造器
public AuthInterceptor(){
//super表示顯示呼叫父類有引數的構造器
//顯示呼叫父類構造器之後,程式將不會隱式呼叫父類無引數的構造器
super(Phase.PRE_INVOKE);//該攔截器將會呼叫之前攔截SOAP訊息
}
//實現自己的攔截器時,需要實現handleMessage方法。
//handleMessage方法中的形參就是被攔截到的Soap訊息
//一旦程式獲取了SOAP訊息,剩下的事情就可以解析SOAP訊息或修改SOAP訊息
@Override
public void handleMessage(SoapMessage msg) throws Fault {
System.out.println("-------"+msg);
//從這裡可以看出,我們已經攔截到了SOAP訊息
//得到SOAP訊息所有Header
List<Header> headers=msg.getHeaders();
//如果沒有Header
if(headers==null||headers.size()<1){
throw new Fault(new IllegalArgumentException("根本沒有Header,不能呼叫"));
}
//假如要求第一個Header攜帶了使用者名稱,密碼資訊
Header firstHeader=headers.get(0);
Element ele=(Element)firstHeader.getObject();
NodeList userIds=ele.getElementsByTagName("userId");
NodeList userPasses=ele.getElementsByTagName("userPass");
if(userIds.getLength()!=1){
throw new Fault(new IllegalArgumentException("使用者名稱的格式不正確!"));
}
if(userPasses.getLength()!=1){
throw new Fault(new IllegalArgumentException("密碼的格式不正確!"));
}
//得到第一個userId元素裡的文字內容,以該內容作為使用者名稱字
String userId=userIds.item(0).getTextContent();
String userPass=userPasses.item(0).getTextContent();
//實際專案中,應該去查詢資料庫,該使用者名稱密碼是否被授權呼叫web service
if(!userId.equals("hejingyuan") || !userPass.equals("hjy")){
throw new Fault(new IllegalArgumentException("使用者名稱密碼不正確!"));
}
}
}
客戶端程式碼:
package hjy;
import java.util.List;
import org.apache.cxf.endpoint.Client;
import org.apache.cxf.frontend.ClientProxy;
import com.tgb.auth.AddHeaderInterceptor;
import com.tgb.service.Cat;
import com.tgb.service.HelloWorld;
import com.tgb.service.User;
import com.tgb.service.impl.HelloWorldImpl;
public class ClientMain {
public static void main(String[] args){
HelloWorldImpl factory=new HelloWorldImpl();
//此處返回的只是遠端Web Service的代理
HelloWorld hw=factory.getHelloWorldImplPort();
Client client=ClientProxy.getClient(hw);
//引數為輸入的使用者名稱,密碼
client.getOutInterceptors().add(new AddHeaderInterceptor("hejingyuan","hjy"));
System.out.println(hw.sayHi("hejingyuan"));
System.out.println("--------------------------");
User user=new User();
user.setId(20);
user.setName("孫悟空");
user.setPass("111");
user.setAddress("花果山");
List<Cat> cats=hw.getCatsByUser(user);
for(Cat cat:cats){
System.out.println(cat.getName());
}
System.out.println("--------------------------");
System.out.println(hw.getAllCats().getEntry().get(0).getKey());
System.out.println(hw.getAllCats().getEntry().get(0).getValue().getName());
}
}
AddHeaderInterceptor:
package com.tgb.auth;
import java.util.List;
import javax.xml.namespace.QName;
import org.apache.cxf.binding.soap.SoapMessage;
import org.apache.cxf.headers.Header;
import org.apache.cxf.helpers.DOMUtils;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
public class AddHeaderInterceptor extends AbstractPhaseInterceptor<SoapMessage>{
private String userId;
private String userPass;
public AddHeaderInterceptor(String userId,String userPass){
super(Phase.PREPARE_SEND);//在準備傳送SOAP訊息時啟用該攔截器
this.userId=userId;
this.userPass=userPass;
}
@Override
public void handleMessage(SoapMessage msg) throws Fault {
List<Header> headers=msg.getHeaders();
//建立Document物件
Document doc=DOMUtils.createDocument();
Element ele=doc.createElement("authHeader");
//此處建立的元素應該按照伺服器那邊的要求
Element idEle=doc.createElement("userId");
idEle.setTextContent(userId);
Element passEle=doc.createElement("userPass");
passEle.setTextContent(userPass);
ele.appendChild(idEle);
ele.appendChild(passEle);
/**
* 上面程式碼生成了一個如下XML文件片段
* <authHeader>
* <userId>hejingyuan</userId>
* <userPass>hjy</userPass>
* </authHeader>
*/
//把ele元素包裝成Header,並新增到SOAP訊息的Header列表中
headers.add(new Header(new QName("hejingyuan"),ele));
}
}
啟動服務端的ServerMain的main函式,將服務釋出,然後啟動客戶端ClientMain的main函式去訪問服務端提供的服務。
使用者名稱密碼錯誤時:
使用者名稱密碼正確時:
總結:
許可權控制的實現方式為使用攔截器,對於攔截到的Soap訊息進行修改。
SOAP訊息:
根元素是Envolope
Header
預設情況下,Header元素不是強制出現的
Header元素由程式設計師控制新增,主要使用者攜帶一些額外的資訊,比如使用者名稱,密碼資訊
Body
如果呼叫正確,Body元素的內容應該遵守WSDL所要求的格式
如果呼叫錯誤,Body元素的內容就是Fault子元素