[英]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 '';
}
在代碼中的每次更改之前都有注釋。 以下是我所做的更改的簡短列表:
我會做一些其他的小調整(在每個文件的頂部移動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
設置,也可以在POST
或GET
方法中返回。 因此,您需要定義適合的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;
您可以設置一次registered
和subscribed
標記,以便稍后使用,在會話生命周期或特殊操作期間沒有數據庫請求:
會話啟動時, subscribed
和registered
標志都將設置為FALSE
直到驗證通過。 所以,我的意見 - 每個會話只需要調用三次數據庫:用戶進行身份驗證,注冊或訂閱時。 如代碼所示,您通過POST
傳輸數據。 將sid
隱藏字段添加到所有表單以保持會話ID。 並且從數據庫中SELECT
所有需要的數據(當然不是密碼),這些數據在會話期間與用戶交互時非常有用。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.