PHP 用户类(登录/注销/注册)

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4707053/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-25 14:04:33  来源:igfitidea点击:

PHP user class (login/logout/signup)

phpoopclasslogin

提问by Nouman

Started experimenting with building classes, and I've began by converting my user registration/login into a single class. Wanted to stop and ask for feedback before getting too far.

开始尝试构建类,我开始将我的用户注册/登录转换为单个类。在走得太远之前想停下来征求反馈。

class UserService
{
    private $_email;
    private $_password;

    public function login($email, $password)
    {
        $this->_email = mysql_real_escape_string($email);
        $this->_password = mysql_real_escape_string($password);

        $user_id = $this->_checkCredentials();
        if($user_id){
            $_SESSION['user_id'] = $user_id;
            return $user_id;
        }
        return false;
    }

    protected function _checkCredentials()
    {
        $query = "SELECT *
                    FROM users
                    WHERE email = '$this->_email'";
        $result = mysql_query($query);
        if(!empty($result)){
            $user = mysql_fetch_assoc($result);
            $submitted_pass = sha1($user['salt'] . $this->_password);
            if($submitted_pass == $user['password']){
                return $user['id'];
            }
        }
        return false;
    }   
}

One of the questions I do have related to my class is: should I be building it as this:

我确实与我的课程相关的问题之一是:我应该这样构建它吗:

$User = new UserService();
$User->login($_POST['email'], $_POST['password']);

Where the login method calls the _checkCredentials method automatically. Or should it be built like:

其中 login 方法会自动调用 _checkCredentials 方法。或者它应该像这样构建:

$User = new UserService();
$UserId = $User->checkCredentials($_POST['email'], $_POST['password']);
$User->login($UserId);

Other than that I've love some tips on how to restructure this and please point out anything I'm doing wrong!

除此之外,我喜欢一些有关如何重构它的技巧,请指出我做错的任何地方!

thanks guys

谢谢你们

回答by netcoder

I think your main idea was to separate the user handling (session) from the database query, which is a good thing in my opinion.

我认为您的主要想法是将用户处理(会话)与数据库查询分开,在我看来这是一件好事。

However, this is not the case with your actual implementation, because loginescapes the data to be sent to the database, even if the rest of the method does not having anything to do with databases. Not to say that your database query depends on a global resource to work. While I'm at it, I will also suggest you use PDO.

但是,您的实际实现并非如此,因为login转义要发送到数据库的数据,即使该方法的其余部分与数据库没有任何关系。并不是说您的数据库查询依赖于全局资源来工作。当我在做的时候,我也会建议你使用 PDO。

Also, your properties $_emailand $_passwordare in the private scope, but are to be accessed by a protected method. This may cause problems. The properties and the method should have equivalent visibility.

此外,您的属性$_email$_password在私有范围内,但将由受保护的方法访问。这可能会导致问题。属性和方法应该具有等效的可见性

Now, I can see that your UserServicerequires three things: a database handler, an email and a password. It would make sense to put it in a constructor.

现在,我可以看到您UserService需要三样东西:数据库处理程序、电子邮件和密码。将它放在构造函数中是有意义的。

Here's how I would do it:

这是我将如何做到的:

class UserService
{
    protected $_email;    // using protected so they can be accessed
    protected $_password; // and overidden if necessary

    protected $_db;       // stores the database handler
    protected $_user;     // stores the user data

    public function __construct(PDO $db, $email, $password) 
    {
       $this->_db = $db;
       $this->_email = $email;
       $this->_password = $password;
    }

    public function login()
    {
        $user = $this->_checkCredentials();
        if ($user) {
            $this->_user = $user; // store it so it can be accessed later
            $_SESSION['user_id'] = $user['id'];
            return $user['id'];
        }
        return false;
    }

    protected function _checkCredentials()
    {
        $stmt = $this->_db->prepare('SELECT * FROM users WHERE email=?');
        $stmt->execute(array($this->email));
        if ($stmt->rowCount() > 0) {
            $user = $stmt->fetch(PDO::FETCH_ASSOC);
            $submitted_pass = sha1($user['salt'] . $this->_password);
            if ($submitted_pass == $user['password']) {
                return $user;
            }
        }
        return false;
    }

    public function getUser()
    {
        return $this->_user;
    }
}

Then use it as such:

然后像这样使用它:

$pdo = new PDO('mysql:dbname=mydb', 'myuser', 'mypass');

$userService = new UserService($pdo, $_POST['email'], $_POST['password']);
if ($user_id = $userService->login()) {
    echo 'Logged it as user id: '.$user_id;
    $userData = $userService->getUser();
    // do stuff
} else {
    echo 'Invalid login';
}

回答by Alfred

I have said this a lot on stackoverflow before but what I think you are doing wrong is that you againare trying to create a login-system(even Jeff Atwood agreeswith me on this) which is probably going to be unsafe. Just to name a few things that could go wrong:

我之前在 stackoverflow 上说过很多,但我认为你做错的是你再次尝试创建一个登录系统(即使 Jeff Atwood同意我的观点),这可能是不安全的。仅列举一些可能出错的事情:

  • You don't do authentication over safe connection(https) which means that that your username/password could be sniffed from the wire.
  • It could have XSS-hole.
  • The passwords aren't stored safein the database because of incorrect use of salt. You aren 't a security expert so I don't think you should even store such sensitive information in your database anyway!
  • It has a CSRF-hole.
  • 您不会通过安全连接 (https) 进行身份验证,这意味着您的用户名/密码可能会从网络中嗅出。
  • 它可能有 XSS 漏洞。
  • 由于不正确使用盐,密码没有安全存储在数据库中。您不是安全专家,所以我认为您无论如何都不应该将如此敏感的信息存储在您的数据库中!
  • 它有一个CSRF洞。

Then there is the annoyance that we have yet to create another account on your server. You could and should avoid this hassle by using one of the freely available alternatives which have been tested for security vulnerabilities by experts:

然后是我们尚未在您的服务器上创建另一个帐户的烦恼。您可以并且应该通过使用一种免费的替代方案来避免这种麻烦,这些替代方案已经过专家的安全漏洞测试:

  • openid => Lightopenidis a really easy library to use/integrate. Even stackoverflow/jeff atwood is using it because he knows it hard to get login-system correctly. Even if you are a security expert.
  • google friend connect.
  • facebook connect.
  • twitter single sign-in.
  • openid => Lightopenid是一个非常容易使用/集成的库。甚至 stackoverflow/jeff atwood 也在使用它,因为他知道很难正确获得登录系统。即使您是安全专家。
  • 谷歌好友连接。
  • 脸书连接。
  • 推特单点登录。

So safe yourself the time of again devising another login-system and instead use for example the really simple lightopenid library and let users sign in with there google account. The snippet below is the only code you need to get it working:

因此,再次设计另一个登录系统的时间可以确保自己安全,而是使用例如非常简单的 lightopenid 库,让用户使用那里的 google 帐户登录。下面的代码片段是您让它工作所需的唯一代码:

<?php
# Logging in with Google accounts requires setting special identity, so this example shows how to do it.
require 'openid.php';
try {
    $openid = new LightOpenID;
    if(!$openid->mode) {
        if(isset($_GET['login'])) {
            $openid->identity = 'https://www.google.com/accounts/o8/id';
            header('Location: ' . $openid->authUrl());
        }
?>
<form action="?login" method="post">
    <button>Login with Google</button>
</form>
<?php
    } elseif($openid->mode == 'cancel') {
        echo 'User has canceled authentication!';
    } else {
        echo 'User ' . ($openid->validate() ? $openid->identity . ' has ' : 'has not ') . 'logged in.';
    }
} catch(ErrorException $e) {
    echo $e->getMessage();
}

回答by Secko

It depends on your design and the notion of a more simpler solution, and how you want to organize your code and keep it simpler, smaller and make it maintainable at the same time.

这取决于您的设计和更简单解决方案的概念,以及您希望如何组织代码并使其更简单、更小并同时使其可维护。

Ask yourself why you would call checkCredentialsand then call loginand then maybe some other method. Do you have a good reason for doing so, is this a good design, what do I want to achieve with this.

问问自己为什么要先调用checkCredentials然后调用login,然后再调用其他方法。你这样做有充分的理由吗,这是一个好的设计,我想用它实现什么。

Calling just login, and performing every login operation is much more simple and elegant. Giving it another name while doing so is much more understandable and also more maintainable.

调用 just login,执行每一次登录操作都更加简单优雅。在这样做的同时给它另一个名字更容易理解,也更易于维护。

If you ask me, I would use a constructor.

如果你问我,我会使用构造函数。

回答by Dennis Kreminsky

The answer really depends on other architecture considerations, such as whether the checkCredentials() method needs to be available outside of the class scope for other system elements. If its sole purpose is to be called by login() method, consider combining the two into just one method.

答案实际上取决于其他架构考虑因素,例如 checkCredentials() 方法是否需要在其他系统元素的类范围之外可用。如果其唯一目的是通过 login() 方法调用,请考虑将两者合并为一个方法。

One more recommendation I might have is to use verbs in naming methods, meaning that 'login' may be too general for understanding its effects at first glance.

我可能还有一个建议是在命名方法中使用动词,这意味着“登录”可能过于笼统,乍一看无法理解其效果。