在.net 2.0中,在使用 remoting 的 TCP Channel, 用戶認證是安全性問題探討主題之一.本文將從兩個角度來探討用戶認證問題, 並提出一個問題來尋求你們的解決方法! html
1、兩個通道類的區別
Tcp Channel :
服務器端註冊通道
方式一:
(1)註冊一個通道
TcpChannel channel = new TcpChannel(8086);
ChannelServices.RegisterChannel(channel, true);
(2)註冊多個通道
因爲IChannel的ChannelName屬性和ChannelPriority屬性都是隻讀的,因此經過下面的方式。
IDictionary props = new Hashtable();
props["name"] = "channelName";
props["port"] = 8086;
IChannel channel = new TcpChannel(props, new BinaryClientFormatterSinkProvider(), new BinaryServerFormatterSinkProvider());
ChannelServices.RegisterChannel(channel, true); web
方式二:
(1)註冊一個通道
TcpServerChannel channel=new TcpServerChannel(8086);
ChannelServices.RegisterChannel(channel, true);
(2)註冊多個通道
IDictionary props = new Hashtable();
props["name"] = "channelName";
props["port"] = 8086;
IChannel channel = new TcpChannel(props, new BinaryServerFormatterSinkProvider());
ChannelServices.RegisterChannel(channel, true); 編程
(3)註冊帶客戶端驗證的通道
IAuthorizeRemotingConnection authorizeCallback = (IAuthorizeRemotingConnection)new AuthorizationModule ();
channel = new TcpServerChannel(props, new BinaryServerFormatterSinkProvider(), authorizeCallback);
ChannelServices.RegisterChannel(channel, true); 安全
客戶端註冊通道
針對服務器端註冊通道方式一的兩類狀況均採用下面的方式註冊便可:
TcpChannel channel = new TcpChannel();
ChannelServices.RegisterChannel(channel, true); 服務器
針對服務器端註冊通道方式二的採用下面的方式註冊便可:
(1)和(3)的對應
TcpClientChannel tcpClientChannel = new TcpClientChannel();
ChannelServices.RegisterChannel(tcpChannel, true);
(2)的對應
TcpClientChannel tcpClientChannel = new TcpClientChannel("channelName", new BinaryClientFormatterSinkProvider());
ChannelServices.RegisterChannel(tcpChannel, true);
從上面的演示能夠看出:
(1) TcpChannel類均可以用在客戶端和服務器端,它們都實現了IChannelReceiver, IChannelSender。
在客戶端使用時,通常採用
TcpChannel channel = new TcpChannel();
TcpChannel () 初始化 TcpChannel 類的新實例,僅激活客戶端信道,不激活服務器信道。
在服務器端使用時,通常採用
IChannel channel = new TcpChannel(props, new BinaryClientFormatterSinkProvider(), new BinaryServerFormatterSinkProvider());
能夠指定的配置屬性和接收器初始化 TcpChannel 類的新實例。IClientChannelSinkProvider 爲遠程處理消息從其流過的客戶端信道建立客戶端信道接收器。 IServerChannelSinkProvider 爲遠程處理消息從其流過的服務器信道建立服務器信道接收器。
(2)
TcpServerChannel 類用在客戶端, TcpClientChannel 類用在服務器端。(這句好象是廢話:) ) 網絡
區別:TcpChannel類是一個通用的信道類,在客戶端和服務器端均可以使用,使用起來很是方便。TcpServerChannel 類和TcpClientChannel 類須要分別使用,可是若是你要經過編程的方式使用.Net 2.0 中的客戶端驗證,那就要使用TcpServerChannel 來完成了。固然,也能夠經過配置文件的方式來完成對客戶端驗證。還有一點,使用TcpChannel類能夠在服務器指定客戶端信道接收器和服務器信道接收器,客戶端不用管,而TcpServerChannel 類和TcpClientChannel 類則分別指定本身的信道接收器。(不知道這句話是否正確?:)) app
2、下面就來從兩個角度來探討一下TCP Channel 用戶認證. tcp
方式一:採用編程的方式和配置文件的方式來完成對客戶端驗證。
先給出受權的類文件代碼:
程序集名稱爲AuthorizationAssemblyName,命名空間爲:AuthorizationNameSpace。 ide
class AuthorizationModule : IAuthorizeRemotingConnection
{
public bool IsConnectingEndPointAuthorized(System.Net.EndPoint endPoint)
{
//驗證IP地址代碼.....
return true;
}
public bool IsConnectingIdentityAuthorized(IIdentity identity)
{
//Windows用戶名identity.Name
//Windows用戶驗證代碼.....
return true;
}
}
函數
IAuthorizeRemotingConnection接口有兩個方法, IsConnectingEndPointAuthorized 獲取一個布爾值,該值指示客戶端的網絡地址是否已被受權鏈接至當前信道。IsConnectingIdentityAuthorized 獲取一個布爾值,該值指示客戶端的用戶標識是否已被受權鏈接至當前信道。
(1) 配置文件的方式:
服務器端的配置文件:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.runtime.remoting>
<application>
<service>
<wellknown mode="SingleCall" type="NameSpace.ClassName, AssemblyName" objectUri="server.rem" />
</service>
<channels>
<channel ref="tcp" secure="true" port="8086" impersonate="true" authorizationModule="AuthorizationNameSpace.AuthorizationClassName, AuthorizationAssemblyName, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</channels>
</application>
</system.runtime.remoting>
</configuration>
(2) 編程的方式:
IAuthorizeRemotingConnection authorizeCallback = (IAuthorizeRemotingConnection)new AuthorizationModule();
IChannel channel = new TcpServerChannel(props, new BinaryServerFormatterSinkProvider(), authorizeCallback);
ChannelServices.RegisterChannel(channel, true);
以上兩種驗證方式是針對IP地址和Windows用戶。只要客戶端調用遠程服務,就是會自動執行上面接口中兩個函數。雖然在必定程度上實現了安全性驗證,可是仍然不是咱們想要的結果。咱們但願的方式是:對於給定的服務(也就是遠程對象提供的方法),一小部分不須要受權,好比登陸驗證服務,大部分服務須要受權驗證,好比管理員刪除用戶;在要受權驗證時,採用的自動提交驗證信息的方式,好比刪除用戶,咱們但願是隻傳刪除用戶的ID,
即 DeleteUserById(string userId), 而不是連管理員的用戶密碼也一塊兒做爲參數傳過去,即DeleteUserById(string adminId,adminpwd,string userId)。那麼,下面就來探討一下解決這個問題的答案:在遠程處理中,能夠經過CallContext類得到。
方式二:在遠程處理中,能夠經過CallContext類得到方式來完成對客戶端驗證。
第一步,建立自定義的類 PrincipalStorage,封裝須要自動傳遞的消息。該對象須要序列化,且須要實現ILogicalThreadAffinative接口。
using System;
using System.Runtime.Remoting.Messaging;
using System.Security.Principal;
namespace Novelty.WinServices
{
public class PrincipalStorage : ILogicalThreadAffinative
{
private GenericPrincipal _currentGenericPrincipal;
/// <summary>
/// 構造函數
/// </summary>
/// <param name="currentGenericPrincipal"></param>
public PrincipalStorage(GenericPrincipal currentGenericPrincipal)
{
_currentGenericPrincipal = principal;
}
/// <summary>
/// 用戶對象的基本功能
/// </summary>
public GenericPrincipal CurrentGenericPrincipal
{
get
{
return _currentGenericPrincipal;
}
}
}
}
(2)客戶端建立該對象並使用CallContext.SetData方法附加到上下文中,它能經過每一個請求自動的傳輸到遠程組件中。
using System;
using System.Threading;
using System.Runtime.Remoting.Messaging;
namespace Novelty.WinServices
{
/// <summary>
/// 客戶端
/// </summary>
class RemotingPrincipalClient
{
[STAThread]
static void Main(string[] args)
{
//註冊客戶端信道和對象等等省略
//RemObject remObject = new RemObject();
GenericIdentity genericIdentity = new GenericIdentity("UserName");
GenericPrincipal genericPrincipal = new GenericPrincipal(genericIdentity, new
string[] { "admin", "user" });
PrincipalStorage principalStorage = new PrincipalStorage(genericPrincipal);
// 使用CallContext將.principal 附加到上下文中
CallContext.SetData("Principal", principalStorage);
//調用遠程方法,這裏不須要顯示傳遞用戶信息
remObject.ExampleMethod();
}
}
}
(3)在遠程組件中獲取信息並驗證它
using System;
using System.Threading;
using System.Runtime.Remoting.Messaging;
namespace Novelty.WinServices
{
/// <summary>
/// 遠程類
/// </summary>
public class RemObject : MarshalByRefObject
{
public RemObject()
{
}
public void ExampleMethod()
{
PrincipalStorage principalStorage = CallContext.GetData("Principal") as PrincipalStorage;
if (ppal == null)
{
return;
}
// 得到用戶名
string userName = principalStorage.CurrentGenericPrincipal.Identity.Name;
//驗證用戶信息.....
//具體方法實現
//.......
}
}
}
固然,這個例子不是很恰當,由於只傳遞了用戶名和角色!能夠修改自定義的類 PrincipalStorage ,讓它僅包含用戶名稱和密碼。或者經過登陸驗證函數時返回給客戶端一個Guid,在服務器端也保存這個Guid,而後經過自動傳遞Guid來驗證。
上面的兩種客戶端驗證能夠一塊兒使用,原本寫到這裏也該結束了,可是我仍是有一個疑問:方法一的優勢是不須要在具體服務(遠程對象的方法)中進行驗證,可是缺點是沒法針對具體方法進行驗證,並且驗證的方式有侷限性。方法二的優勢是針對具體方法進行驗證,驗證的方式能夠本身擴展,可是也存在缺點,那就是必須在須要驗證的方法中添加驗證。也許有人認爲這是句廢話,可是我想解釋一下。若是可以象ASP.NET 2.0同樣,只須要embership的Provider,那麼只在登陸頁面用登陸控件,就能夠實現對全部的頁面進行用戶驗證,並且在web.config中設置不須要驗證的頁面。在頁面中,看不到任何驗證用戶的代碼。個人意思是,是否能夠不在具體服務(遠程對象的方法)進行驗證,而是經過在使用配置文件或是在某個地編寫一小段代碼,就能夠有選擇性的對具體服務(遠程對象的方法)進行驗證?這樣,具體服務(遠程對象的方法)的內容與驗證就脫離開來,只須要對本身的任務負責,不去管驗證的事情。而客戶端只須要在登陸驗證經過後就能夠調用其角色受權的方法,而不須要再在調用方法時還先發一下上下文信息。
參考照料:
(1) http://dotnetwithme.blogspot.com/2007/01/how-to-build-authorization-module-for.html(2)MSDN