簡體   English   中英

使用會話進行高級用戶驗證

[英]advanced user authentication using sessions

我正在建立一個有3種類型用戶的網站:

-those who are registered and are subscribed for current month
-those that are registered, but are not subscribed for current month
-users that are not registered (you cant be subscribed if you are not regitered)

我已經創建了識別這3種用戶的代碼並且行為恰當。 我的問題是,這是要走的路嗎? 我以前從未做過類似的事情。 或者我應該重新編程我的方法?

//login.php

//connect to database and see if a user and password combination exists. Store $exists=0 if not, and $exists=1 if it exists.
session_start();

$conn = new mysqli($hn,$un,$pw,$db);
if ($conn->connect_error){
    die($conn->connect_error);
}
$query = "SELECT COUNT(1) as 'exists',expiration_date FROM table WHERE email = ? AND password = ?;";
$stmt = $conn->prepare($query);
$stmt->bind_param("ss", $email, $password);

$email = $_POST["email"];
$password = hash("hashingalgorithm", "salt".$_POST["password"]."salthere");

$stmt->execute();
   /* Get the result */
$result = $stmt->get_result();
$num_of_rows = $result->num_rows;
$row = $result->fetch_assoc();
$exists = $row["exists"];
$expiration_date = $row["expiration_date"];

/* free results */
$stmt->free_result();

/* close statement */
$stmt->close();
$conn->close();

date_default_timezone_set('Europe/Berlin');


if ($exists==0){
    echo "Wrong email or password";
    $_SESSION['loginerror'] = 2;
    header('Location: https://www.homepage.com/login'); 
}else if ($exists){
    if (strtotime($expiration_date) < (strtotime("now"))){//logged in, but not subscribed
        session_destroy();
        session_start();
        $_SESSION["authenticated"] = true;
        header('Location: https://www.homepage.com');
    }else{//logged in and ready to go
        $_SESSION["authenticated"] = true;
        $_SESSION["email"] = $email;
        header('Location: https://www.homepage.com');
    }
}else{
    echo "An error with has occured.";
} 

然后在我網站的每一頁上我都使用這段代碼,看看有哪些用戶訪問過我

session_start();
if(isset($_SESSION["authenticated"]) && isset($_SESSION["email"])){ 
    $email = $_SESSION["email"];

    //connect to database and fetch expiration_date for a user with $email. Store it in $expiration_date

$conn = new mysqli($hn,$un,$pw,$db);
    if ($conn->connect_error){
        die($conn->connect_error);
    }
    $query = "SELECT expiration_date FROM table WHERE email = ?;";
    $stmt = $conn->prepare($query);
    $stmt->bind_param("s", $email);

    $email = $_SESSION["email"];
    $stmt->execute();
    /* Get the result */
    $result = $stmt->get_result();
    $num_of_rows = $result->num_rows;
    $row = $result->fetch_assoc();
    $expiration_date = $row["expiration_date"];
    /* free results */
    $stmt->free_result();
    /* close statement */
    $stmt->close();
    $conn->close();
    date_default_timezone_set('Europe/Berlin');

    if (strtotime($expiration_date) < (strtotime("now"))){//logged in, but not subscribed
        session_destroy();
        session_start();
        $_SESSION["authenticated"] = true;
        header('Location: https://www.homepage.com');
    }else{  //html for subsribed and registered user
    echo <<<_END
    //html here
    _END;
    }
}else if(isset($_SESSION["authenticated"]) && !isset($_SESSION["email"])){
        // user is logged in, but not subscribed;
        echo <<<_END
        //htmlhere
        _END;
}else{// user is not registered nor is subscribed
        echo <<<_END
        //htmlhere
        _END;
}

代碼有效,但我擔心一旦用戶注冊並訂閱,就會在每個頁面上訪問數據庫。 我實際上是在懲罰用戶注冊和訂閱。 是否有更好的,性能明智的方式來處理這類問題?

這里的解決方案是檢查訂閱用戶(僅在他們登錄您網站的第一頁),即您可以使用的login.php內,

// ...your previous code
session_start();
// initialize session variables
$_SESSION['subscribe_date'] = $_SESSION['authenticated'] = false;
if ($exists == 0){
    echo "Wrong email or password";
    $_SESSION['loginerror'] = 2;
    header('Location: https://www.homepage.com/login'); 
} else if ($exists){
    $_SESSION["authenticated"] = true;
    if (strtotime($expiration_date) > (strtotime("now"))){ //logged in,& subscribed
        $_SESSION["email"] = $email;
        $_SESSION['subscribe_date'] = $expiration_date;
    } else {  //logged in and not subscribed, do nothin!
    }
    header('Location: https://www.homepage.com');
} else {
    echo "An error has occured.";
} 

然后,在每個其他頁面上,您需要做的只是檢查$_SESSION['subscribe_date']而不是每次都觸發查詢

if(!empty($_SESSION["subscribe_date"]) && strtotime($_SESSION["subscribe_date"]) > (strtotime("now"))) {
 // html for subscribed and registered user
} else if (!empty($_SESSION["authenticated"])) {
 // html for registered user
} else {
 // html for unregistered user
}

另請注意,我已刪除session_destroy(); 在每個頁面上調用它是一個非常糟糕的主意。 如果需要,您可以取消設置會話變量。 即未unset($_SESSION["email"]); 你應該調用session_destroy(); 僅在用戶注銷時才結束。 查看session_destroy()的警告部分

如果是我,我會將這些代碼分離並重構為外部類/函數。 但是如果我們在談論你的代碼的小問題,那么我將做什么:

的login.php

//connect to database and see if a user and password combination exists. Store $exists=0 if not, and $exists=1 if it exists.
session_start();

$conn = new mysqli($hn,$un,$pw,$db);
if ($conn->connect_error){
    die($conn->connect_error);
}
$query = "SELECT COUNT(1) as 'exists',expiration_date FROM table WHERE email = ? AND password = ?;";
$stmt = $conn->prepare($query);
$stmt->bind_param("ss", $email, $password);

$email = $_POST["email"];
$password = hash("hashingalgorithm", "salt".$_POST["password"]."salthere");

$stmt->execute();
/* Get the result */
$result = $stmt->get_result();
$num_of_rows = $result->num_rows;
$row = $result->fetch_assoc();
$exists = $row["exists"];
$expiration_date = $row["expiration_date"];

/* free results */
$stmt->free_result();

/* close statement */
$stmt->close();
$conn->close();

date_default_timezone_set('Europe/Berlin');


if ($exists==0){
    //removed echo, you shouldn't output anything before headers
    $_SESSION['loginerror'] = 2;
    header('Location: https://www.homepage.com/login');
    //use exit after each header redirect
    exit;
}else if ($exists){
    //moved to the top to minimize code redundancy
    $_SESSION["authenticated"] = true;
    //it's good to have user identifier (e.g. email) accessible for all authenticated users
    $_SESSION["email"] = $email;
    if (strtotime($expiration_date) <= (strtotime("now"))){//logged in, but not subscribed
        //unset only subscribed sessions, do not destroy all sessions
        unset($_SESSION["subscribed"]);
        header('Location: https://www.homepage.com');
        exit;
    }else{
        //logged in with active subscribtion
        $_SESSION["subscribed"] = true;
        header('Location: https://www.homepage.com');
        exit;
    }
}else{
    echo "An error with has occured.";
} 

每頁都包含代碼

session_start();
if(isset($_SESSION["authenticated"])){
    //merged if-statement for both subscribed and unsubscribed user
    $email = $_SESSION["email"];

    //connect to database and fetch expiration_date for a user with $email. Store it in $expiration_date

    $conn = new mysqli($hn,$un,$pw,$db);
    if ($conn->connect_error){
        die($conn->connect_error);
    }
    //check if subscribed user is still subscribed and unsubscribed user is still unsubscribed
    $query = "SELECT expiration_date FROM table WHERE email = ?;";
    $stmt = $conn->prepare($query);
    $stmt->bind_param("s", $email);

    $email = $_SESSION["email"];
    $stmt->execute();
    /* Get the result */
    $result = $stmt->get_result();
    $num_of_rows = $result->num_rows;
    $row = $result->fetch_assoc();
    $expiration_date = $row["expiration_date"];
    /* free results */
    $stmt->free_result();
    /* close statement */
    $stmt->close();
    $conn->close();
    date_default_timezone_set('Europe/Berlin');

    //I have separated expiration verification and template echoing
    //first - expiration verification
    if (strtotime($expiration_date) <= (strtotime("now")) && isset($_SESSION["subscribed"])){
        //had been subscribed a second ago, but now he's not
        unset($_SESSION["subscribed"]);
        header('Location: https://www.homepage.com');
        exit;
    }else if (strtotime($expiration_date) > (strtotime("now")) && !isset($_SESSION["subscribed"])){
        //had been unsubscribed, but renewed subscription in the meantime (maybe in another browser with this session still active)
        $_SESSION["subscribed"] = true;
        header('Location: https://www.homepage.com');
        exit;
    }

    //user successfully passed expiraton verification, maybe was few times redirected
    //second - template echoing
    if (isset($_SESSION["subscribed"])) {
        // user is logged in and subscribed;
        echo '';
    }else{
        // user is logged in, but not subscribed;
        echo '';
    }
}else{
    // user is not registered
    echo '';
}

在代碼中的每次更改之前都有注釋。 以下是我所做的更改的簡短列表:

  • 使用退出; 在每個頭重定向之后: php - 我應該在調用Location:header之后調用exit()嗎?
  • 不要在標題之前回顯輸出
  • 為每個經過身份驗證的用戶使用3個會話驗證變量 - 經過身份驗證,訂閱和電子郵件
  • 如果用戶未訂閱,請不要銷毀整個會話,請改用unset
  • 我已將兩個經過身份驗證的用戶(已訂閱和未訂閱)合並到一個if語句中
  • 我在兩個后續步驟中分離了到期驗證和輸出回顯
  • 在每個頁面上,我正在檢查訂閱的用戶是否仍然訂閱(訂閱可以在他的會話期間到期),如果取消訂閱沒有同時訂閱(他可以使用兩個具有活動會話的瀏覽器)

我會做一些其他的小調整(在每個文件的頂部移動date_default_timezone_set等),但它們不是你問題的主題。

根據我的理解,你已經有了一個有效的代碼。 你要問的是意見。 您希望在檢查數據庫的每個頁面中刪除重復以進行身份​​驗證和訂閱。

在我看來,你需要改變你使用會話的方式,

 $_session['email']        // email address of user 
 $_session['auth_type']    // holds authentication type
 $_session['auth_till']    // subscription expire date

然后讓我們創建函數來檢查訂閱。 此函數可以放在單獨的文件中,例如:init.php。 在這里,我們可以設置會話啟動機制,以便在任何情況下都可以使用會話。

if(!isset($_SESSION)) 
    session_start();       // start session if not already started

// lets define global vars to hold authentication type of visitor
define("SUBSCRIBED",1); 
define("UN_SUBSCRIBED",2);
define("REGISTERED",3);
function checkSubscription():bool{
    $return = false;
    if(($_session['auth_type']==SUBSCRIBED)&&(strtotime($_session['auth_till']) < strtotime("now")))
        $return= true;
    return $return
}

並且在login.php上使用相同的技術,同時設置會話身份驗證類型。

現在任何其他頁面都可以簡單地使用函數來檢查訂閱

例如:

<?php
// file: product.php
include_once("init.php");
if(!checkSubscription()){
    // subscription finished. do what you need to do. otherwise continue.
}

您的代碼可以進行許多改進。 但我認為這將滿足您的需求。 如果您需要任何其他助手,請告訴我。 請訪問Scape ,如果有任何有用的編碼,請告訴我。

在對每個頁面加載進行SQL查詢之前,使用PHP會話變量來存儲有關當前用戶的信息。

例如,當用戶登錄時,將用戶ID保存到會話變量中的該用戶會話:

$_SESSION['user_id'] = <userid>;

在下一頁加載時,在運行數據庫查詢之前檢查該會話變量。

if ($_SESSION['user_id']) {
    // normal operations
} else {
    // Make your database call
}

一般來說,當用戶第一次登錄時,在$_SESSION設置兩個狀態標志:

 $_SESSION['loggedIn']   = true
 $_SESSION['subscribed'] = bool //true or false

我假設只有注冊用戶才能登錄。 如果用戶做了某些事情來改變他或她的狀態,請相應地更新$_SESSION

請注意,在檢查值之前,請務必檢查會話是否處於活動狀態。 此外,使用session_regenerate_id來阻止會話固定。

真正復雜的類型可能會嘗試序列化 User對象並將其存儲在$_SESSION 然后,在每個頁面加載時,可以對User對象的屬性進行封送 (反序列化),並使其在User對象的新實例中再次生效。 在那個世界中,您只需檢查User對象的屬性,看看他或她是否(a)登錄並(b)訂閱。 User對象的狀態將是您關心的問題,而不僅僅是$_SESSION超全局中的隔離值。

PHP手冊: 對象序列化

書: 面向對象思想過程(第4版) :見第12章

在login.php上你已經從DB獲得expiration_date ,你可以將它存儲在會話變量中

$_SESSION['expiration_date'] = $row['expiration_date'];

在每個頁面上,您可以使用上面的值來檢查而不是查詢數據庫

if (isset($_SESSION['expiration_date']) && (strtotime($_SESSION['expiration_date']) < (strtotime("now") ))

PHP會話模型允許啟動會話並稍后獲取所有數據,這些數據與序列化並存儲在$_SESSION數組中的會話有關。

擔心導致每個頁面訪問的數據庫調用是正確的。 會話數據可以使用cookie設置,也可以在POSTGET方法中返回。 因此,您需要定義適合的PHP會話ID retreival和會話生命周期的方法。

$_SESSION['name'] = 'value'; // Stores Session values to avoid database requests
...
session_id($sid); // sets the id of existing session
session_start(); // retreive stored session data;

您可以設置一次registeredsubscribed標記,以便稍后使用,在會話生命周期或特殊操作期間沒有數據庫請求:

  • 訂閱 - 未預期的行動。 用戶已滿足所有期望。
  • 已注冊 - 只能訂閱。 因此,在滿足訂閱操作之前,用戶環境可以使數據庫空閑直到訂閱請求。
  • 未注冊 - 只能注冊。 沒有存儲的數據。 沒有數據庫請求。

會話啟動時, subscribedregistered標志都將設置為FALSE直到驗證通過。 所以,我的意見 - 每個會話只需要調用三次數據庫:用戶進行身份驗證,注冊或訂閱時。 如代碼所示,您通過POST傳輸數據。 sid隱藏字段添加到所有表單以保持會話ID。 並且從數據庫中SELECT所有需要的數據(當然不是密碼),這些數據在會話期間與用戶交互時非常有用。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM