[英]advanced user authentication using sessions
I am setting up a website that has 3 types of users: 我正在建立一个有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)
I've created code that identifies these 3 kinds of users and acts appropriately. 我已经创建了识别这3种用户的代码并且行为恰当。 My question is, is this the way to go?
我的问题是,这是要走的路吗? I've never done similar thing before.
我以前从未做过类似的事情。 Or should I reprogram my approach?
或者我应该重新编程我的方法?
//login.php //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.";
}
Then on every single page on my website I use this code, to see what kind of user has visited me 然后在我网站的每一页上我都使用这段代码,看看有哪些用户访问过我
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;
}
The code works, but I am worried about accessing database on every single page once the user registers and is subscribed. 代码有效,但我担心一旦用户注册并订阅,就会在每个页面上访问数据库。 I am in effect penalizing users for registering and subscribing.
我实际上是在惩罚用户注册和订阅。 Is there a better, performance wise, way to handle this kind of problem?
是否有更好的,性能明智的方式来处理这类问题?
The solution here is to check for subscribed users (only on the first page where they login to your website) ie inside login.php
you could use, 这里的解决方案是检查订阅用户(仅在他们登录您网站的第一页),即您可以使用的
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.";
}
Then, on every other page, all you need to do is only check for $_SESSION['subscribe_date']
instead of firing a query each time 然后,在每个其他页面上,您需要做的只是检查
$_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
}
Also, note that I have removed session_destroy();
另请注意,我已删除
session_destroy();
A very bad idea to call it on every page. 在每个页面上调用它是一个非常糟糕的主意。 You can unset session variables instead if needed.
如果需要,您可以取消设置会话变量。 ie
unset($_SESSION["email"]);
即未
unset($_SESSION["email"]);
You should call session_destroy();
你应该调用
session_destroy();
only at the end when the user logs out. 仅在用户注销时才结束。 Check out the warning-section for session_destroy()
查看session_destroy()的警告部分
If it was me, I would separate and refactor these codes into external classes/functions. 如果是我,我会将这些代码分离并重构为外部类/函数。 But if we are talking about minor tweeks to your code, here is what would I do:
但是如果我们在谈论你的代码的小问题,那么我将做什么:
login.php 的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.";
}
included code on every page 每页都包含代码
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 '';
}
There are comments before each change in your code. 在代码中的每次更改之前都有注释。 Here is short list of changes I've made:
以下是我所做的更改的简短列表:
There are some other minor tweaks I would do (move date_default_timezone_set on top of every file etc.), but they are not topic of your question. 我会做一些其他的小调整(在每个文件的顶部移动date_default_timezone_set等),但它们不是你问题的主题。
In my understanding, You already have a working code. 根据我的理解,你已经有了一个有效的代码。 And what you are asking is opinion.
你要问的是意见。 You want to remove duplication in each page of checking into database for authentication and subscription.
您希望在检查数据库的每个页面中删除重复以进行身份验证和订阅。
In my opinion, you need to change how you use sessions , 在我看来,你需要改变你使用会话的方式,
$_session['email'] // email address of user
$_session['auth_type'] // holds authentication type
$_session['auth_till'] // subscription expire date
Then lets create function to check subscription. 然后让我们创建函数来检查订阅。 This function can be put into a separate file for example: init.php.
此函数可以放在单独的文件中,例如:init.php。 Here we can put session start mechanism so that in any case sessions will be available.
在这里,我们可以设置会话启动机制,以便在任何情况下都可以使用会话。
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
}
And on login.php use same technique, while setting up sessions authentication type. 并且在login.php上使用相同的技术,同时设置会话身份验证类型。
Now any other page can simply use function to check subscription 现在任何其他页面都可以简单地使用函数来检查订阅
for example: 例如:
<?php
// file: product.php
include_once("init.php");
if(!checkSubscription()){
// subscription finished. do what you need to do. otherwise continue.
}
There are many improvements that can be done to your codes. 您的代码可以进行许多改进。 But i think this would meet your needs.
但我认为这将满足您的需求。 Please let me know if you need any further assistant.
如果您需要任何其他助手,请告诉我。 Please visit Scape and let me know if any useful coding available there.
请访问Scape ,如果有任何有用的编码,请告诉我。
Use a PHP session variable to store information about the current user before making SQL queries on every page load. 在对每个页面加载进行SQL查询之前,使用PHP会话变量来存储有关当前用户的信息。
For example, when the user logs in, save the user id to that users session in a session variable: 例如,当用户登录时,将用户ID保存到会话变量中的该用户会话:
$_SESSION['user_id'] = <userid>;
On the next page load, check for that session variable before running the database query. 在下一页加载时,在运行数据库查询之前检查该会话变量。
if ($_SESSION['user_id']) {
// normal operations
} else {
// Make your database call
}
Generally speaking, when a user logs in for the first time, set two state flags in $_SESSION
: 一般来说,当用户第一次登录时,在
$_SESSION
设置两个状态标志:
$_SESSION['loggedIn'] = true
$_SESSION['subscribed'] = bool //true or false
I assume that only registered users can login. 我假设只有注册用户才能登录。 If a user does something to change his or her state, update
$_SESSION
accordingly. 如果用户做了某些事情来改变他或她的状态,请相应地更新
$_SESSION
。
Take note, be sure to check that a session is active before checking values. 请注意,在检查值之前,请务必检查会话是否处于活动状态。 Also, use
session_regenerate_id
to deter session fixation. 此外,使用
session_regenerate_id
来阻止会话固定。
Truly sophisticated types might try serializing a User
object and storing it in $_SESSION
. 真正复杂的类型可能会尝试序列化
User
对象并将其存储在$_SESSION
。 Then, on each page load, the User
object's properties can be marshaled (unserialized) and made to come alive once more in a new instance of a User
object. 然后,在每个页面加载时,可以对
User
对象的属性进行封送 (反序列化),并使其在User
对象的新实例中再次生效。 In that world, you would just check the properties of the User
object to see if he or she is (a) logged in and (b) subscribed. 在那个世界中,您只需检查
User
对象的属性,看看他或她是否(a)登录并(b)订阅。 The state of the User
object would be your concern, not just isolated values in the $_SESSION
superglobal. User
对象的状态将是您关心的问题,而不仅仅是$_SESSION
超全局中的隔离值。
PHP Manual: Object Serialization PHP手册: 对象序列化
Book: The Object Oriented Thought Process (4th Edition) : See Chapter 12 书: 面向对象思想过程(第4版) :见第12章
on login.php you already getting expiration_date
from the DB, you could store it in session variable 在login.php上你已经从DB获得
expiration_date
,你可以将它存储在会话变量中
$_SESSION['expiration_date'] = $row['expiration_date'];
and on every page you could use the above value to check instead of querying the DB 在每个页面上,您可以使用上面的值来检查而不是查询数据库
if (isset($_SESSION['expiration_date']) && (strtotime($_SESSION['expiration_date']) < (strtotime("now") ))
PHP session model allows to start the session and fetch later all data, concerning to the session that was serialized and stored in $_SESSION
array. PHP会话模型允许启动会话并稍后获取所有数据,这些数据与序列化并存储在
$_SESSION
数组中的会话有关。
It is right to worry about database calls that cause each page access. 担心导致每个页面访问的数据库调用是正确的。 The session data can be set with
cookie
or given back inside POST
or GET
method. 会话数据可以使用
cookie
设置,也可以在POST
或GET
方法中返回。 So, you need to define suitable method of PHP session id retreival and session lifetime. 因此,您需要定义适合的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;
You can set once such flags as registered
and subscribed
for beinh used later with no database requests during session lifetime or special actions: 您可以设置一次
registered
和subscribed
标记,以便稍后使用,在会话生命周期或特殊操作期间没有数据库请求:
When session starts, both subscribed
and registered
flags will be set to FALSE
until authentication passed. 会话启动时,
subscribed
和registered
标志都将设置为FALSE
直到验证通过。 So, my opinion - database must be called only three times per session: when user authenticates, registers or subscribes. 所以,我的意见 - 每个会话只需要调用三次数据库:用户进行身份验证,注册或订阅时。 As code shows, you transfer data via
POST
. 如代码所示,您通过
POST
传输数据。 Add sid
hidden field to all forms to keep session id. 将
sid
隐藏字段添加到所有表单以保持会话ID。 And SELECT
from database all needed data (not password, of course) that can be useful during the session to interact with user. 并且从数据库中
SELECT
所有需要的数据(当然不是密码),这些数据在会话期间与用户交互时非常有用。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.