使用会话进行高级用户验证

时间:2017-02-21 21:40:52

标签: php session login

我正在建立一个有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;
}

代码有效,但我担心一旦用户注册并订阅,就会在每个页面上访问数据库。我实际上是在惩罚用户注册和订阅。 是否有更好的,性能明智的方式来处理这类问题?

7 个答案:

答案 0 :(得分:2)

此处的解决方案是检查订阅的用户(仅在他们登录您网站的第一页),即您可以使用的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()

的警告部分

答案 1 :(得分:1)

如果是我,我会将这些代码分离并重构为外部类/函数。但是,如果我们正在谈论您的代码的小问题,我将采取以下措施:

<强>的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 - Should I call exit() after calling Location: header?
  • 不要在标题
  • 之前回显输出
  • 为每个经过身份验证的用户使用3个会话验证变量 - 经过身份验证,订阅和发送电子邮件
  • 如果用户未订阅,则
  • 不销毁整个会话,而是使用取消设置
  • 我已将两个经过身份验证的用户(已订阅和已取消订阅)合并为一个if语句
  • 我已在两个后续步骤中分离过期验证并输出回显
  • 在每个页面上我正在检查订阅的用户是否仍然订阅(订阅可以在他的会话期间到期),如果取消订阅没有同时订阅(他可以使用两个具有活动会话的浏览器)

我会做一些其他的小调整(在每个文件的顶部移动date_default_timezone_set等),但它们不是你问题的主题。

答案 2 :(得分:1)

据我了解,您已经有了一个可行的代码。你要问的是意见。您希望在检查数据库的每个页面中删除重复以进行身份​​验证和订阅。

在我看来,您需要更改使用会话的方式,

 $_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并告知我们是否有任何有用的编码。

答案 3 :(得分:1)

在每次加载页面之前,使用PHP会话变量存储有关当前用户的信息。

例如,当用户登录时,将用户ID保存到会话变量中的该用户会话:

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

在下一页加载时,在运行数据库查询之前检查该会话变量。

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

答案 4 :(得分:1)

一般来说,当用户第一次登录时,在$_SESSION中设置两个状态标志:

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

我假设只有注册用户才能登录。如果用户做了某些事情来改变他或她的状态,请相应地更新$_SESSION

请注意,在检查值之前,请务必检查会话是否处于活动状态。另外,使用session_regenerate_id来阻止会话固定。

真正复杂的类型可能会尝试序列化一个User对象并将其存储在$_SESSION中。然后,在每个页面加载时,User对象的属性可以封送(未序列化),并在User对象的新实例中再次生效。在那个世界中,您只需检查User对象的属性,看看他或她是否(a)已登录并且(b)已订阅。 User对象的状态将是您的关注点,而不仅仅是$_SESSION超全局中的孤立值。

PHP手册:Object Serialization

书:The Object Oriented Thought Process (4th Edition):见第12章

答案 5 :(得分:1)

在login.php上你已从数据库中获取expiration_date,你可以将它存储在会话变量中

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

并且在每个页面上都可以使用上面的值来检查而不是查询数据库

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

答案 6 :(得分:1)

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来自数据库所有需要的数据(当然不是密码),在会话期间与用户交互时非常有用。