1. 程式人生 > >Silverlight 中的通訊安全訪問策略

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)內,同樣也會觸發這樣一個錯誤。