最近協助處理的問題,某個網頁使用IFrame內嵌了來自另一個網站的ASP.NET網頁,被內嵌的網頁有使用到Session,單獨開啟操作時一切正常;但被內嵌使用時,會出現Session無法儲存的問題。而有趣的是,另外單獨開啟Session網頁一次,再回頭使用被內嵌的版本,居然Session功能就正常了。

我用以下的網頁來模擬情境,httq://172.28.1.1/P3P/Main.aspx以IFrame內嵌了另一個網站httq://127.0.0.1/P3P/UserSession.aspx(其實都是同一台機器的同一個Web Application,但我透過對外IP及127.0.0.1模擬成不同網站)

Main.aspx的內容如下,純粹只是一個包含IFrame的容器網頁,另外再顯示Request.Url以便識別:

<%@ Page Language="C#" %>
 
<!DOCTYPE html>
 
<script runat="server">
    void Page_Load(object sender, EventArgs e) {
        u.Text = Request.Url.AbsoluteUri;
    }
</script>
 
<html>
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <asp:Label ID="u" runat="server"></asp:Label><hr />
    <iframe src="<% =Request.Url.AbsoluteUri.Replace(
        Request.Url.Host, "127.0.0.1").Replace("Main", "UseSession") %>" 
     style="width: 400px; height: 100px;"></iframe>
    </form>
</body>
</html>

UseSession.aspx提供TextBox可輸入要存入Session的字串,按下Save儲存,按Refresh則可顯示存入的Session內容。

<%@ Page Language="C#" %>
 
<!DOCTYPE html>
 
<script runat="server">
    protected void Page_Load(object sender, EventArgs e)
    {
        u.Text = Request.Url.AbsoluteUri;
        RefreshSessionDisplay();
    }
    private void RefreshSessionDisplay()
    {   
        l.Text = Server.HtmlEncode((string)Session["Boo"]);
    }
    protected void b_Click(object sender, EventArgs e)
    {
        Session["Boo"] = t.Text;
        t.Text = "";
        RefreshSessionDisplay();
    }
    protected void r_Click(object sender, EventArgs e)
    {
        RefreshSessionDisplay();
    }    
</script>
 
<html>
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <asp:Label ID="u" runat="server" />
    <div>
    Session: <asp:TextBox ID="t" runat="server"></asp:TextBox>
    <asp:Button ID="b" runat="server" Text="Save" onclick="b_Click" />
    <asp:Button ID="r" runat="server" Text="Refresh" onclick="r_Click" />
    </div>
    <hr />
    <div>
    Session[Boo] = <asp:Label ID="l" runat="server"></asp:Label>
    </div>
    </form>
</body>
</html>

依經驗研判,這應與第三方網站Cookie被拒有關,而ASP.NET的Session又需要以Cookie為憑(可參考ASP.NET Session大排長龍一文),一旦Cookie被禁,ASP.NET Session也就跟著失效了。以前IE7/8的狀態列有個小眼睛Icon可以顯示第三方Cookie被拒的警示,但到IE9不知怎麼就沒了。山不轉路轉,這裡就用IE9 Dev Tools的新功能Network擷取功能來驗證:

開啟網路擷取功能後,輸入內容,按下Save後,再按一次Refresh發生Session遺失,兩次按鈕動作觸發了兩次POST。

在第一個POST中,Response裡出現儲存ASP.NET_SessionId Cookie的Set-Cookie指令。

但在第二個POST Request中,卻沒有附帶任何Cookie,少了ASP.NET_SessionId Cookie,便無法與先前儲存的Session資料聯結在一起,於是Session資料就遺失了。

但獨立開啟UseSession.aspx時,ASP.NET_SessionId可以被儲存,而連帶瀏覽器在對內嵌於Main.aspx中UseSession.aspx發出Request時,也會附上剛才獨立開啟時保存的Cookie,於是便可維繫與Session資料的連結。

而UseSession.aspx的Cookie之所以被禁,是瀏覽器基於維護隱私權,預設封鎖來自第三方網站的Cookie,更精確一點說,應是瀏覽器會封鎖下列兩種第三方 Cookie:不具有精簡原則 (一段簡短扼要的、可供電腦閱讀的隱私權聲明) 的第三方 Cookie,或是具有精簡原則,說明即使未獲得您的默許,仍會使用可識別個人身份的資訊的第三方 Cookie。(參考來源,該篇翻譯不太好懂,稍後會有較實務面的說明)

要解決瀏覽器封鎖Cookie,有兩種做法,一個是要求使用者調整瀏覽器端的設定,將UseSession.aspx的網站列入信任的網站(Trusted Sites)或近端內部網路(Local Intranet),就可享受較寬鬆的限制。而另一個思考方向,就是設法提供所謂的"精簡原則"(Compact Policy),避開瀏覽器的封鎖行為。

所謂的精簡原則,是P3P規範(隱私權偏好選項平台,Platform for Privacy Preferences)的一部分,P3P規範極其複雜,若要說清楚,天都黑一半了。簡單來說,所謂P3P就是網站向瀏覽器表明自己的隱私權政策,例如: 網站是否會蒐集使用者的個人資訊、打算拿Cookie來做什麼用途... 等等,若表現得夠誠懇善良又正直,瀏覽器便會依規則及使用者偏好做出判斷,決定是否接受第三方網站的Cookie。關於P3P的中文資料不多,我找到一篇不錯的文章,有興趣的人可以參考;另外,MSDN裡也有一系列的文章,提供詳細介紹。

部署P3P的完整程序,應包含建立隱私權保護政策文件(policy.html)、原則檔(policy.xml)、原則參考檔(p3p.xml),其中的細節複雜到要靠額外編輯器工具才能搞定。而且要留意,若是對大眾營運的網站,宣告的隱私權保護政策與實際資料蒐集行為不符,會有吃上官司的風險,不可不慎。

但如果我們只是要解決企業內部的跨網站整合,問題就單純多了。只需要在UseSession.aspx加一行:

    protected void Page_Load(object sender, EventArgs e)
    {
        Response.Headers.Add("P3P", "CP=\"NOI ADM DEV COM NAV OUR\"");
        u.Text = Request.Url.AbsoluteUri;
        RefreshSessionDisplay();
    }

在網頁回傳的Header中加入P3P標籤,這些標籤即先前所說的精簡原則,例如: NOI表示不蒐集可識別資料,ADM表示資料為網站管理用... 等等。(RENJIN的文章有介紹一些標籤,完整標籤清單則可參考這裡,MSDN上則有另一分較簡要的清單) 瀏覽器會依照這些標籤判斷是否接受Cookie,理論上標籤應真實反應網站的資料蒐集行為,所以不會有任何官方建議,而瀏覽器怎麼由標籤判別是否接受Cookie也沒有明確的準則,依我自己測試的結果,加上NOI是最省事的做法,一顆見效;不過網站應該很難真的做到NOI,除非永遠匿名,但我想應用在企業內部引發爭議的機率不高(企業系統以公務為主,員工資料也早在系統中, 不太會因Cookie涉及隱私,更何況很多公司連員工MSN都在監聽了... XD)。但還是再提醒一次,P3P隱私權宣告有可能衍生法律議題,請大家自行依實際狀況因時因地制宜,各人造業各人擔囉~~ XD (PS: 除了由ASP.NET回傳Header,也可透過IIS設定或HTML META標籤方式指定P3P原則)

stackoverflow有篇討論提出幾個P3P法律問題的論點值得參考:

  • NOI - "Web Site does not collected identified data." 有登入機制、使用者客製化時可能就違背了。
  • STP - "Information is retained to meet the stated purpose. This requires information to be discarded at the earliest time possible. Sites MUST have a retention policy that establishes a destruction time table. The retention policy MUST be included in or linked from the site's human-readable privacy policy." 如果宣稱STP,卻沒有明確的保存政策,應該也算詐騙吧? :P

Comments

# by 91

誠懇善良又正直,這不是在說我嘛 XD

# by u329.tw

這在IE6剛出來時,我就遇到此問題了,當時的解法除了改網頁外,也可從IIS下手,這樣應該會比較方便,我將當時的筆記貼在下方,參考~ ---------------------------------------- 解決IE跨Server安控 方法有二,擇一使用: 1.在接收方Server的入口網頁加入: Response.AddHeader "P3P","CP=CAO PSA OUR" 2.在接收方Server的IIS加入Http標題,標題名稱為[P3P],標題值為[CP=CAO PSA OUR] 問題原因在於IE6之後,實做了P3P的機制(Privacy Preferences Project),對於在主視窗中以frame或sub-window的方式開出的網頁會視為是其他網站的網頁,為安全性考量會拒絕送出Cookie值。所以加上header以告知Browser此網頁不需做P3P的考量,Browser即會將Cookie送出,也就能取得想要的session了。

# by Breeze

感謝前輩們的指導 這篇真的對我幫助"無窮盡"的大 我也是同樣的情況 在別人的網頁中main.aspx 嵌入我自己的搜尋程式search.aspx 但他們在使用時選擇完條件按送出後"時常"會跳回main.aspx就像重新整理了一樣 後來去塞log發現"有時"跳回首頁的原因是連search.aspx 裡的按鈕事件都沒有觸發 難怪會沒有參數生成可提供Redirect 但使用前輩們的方法後 此情況完全改善了....只是還不太了解為什麼會解決了...因為我的search.aspx裡並沒有需要記什麼session 難道沒有session也會影響我的參數傳遞嗎??

Post a comment


26 + 25 =