使用.net调用xfire发布webservice的统一安全认证
在实际的项目中,服务端采用java框架,客户端采用C#的.net框架,客户端通过访问Xfire的java WebSevcie服务来实现与客户端的非实时数据交换;
如何对客户端访问的WebService进行授权认证,是必须要考虑的问题,对客户端个访问的每个服务都认证太繁琐,是否可以采用对业务无侵入的外挂式认证呢?是我们必须要考虑的问题?
本文采用对登录方法进行SoapHeader认证,并在服务端记录客户端的认证的状态(服务端的安全处理Handler是有状态的),在认证有效的时间内,认证过的客户端访问其他服务,无需再进行认证。
该方案虽然存在一定的严密性缺陷(小权限访问多接口),但是在实际的应用中,却不失为一个性价比很好的解决方案;
1. 使用wsdl.exe工具生成.net访问Xfire服务的代理类,指令如下:
在Doc控制台执行如下指令:
wsdl /language:c# /sharetypes /n:vvdcmonitor.vvdcm.module /out:C:\Users\Administrator\Desktop\vvmc_test.cs http://192.168.1.126:88//service/Test?wsdl
2. 服务器上添加自定义的验证方法
当web服务器启动的时候,比如tomcat启动的时候,该类将被实列化,生命周期直到tomcat关闭的时候结束
代码如下:
package org.ServiceAuthentication;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import org.codehaus.xfire.MessageContext;
import org.codehaus.xfire.fault.XFireFault;
import org.codehaus.xfire.handler.AbstractHandler;
import org.codehaus.xfire.transport.http.XFireServletController;
import org.jdom.Element;
import org.jdom.Namespace;
/**
* 认证拦截和缓存
*
* @authorzhx
*
*/
publicclassAuthenticationHandler extends AbstractHandler implements Runnable {
//定义hashMap 存储用户验证信息,key经过验证的用户IP,或者客户端特定信息
private HashMap<String, ClientAuthenticationInfo> clientAuthenticationInfoMap =new HashMap<String, ClientAuthenticationInfo>();
privatefinalstatic NamespaceTOKEN_NS = Namespace.getNamespace("http://tempuri.org/");
public AuthenticationHandler() {
//该线程检查用户登录是否超时
new Thread(this).start();
}
/**
* 认证拦截
* 仅登录方法增加认证,所有必须登录方法调用成功后,其他方法才可以调用
*/
publicvoid invoke(MessageContext cfx)throws Exception {
//为简化变更时,对WebServcies代理桩的修改工作量,仅仅登录函数增加 SoapHeader,其他函数并不增加
//假如clientAuthenticationInfoMap中存在用户验证信息,则直接跳出该函数,不再进行验证
if (clientAuthenticationInfoMap.containsKey(XFireServletController.getRequest().getRemoteAddr())){
return;
}
//得到客户soap header对象
Element header=cfx.getInMessage().getHeader();
if ( header==null) {//为简化变更时,对WebServcies代理桩的修改工作量,仅仅登录函数增加 SoapHeader,其他函数并不增加;
thrownew org.codehaus.xfire.fault.XFireFault("您超过1小时没有进行任何操作,您的认证信息已经失效,需要对您进行重新身份认证,请重新登录!",org.codehaus.xfire.fault.XFireFault.SENDER);
}
// 得到认证令牌
Element token = header.getChild("AuthenticationToken",TOKEN_NS);
if (token ==null) {
thrownew org.codehaus.xfire.fault.XFireFault("对服务的访问,必须包含认证信息!",org.codehaus.xfire.fault.XFireFault.SENDER);
}
Element Username = token.getChild("Username",TOKEN_NS);
Element password = token.getChild("Password",TOKEN_NS);
if (Username ==null || password ==null) {
thrownew XFireFault("AuthenticationToken Error,name or password is null", XFireFault.SENDER);
}
String nameValue = Username.getValue();
String passwordValue = password.getValue();
if (nameValue ==null||passwordValue==null) {
thrownew XFireFault("name or password's value is null.", XFireFault.SENDER);
}
try {
// 进行身份验证 ,只有abcd@1234的用户为授权用户
if (checkLogin(nameValue,passwordValue)){//
// 这语句不显示
System.out.println("身份验证通过");
//通过身份认证记录用户认证信息
ClientAuthenticationInfo clientLoginInfo=new ClientAuthenticationInfo();
clientLoginInfo.setClientIpAddr(XFireServletController.getRequest().getRemoteAddr());
clientLoginInfo.setLoginTime(new Date());
clientLoginInfo.setLastRequestServerTime(new Date());
clientAuthenticationInfoMap.put(clientLoginInfo.getClientIpAddr(),clientLoginInfo);
}
else{
thrownew Exception();
}
} catch (Exception e) {
thrownew org.codehaus.xfire.fault.XFireFault("对访问服务时,提供的用户名或密码无效",org.codehaus.xfire.fault.XFireFault.SENDER);
}
}
/**
* 检测用户登录认证
* @param userName
* @param password
* @return
*/
privateboolean checkLogin(String userName,String password){
if (userName.equals("abcd") && password.equals("1234")){
returntrue;
}
returnfalse;
}
/**
* 客户端登录信息
* @authorzhx
*
*/
privateclass ClientAuthenticationInfo{
//登录客户端的IP
private StringclientIpAddr="";
public String getClientIpAddr() {
returnclientIpAddr;
}
publicvoid setClientIpAddr(String clientIpAddr) {
this.clientIpAddr = clientIpAddr;
}
//客户端登录时间
private DateloginTime=new Date();
public DategetLoginTime() {
returnloginTime;
}
publicvoid setLoginTime(Date loginTime) {
this.loginTime = loginTime;
}
//客户端最后访问时间
private DatelastRequestServerTime=new Date();
public Date getLastRequestServerTime() {
returnlastRequestServerTime;
}
publicvoid setLastRequestServerTime(Date lastRequestServerTime) {
this.lastRequestServerTime = lastRequestServerTime;
}
/**
* 检测用户认证信息是否超时
* @return
*/
publicboolean checkLoginInfo(){
Date now = new Date();
long time = now.getTime()-this.getLastRequestServerTime().getTime();
if (time >authenticationTimeOut) {
returnfalse;
}
returntrue;
}
}
/**
* 启动线程定时检测
*/
publicvoid run() {
checkClientAuthenticationInfo();
}
/**
* 认证超时时间
*/
privatelongauthenticationTimeOut=1000*60*60;//最后一次请求后1小时,认证失效;
/**
* 检测客户端认证信息是否失效
* 认证通过的客户端IP,最后访问中心服务时间,超过设定的阀值时,认证信息将失效,必须重新登录并认证
*/
publicsynchronizedvoid checkClientAuthenticationInfo() {
while (true) {
try {
/**
*
* **/
for (Iterator<String> keys =clientAuthenticationInfoMap.keySet().iterator(); keys.hasNext();) {
String key = keys.next();
ClientAuthenticationInfo clientInfo=clientAuthenticationInfoMap.get(key);
if (!clientInfo.checkLoginInfo()) {
synchronized (clientAuthenticationInfoMap) {
clientAuthenticationInfoMap.remove(key);
}
}
}
//每10秒钟进行一次用户登录信息检测
Thread.sleep(10000);
} catch (InterruptedException ex) {
System.out.println(ex.getMessage());
}
}
}
}
3. 服务器services.xml(xFire的服务配置文件)
配置如下
<service xmlns="http://xfire.codehaus.org/config/1.0">
<name>Test</name>
<namespace>http://tempuri.org/</namespace>
<serviceClass>webservice.ITest</serviceClass>
<implementationClass>webservice.Test</implementationClass>
<inHandlers>
<handler handlerClass="org.ServiceAuthentication.AuthenticationHandler" ></handler>
</inHandlers>
<style>wrapped</style>
<use>literal</use>
<scope>application</scope>
</service>
4. .net客户端调用的时候添加一个类
客户端采用.net调用,添加客户端的AuthenticationToken
具体代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Services;
using System.Web.Services.Protocols;
namespace myWindowsFormsApp
{
[System.Serializable]
[System.Xml.Serialization.XmlType(Namespace ="http://tempuri.org/")]
[System.Xml.Serialization.XmlRoot(Namespace ="http://tempuri.org/", IsNullable = false)]
public class AuthenticationToken : SoapHeader
{
public string Username = null;
public string Password = null;
}
}
5. 然后在生成的代理类里面添加以下代码
public AuthenticationToken SoapHeader = new AuthenticationToken();
6. 在生成的代理类的调用方法上再添加如下代码
[SoapHeader("SoapHeader")]
在调用该方法时候,验证信息将被加入soap header中。
7. 客户端调用实现
8. using System;
9. using System.Collections.Generic;
10. using System.ComponentModel;
11. using System.Data;
12. using System.Drawing;
13. using System.Linq;
14. using System.Text;
15. using System.Windows.Forms;
16. using System.ServiceModel.Description;
17. using vvdcmonitor.vvdcm.module;
18.
19. namespace myWindowsFormsApp
20. {
21. public partial class MyForm : Form
22. {
23. public MyForm()
24. {
25. InitializeComponent();
26. }
27.
28. private void button1_Click(object sender, EventArgs e)
29. {
30. try
31. {
32. Test client =new Test();
33. client.SoapHeader.Password = "1234";
34. client.SoapHeader.Username = "abcd";
35. MessageBox.Show("访问成功,返回:"+client.test(34).ToString());
36. }catch(Exception ex){
37. MessageBox.Show("访问失败,原因:"+ex.Message);
38. }
39.
40. }
41. }
42. }
43.
44. 其他替代方法:
其实Java与C#通过Webservice通信的安全控制还是可以用session来控制的,只是客户端需要保存一cookie,C#提供了一个cookieContainer类来为我们保存Session。