2012-10-05 61 views
1

重要使用PHP,MySQL基於日誌的系統

依照下列答案的意見後存在的問題,客戶端能夠登錄,在沒有任何問題,但並沒有試圖實際導航保護的頁面。當他稍後嘗試這樣做時,他會像以前一樣返回登錄,並顯示「請登錄」錯誤。想到很多頭腦之後,想到了一件令人難以置信的簡單事情 - 客戶端訪問該網站的時間爲http://www.example.com/admin,登錄腳本中的所有內容都重定向到http://example.com,因此它正在查找的會話cookie已設置爲另一個域。這也解釋了爲什麼他第一次登錄時遇到問題,但沒有後續登錄 - 腳本將他重定向到沒有www的登錄表單。

一個快速解決方案是編寫一個.htaccess文件來解決www問題。當然,這也可以在登錄腳本中處理,我將改進以備將來使用。

原帖

我開發了家釀CMS和登錄系統,PHP和MySQL基礎的網站。我的CMS對每個客戶都是獨一無二的,而且它一直都是人羣中的焦點 - 不幸的是,我的登錄系統並非如此。以下是一篇很長的文章,但我需要覆蓋細節以嘗試找到解決方案。忍受我..

該系統是相當直接,如果不是一個巨大的。每個用戶在SALT旁邊都有一個存儲在MySQL表中的鹽味散列。當用戶登錄時,他們的SALT被檢索並且提交的密碼變成一個含鹽散列。

如果提交的salted哈希值與表中存儲的哈希值相匹配,則認證用戶。諸如名稱,最後IP地址和帳戶級別(大多數站點上的3個級別)等詳細信息存儲在分配給會話變量的數組中。然後,他們被重定向到他們登錄的受限站點的登錄頁面(僅限會員或Admin/CMS)。

安全頁面包含一個較小的auth.php文件,用於檢查包含其詳細信息的會話變量是否存在。如果不是,則會將其重定向到該網站的登錄表單,並顯示一條錯誤消息,內容爲「請登錄」。如果存在,則允許它們繼續,並將存儲在數組中的細節分配給變量。

許多用戶報告的問題是,他們經常需要多次登錄以避免被「請登錄」錯誤消息彈回到登錄表單,或者他們導航到另一個頁面在安全網站中隨機彈出並返回登錄,並顯示相同的錯誤。所以,session變量似乎沒有被設置,或者在正常使用站點期間出於某種原因被清除。

第一個問題從來沒有發生在我身上 - 通過多種設備和網絡 - 我已經在客戶辦公室使用他們的筆記本電腦目睹了它。我讓他們連接到我的移動熱點,並沒有改變。但是,他們能夠使用我的筆記本電腦和我的熱點連接無任何問題地登錄。不幸的是,我無法使用我的筆記本電腦連接到他們的網絡,因此無法排除變量。

* 注 - *我忘了最初提及的是,系統正常工作的問題客戶,他們使用正確的憑據登錄兩次或三次之後。在瀏覽器保持打開的狀態下進行的後續登錄嘗試往往在未出現問題的情況下執行。此外,登錄頁面會破壞會話。

這裏是每一級的代碼,開始與登錄腳本:

的login.php

<?php 
putenv("TZ=US/Eastern"); 

if (array_key_exists('site', $_POST)) { 
    $authenticate = new loginUser($_POST['username'], $_POST['password'], $_POST['site'], $_SERVER['REMOTE_ADDR']); 
} 
//Authenticate and log-in 
class loginUser { 
    private $memDB, $username, $password, $site, $ip_address; 

    //Clean input variables 
    private function clean($str) { 
     $str = @trim($str); 
     if(get_magic_quotes_gpc()) { 
      $str = stripslashes($str); 
     } 
     return $str; 
    } 
    //Construct variables 
    function __construct($username, $password, $site, $ip_address) { 
    session_start(); 
     $this->memDB = new PDO('mysql:host=localhost;dbname=exampleDB', 'exampleUser', 'examplePassword'); 
     $this->username = $this->clean($username); 
     $this->password = $this->clean($password); 
     $this->site = $site; 
     $this->ip_address = $ip_address; 
    $this->authUser(); 
    } 
    //Validate username 
    private function validateUsername($username) { 

     $checkUsername = $this->memDB->prepare("SELECT COUNT(*) FROM accounts WHERE username = ?"); 
     $checkUsername->execute(array($username)); 
     return $checkUsername->fetchColumn(); 
    } 
    //Obtain and set account details 
    private function accountDetails() { 

     $fetchAccountDetails = $this->memDB->prepare("SELECT id, name_f, name_l, ipAddr, lastLogin, accountLevel, isActive 
     FROM accounts WHERE username = ?"); 
     $fetchAccountDetails->execute(array($this->username)); 
     $accountDetails = $fetchAccountDetails->fetch(); 
     $this->updateLogin(); 
     return $accountDetails; 
    } 
    //Update last login details 
    private function updateLogin() { 

     $updateLogin = $this->memDB->prepare("UPDATE accounts SET ipAddr = ?, lastLogin = DATE_ADD(NOW(), INTERVAL 1 HOUR) WHERE username = ?"); 
     $updateLogin->execute(array($this->ip_address, $this->username)); 
    } 
    public function authUser() { 

     $loginErr = array(); //Array for holding login error message 
     $loginErrFlag = false; //Boolean for error 
     //Validate submitted $_POST elements 
     if (!$this->username) { 
      $loginErr[] = "Username missing"; 
      $loginErrFlag = true; 
     } 
     if (!$this->password) { 
      $loginErr[] = "Password missing"; 
      $loginErrFlag = true; 
     } 
     if ($this->username && $this->validateUsername($this->username) == 0) { 
      $loginErr[] = "Username invalid"; 
      $loginErrFlag = true; 
     } 
     if (!$loginErrFlag) { 
      //Fetch the password and SALT to compare to entered password 
      $validatePW = $this->memDB->prepare("SELECT password, salt FROM accounts WHERE username = ? LIMIT 1"); 
      $validatePW->execute(array($this->username)); 
      $passwordResult = $validatePW->fetch(); 
      $dbPW = $passwordResult['password']; 
      $dbSalt = $passwordResult['salt']; 
      //Compare entered password to SALT + hash 
      $hashPW = hash('sha512', $dbSalt . $this->password); 
      if ($hashPW === $dbPW) { 
       //Logged in 
       $_SESSION['CVFD-USER-DETAILS'] = $this->accountDetails(); 
       //Redirect to secure landing page for log-in origin (Members or Admin) 
       //Adding SID is a recent attempt to handle log-in problems 
       header("Location: http://example.com/$this->site/$this->site-main.php?" . SID); 
       //session_write_close() was here but was removed 
       exit(); 
      } else { 
       //Password invalid 
       $loginErr[] = "Please check your password and try again"; 
       $_SESSION['CVFD_LOGIN_ERR'] = $loginErr; 
       //Redirect to the log-in for the origin 
       header("Location: http://example.com/$this->site"); 
     session_write_close(); 
       exit(); 
      } 
     } else { 
      $_SESSION['CVFD_LOGIN_ERR'] = $loginErr; 
      header("Location: http://example.com/$this->site"); 
      session_write_close(); 
      exit(); 
     } 

    } 
} 
?> 

auth.php

<?php 
session_start(); 
if (!isset($_SESSION['CVFD-USER-DETAILS']) || $_SESSION['CVFD-USER-DETAILS'] == '') { 
    //Not logged in 
    $_SESSION['CVFD_LOGIN_ERR'] = array('Please login'); 
    header('Location: http://example.com/members'); 
    session_write_close(); 
    exit(); 
} else { 
    $userDetails = $_SESSION['CVFD-USER-DETAILS']; //Assign user details array to variable 
    //Check to see if account is active 
    $accountStatus = $userDetails['isActive']; 
    $accountLevel = $userDetails['accountLevel']; 
    if ($accountStatus == 0) { 
     //Account is not yet active (pending Admin activation) 
     $_SESSION['CVFD_LOGIN_ERR'] = array('Your account is suspended or pending activation'); 
     header('Location: http://example.com/members'); 
     session_write_close(); 
     exit(); 
    } else { 
     $CVFDFirstName = $userDetails['name_f']; 
     $CVFDLastName = $userDetails['name_l']; 
     $CVFDLastLogin = date("m/d/Y H:i:s", strtotime($userDetails['lastLogin'])); 
     $CVFDAccountLevel = $userDetails['accountLevel']; 
     $CVFDIPAddr = $userDetails['ipAddr']; 
    } 
} 
?> 

這裏是auth.php如何包含在安全文件中 -

<?php 
if (substr_count($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip')) ob_start("ob_gzhandler"); else ob_start(); 
require($_SERVER['DOCUMENT_ROOT'] . '/members/includes/handlers/handler.auth.php'); 

任何幫助,將不勝感激。相當神祕..

謝謝!

+0

所以你不能重現這個問題呢? – MrCode

+0

從我的設備和網絡,沒有。第二個問題 - 在使用CMS期間隨機地重新登錄註冊 - 偶爾發生在我身上。大多數客戶使用IE和Windows,儘管投訴最多的客戶在他的iPhone,iPad,Windows NT筆記本電腦上使用IE 9和Chrome都存在問題。當他在家中並且在所有設備上工作時都會出現問題。 – NightMICU

+0

我也確認了在他的設備和瀏覽器中啓用了Cookie。我親自目睹了這個問題。不過想到一個重要的問題 - 增加了原始問題 – NightMICU

回答

0

的一兩件事,在我跳出如下:

header('Location: http://example.com/members'); 
session_write_close(); 
exit(); 

我把之前的header('location ...')

session_write_close()電話是錯誤的任何「已經發送了頭」顯示在你的日誌呢?

想到的其他事情是一些AJAX競爭條件。任何異步調用進行登錄頁面?

+0

有效的點。但是,目前代碼中唯一出現的地方是auth.php,並且只有在會話變量丟失或者未被授權查看網站(錯誤的帳戶級別)的情況下。它們被包裝在條件語句中。這仍然是一個問題嗎?至於錯誤,我還沒有看到。 – NightMICU

+0

沒有使用AJAX。 – NightMICU

+0

我認爲這是問題,我會完全擺脫'session_write_close()',看看會發生什麼。如果你不希望代碼經常執行,那麼你可能會有一個bug。你可以在那裏添加一個日誌並監視它。 – MrCode

1

我做登錄系統的方式是隻使用會話ID,而不是在會話中存儲任何內容。當用戶登錄他們的散列用戶代理數據時,他們的會話ID,他們的用戶ID(對應於一個用戶表)和到期時間被放入一個通常被稱爲「active_users」的表中,然後我有一個登錄頭文件在每個啓動會話的管理員限制頁面中,檢索用戶會話標識並檢查該會話標識是否在活動用戶表中,以及被檢查的用戶是否具有相同的用戶代理數據,並且不超過到期時間。如果該查詢沒有返回任何內容,則它們未登錄並被退出。

這就是我製作的大多數登錄系統的工作方式,並且我沒有遇到任何問題。

+0

後,系統似乎正常工作有趣。所以,對於我的應用程序,我會用他們的用戶ID,會話ID和到期時間創建一個新表。然後,使用auth.php檢查每個頁面視圖的會員表以檢索其詳細信息,並確保它們具有該網站的正確帳戶級別?這是很多MySQL的查詢,但我想他們做這樣簡單的事情是相當快的..平均不處理超過30個用戶 – NightMICU

+0

只是想更多的關於這一點。看起來會話被破壞了,導致了這個錯誤。當這種情況發生時,會話ID是否也會發生變化? – NightMICU

+0

您不應該使用用戶代理來識別會話。它可以在請求之間改變。除此之外,它不提供任何額外的安全性,因爲如果攻擊者嗅探到某人的會話ID,那麼他們幾乎肯定會擁有用戶代理。 – MrCode

0

成功!仍然需要精確地縮小導致問題消失的變化,但客戶報告說他不再有登錄問題。

立即想到的最大變化是在幾乎所有地方都刪除了session_write_close()。它可能在代碼的頭部重定向之後放置,或者只是出現它可能是原因。我會嘗試將它放在重定向之前。

感謝大家的建議