前幾天才嘗試修改讓 Discuz! X2.5 可以支援 https 協定 下運作
昨天登入 Google 的 Search Console 收到這樣的提示
自 2017 年 1 月起,Chrome (56 以上版本) 會將收集密碼或信用卡詳細資料的網頁標示為「不安全」(透過 HTTPS 提供的網頁除外)。
下列網址包含密碼或信用卡詳細資料的輸入欄位,將會觸發這項新的Chrome 警告。請查看這些示例,掌握這類警告出現的位置,方便您採取有助於保護使用者資料的行動。請注意,這份清單中僅列出部分示例。
目前鸚鵡裝的是 Chrome 55 版,在 http 協定連線時,網址列前面是一個圓形驚嘆號
但不會有其他提示,不知道 Chrome 56 會不會跳出提示
為了避免被跳出不安全的提示,所以決定再來嘗試改一下程式碼
讓論壇即使是 http 協定連入,登入表單也必定傳送到 https 協定
跟登入有關的樣板檔案有兩個
一個是論壇右上角的快速登入表單 (login_simple.htm)
另一個是浮動視窗的登入表單 (login.htm)
將兩個樣板檔案中 form 的 action 修改成 https 的網址,登入時會發生錯誤
再追一下 Discuz 的登入實作方式和流程
流程是 form 的 target 是指向一個隱藏 iframe (即時建立)
再透過 iframe 的 onload event 觸發 function 來讀取 iframe 內的相關資料
所以在 http 協定下將登入時的 form 對象改為 https 時
會造成讀取 iframe 內的 DOM 時違反了瀏覽器的 同源政策(Same-origin policy)
所以想到的就是得克服 同源政策 的限制
- 使用 iframe 且 FQDN 相同,但因 protocol 不同所以無法透過 document.domain 來解決
- 登入結果返回時,會有 xml 和 javascript code 兩種模式
要透過 window.postMessage 來傳遞訊息就必修大量修改論壇程式 - 要以 JSONP 來解決也跟上面的情況一下,要大量修改
- 以 CORS (Cross-Origin Resource Sharing) 來實作可以解決
但可能會有瀏覽器支援性的問題 (雖然現在要找到不支援的瀏覽器很難)
回頭看程式碼的時候在 source/class/class_member.php 中看到 第 3-8 行
登入處理時會先判斷 uid 是否存在,如果已經存在則直接顯示登入成功要顯示的資訊
1 2 3 4 5 6 7 8 9 10 11 12 13 |
function on_login() { global $_G; if($_G['uid']) { $referer = dreferer(); $ucsynlogin = $this->setting['allowsynlogin'] ? uc_user_synlogin($_G['uid']) : ''; $param = array('username' => $_G['member']['username'], 'usergroup' => $_G['group']['grouptitle'], 'uid' => $_G['member']['uid']); showmessage('login_succeed', $referer ? $referer : './', $param, array('showdialog' => 1, 'locationtime' => true, 'extrajs' => $ucsynlogin)); } $from_connect = $this->setting['connect']['allow'] && !empty($_GET['from']) ? 1 : 0; $seccodecheck = $from_connect ? false : $this->setting['seccodestatus'] & 2; $seccodestatus = !empty($_GET['lssubmit']) ? false : $seccodecheck; $invite = getinvite(); |
原本鸚鵡決定的實作方式如下
- 讓 http 和 https 的 cookie 可以共用
- form 的 target 對象的 iframe 觸發 onload event 後
若讀取 iframe 的 DOM 發生 cross-origin frame 錯誤時
將 iframe 的 src 屬性設定為 form 的 action 屬性,且 protocol 改回使用 http - iframe 重新載入時,https 登入時的 cookie 也會在 header 中一併送出
- class_member.php 確認 uid 之後直接返回登入結果
這時候 iframe 的 onload event 觸發的 function 就可以正常讀取 iframe 內的 DOM
缺點是登入動作會多產生一次 Request,使用者體驗上登入動作會稍微慢一點點
測試起來好像沒有問題,可以正確登入,提示訊息也正常顯示
然後忽然想到登入失敗的部份,就測試一下
果然錯誤訊息沒辦法正常顯示….
那就… 改用 CORS 來實作吧,反正現在不支援的瀏覽器很難找 =..=
最後實作的方式是
- 讓 http 和 https 的 cookie 可以共用
- 在 https 協定下進行登入,使用原本的方式
在 http 協定下進行登入,以 CORS 的方式來完成,並接受伺服器傳來的 cookie - class_member.php 處理登入時如果檢查到 Origin 標頭
就回應 Access-Control-Allow-Origin 相關 header - XMLHttpRequest 取得回應的資料後,依循原本的處理模式進行處理
所以總共修改了6個檔案
樣板使用的是:default,有使用其他樣板就修改對應的檔案即可
- template/default/member/login.htm
12345# 找到<form method="post" autocomplete="off" name="login" id="loginform_$loginhash" class="cl" onsubmit="{if $this->setting['pwdsafety']}pwmd5('password3_$loginhash');{/if}pwdclear = 1;ajaxpost('loginform_$loginhash', 'returnmessage_$loginhash', 'returnmessage_$loginhash', 'onerror');return false;" action="member.php?mod=logging&action=login&loginsubmit=yes{if !empty($_GET['handlekey'])}&handlekey=$_GET[handlekey]{/if}{if isset($_GET['frommessage'])}&frommessage{/if}&loginhash=$loginhash"># 修改成<form method="post" autocomplete="off" name="login" id="loginform_$loginhash" class="cl" onsubmit="{if $this->setting['pwdsafety']}pwmd5('password3_$loginhash');{/if}pwdclear = 1;ajaxpostCors('loginform_$loginhash', 'returnmessage_$loginhash', 'returnmessage_$loginhash', 'onerror');return false;" action="https://{$_SERVER['HTTP_HOST']}/member.php?mod=logging&action=login&loginsubmit=yes{if !empty($_GET['handlekey'])}&handlekey=$_GET[handlekey]{/if}{if isset($_GET['frommessage'])}&frommessage{/if}&loginhash=$loginhash">
- template/default/member/login_simple.htm
12345# 找到<form method="post" autocomplete="off" id="lsform" action="member.php?mod=logging&action=login&loginsubmit=yes&infloat=yes&lssubmit=yes" onsubmit="{if $_G['setting']['pwdsafety']}pwmd5('ls_password');{/if}return lsSubmit();"># 修改成<form method="post" autocomplete="off" id="lsform" action="https://{$_SERVER['HTTP_HOST']}/member.php?mod=logging&action=login&loginsubmit=yes&infloat=yes&lssubmit=yes" onsubmit="{if $_G['setting']['pwdsafety']}pwmd5('ls_password');{/if}return lsSubmit();">
- static/js/common.js
新增一個 function ajaxpostCors()
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970function ajaxpostCors(formid, showid, waitid, showidclass, submitbtn, recall) {if( location.protocol == 'https:' ) {ajaxpost(formid, showid, waitid, showidclass, submitbtn, recall);return false;}var waitid = typeof waitid == 'undefined' || waitid === null ? showid : (waitid !== '' ? waitid : '');var showidclass = !showidclass ? '' : showidclass;var curform = $(formid);var formtarget = curform.target;var handleResult = function() {if(xhr.readyState == 4 && xhr.status == 200) {var evaled = false;var s = xhr.responseText.trim();s = s.replace('<?xml version="1.0" encoding="utf-8"?>', '');s = s.replace('<root><![CDATA[', '').replace(']]></root>', '');s = s.replace('<root>', '').replace('</root>', '');showloading('none');if(s != '' && s.indexOf('ajaxerror') != -1) {evalscript(s);evaled = true;}if(showidclass) {if(showidclass != 'onerror') {$(showid).className = showidclass;} else {showError(s);ajaxerror = true;}}if(submitbtn) {submitbtn.disabled = false;}if(!evaled && (typeof ajaxerror == 'undefined' || !ajaxerror)) {ajaxinnerhtml($(showid), s);}ajaxerror = null;if($(formid)) $(formid).target = formtarget;if(typeof recall == 'function') {recall();} else {eval(recall);}if(!evaled) evalscript(s);}};showloading();var action = curform.getAttribute('action');action = hostconvert(action);curform.action = action.replace(/\&inajax\=1/g, '')+'&inajax=1';var url = curform.action;var xhr = new XMLHttpRequest();var data = new FormData(curform);xhr.open("POST", url, true);xhr.withCredentials = true;xhr.onreadystatechange = handleResult;xhr.send(data);if(submitbtn) {submitbtn.disabled = true;}doane();return false;}
- static/js/logging.js
將ajaxpost
修改成ajaxpostCors
123456789101112function lsSubmit(op) {var op = !op ? 0 : op;if(op) {$('lsform').cookietime.value = 2592000;}if($('ls_username').value == '' || $('ls_password').value == '') {showWindow('login', 'member.php?mod=logging&action=login' + (op ? '&cookietime=1' : ''));} else {ajaxpostCors('lsform', 'return_ls', 'return_ls');}return false;}
- source/function/function_core.php
搜尋function dsetcookie
,然後修改成
12345678910$life = $life > 0 ? getglobal('timestamp') + $life : ($life < 0 ? getglobal('timestamp') - 31536000 : 0);$path = $httponly && PHP_VERSION < '5.2.0' ? $config['cookiepath'].'; HttpOnly' : $config['cookiepath'];//$secure = $_SERVER['SERVER_PORT'] == 443 ? 1 : 0;$secure = 0;if(PHP_VERSION < '5.2.0') {setcookie($var, $value, $life, $path, $config['cookiedomain'], $secure);} else {setcookie($var, $value, $life, $path, $config['cookiedomain'], $secure, $httponly);}
- source/class/class_member.php
搜尋function on_login()
,然後修改成
12345678910111213function on_login() {if( array_key_exists('HTTP_ORIGIN', $_SERVER) ) {header("Access-Control-Allow-Origin: http://". $_SERVER['HTTP_HOST']);header('Access-Control-Allow-Methods: POST, GET');header('Access-Control-Allow-Credentials: true');}global $_G;if($_G['uid']) {$referer = dreferer();$ucsynlogin = $this->setting['allowsynlogin'] ? uc_user_synlogin($_G['uid']) : '';$param = array('username' => $_G['member']['username'], 'usergroup' => $_G['group']['grouptitle'], 'uid' => $_G['member']['uid']);showmessage('login_succeed', $referer ? $referer : './', $param, array('showdialog' => 1, 'locationtime' => true, 'extrajs' => $ucsynlogin));}
完成後進入 管理中心 -> 工具 -> 更新緩存,更新樣板緩存
讓瀏覽器可以載入新的 script 檔案,這樣就完成了