Silverlight 中的通訊安全訪問策略
Silverlight 支援使用 HTTP/HTTPS (System.Net.WebClient 等) 和 Socket (System.Net.Sockets) 兩種方式訪問遠端伺服器,但基於安全原因,對這些網路訪問制定了嚴格的安全策略。
1. HTTP/HTTPS 訪問策略
(1) 始終允許同域呼叫。同域是指呼叫必須使用同一子域、協議和埠,這是出於安全原因以及防止跨域偽造。
(2) Silverlight 支援訪問包含跨域策略檔案的網站服務。跨域訪問時,Silverlight Application 首先在目標 Web 服務的根路徑查詢 Silverlight 跨域策略檔案 (clientaccesspolicy.xml)。如果沒找到(404 Not Found)或發生其他錯誤,將繼續在根路徑處查詢 Flash 跨域策略檔案 (crossdomain.xml)。
clientaccesspolicy.xml <?xml version="1.0" encoding="utf-8"?> <access-policy> <cross-domain-access> <policy> <allow-from> <domain uri="*"/> </allow-from> <grant-to> <resource path="/" include-subpaths="true"/> </grant-to> </policy> </cross-domain-access> </access-policy>
(3) 所有通訊都是非同步的。
(4) 僅支援 GET 和 POST 謂詞。
(5) 支援大多數標準請求標頭和所有自定義請求標頭(必須是跨域策略檔案中允許的標頭)。
<?xml version="1.0" encoding="utf-8"?> <access-policy> <cross-domain-access> <policy > <allow-from http-request-headers="SOAPAction, x-custom-header"> <domain uri="*"/> </allow-from> <grant-to> <resource path="/services/" include-subpaths="true"/> </grant-to> </policy> </cross-domain-access> </access-policy>
(6) 只有 "200 OK" 和 "404 Not Found" 狀態程式碼可用。
我們嘗試用 WebClient 訪問一個跨域網站。
private void Button_Click(object sender, RoutedEventArgs e)
{
var client = new WebClient();
client.DownloadStringCompleted += (s, ex) =>
{
this.TextBlock1.Text = ex.Error != null ? ex.Error.ToString() : ex.Result;
};
client.DownloadStringAsync(new Uri("http://localhost:8081/", UriKind.Absolute));
}
當目標網站沒有提供策略檔案時,你會看到如下的錯誤資訊。
在其根路徑放置好 clientaccesspolicy.xml 後,訪問正常。
2. Sockets 訪問策略
建立一個供 Silverlight Application 連線的 Socket Server (非同域網站),必須符合以下安全策略。
(1) 監聽 943 埠,為 Silverlight Application 提供策略檔案(clientaccesspolicy.xml)。
(2) Socket Server 的服務埠範圍必須在 4502 - 4534 之間。這是 Silverlight Application Socket 連線所允許使用的埠範圍,否則連線失敗。
我們寫一個簡單的 Time Server 作為演示。
Server CUI
class Program
{
static void Main(string[] args)
{
AccessPolicyServer();
TimeServer();
Console.WriteLine("Press any key to exit...");
Console.ReadKey(true);
Environment.Exit(0);
}
/// <summary>
/// 建立時間伺服器執行緒
/// </summary>
private static void TimeServer()
{
new Thread(() =>
{
// 監聽 4502 埠
var server = new TcpListener(IPAddress.Parse("127.0.0.1"), 4502);
server.Start();
while (true)
{
var client = server.AcceptTcpClient();
var stream = client.GetStream();
// 傳送當前時間
var send = Encoding.UTF8.GetBytes(DateTime.Now.ToString());
stream.Write(send, 0, send.Length);
client.Close();
}
}).Start();
}
/// <summary>
/// 建立策略檔案伺服器執行緒
/// </summary>
private static void AccessPolicyServer()
{
new Thread(() =>
{
// 監聽 943 埠
var server = new TcpListener(IPAddress.Parse("127.0.0.1"), 943);
server.Start();
while (true)
{
var client = server.AcceptTcpClient();
var stream = client.GetStream();
// 讀取客戶端傳送資訊,如果請求字串不匹配則斷開。
var buffer = new Byte[1024];
var len = stream.Read(buffer, 0, buffer.Length);
var rece = Encoding.UTF8.GetString(buffer, 0, len);
if (String.Compare(rece, "<policy-file-request/>", true) != 0)
{
client.Close();
break;
}
Console.WriteLine(rece);
// 傳送策略檔案內容
var send = Encoding.UTF8.GetBytes(@"
<?xml version=""1.0"" encoding=""utf-8""?>
<access-policy>
<cross-domain-access>
<policy>
<allow-from>
<domain uri=""*""/>
</allow-from>
<grant-to>
<socket-resource port=""4502-4534"" protocol=""tcp"" />
</grant-to>
</policy>
</cross-domain-access>
</access-policy>");
stream.Write(send, 0, send.Length);
client.Close();
}
}).Start();
}
}
Client Silverlight XAP
private void Button_Click(object sender, RoutedEventArgs e)
{
// 建立 Socket
var client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
// 建立等待控制代碼
var wait = new ManualResetEvent(true);
// 建立連線引數
var args = new SocketAsyncEventArgs();
args.RemoteEndPoint = new DnsEndPoint("127.0.0.1", 4502);
// 訂閱處理事件
args.Completed += (s, ex) =>
{
if (ex.LastOperation == SocketAsyncOperation.Connect)
{
// 連線完成,則設定緩衝區
var buffer = new byte[1024];
ex.SetBuffer(buffer, 0, buffer.Length);
// 接收伺服器資料
client.ReceiveAsync(ex);
}
else if (ex.LastOperation == SocketAsyncOperation.Receive)
{
// 如果接收資訊正確,則顯示伺服器傳送內容,否則顯示錯誤資訊。
if (ex.SocketError == SocketError.Success)
{
this.Dispatcher.BeginInvoke(() =>
{
this.TextBlock1.Text = Encoding.UTF8.GetString(ex.Buffer, 0, ex.BytesTransferred);
});
}
else
{
this.Dispatcher.BeginInvoke(() =>
{
this.TextBlock1.Text = ex.SocketError.ToString();
});
}
wait.Set();
}
};
// 連線伺服器
client.ConnectAsync(args);
// 等待處理結束
wait.WaitOne();
}
如果不啟動伺服器策略檔案服務,或者埠不是 943,你會在客戶端獲得一個 AccessDenied 錯誤。
當然,如果伺服器監聽埠不再允許範圍(4502 - 4534)內,同樣也會觸發這樣一個錯誤。