我正在用Codeigniter 3.1.8和Bootstrap 4编写一个基本的博客应用程序。
应用程序允许注册和登录。我对我所设立的注册制度的保安水平表示关注。
登记册主计长:
class Register extends CI_Controller {
public function __construct()
{
parent::__construct();
}
public function index() {
$data = $this->Static_model->get_static_data();
$data['pages'] = $this->Pages_model->get_pages();
$data['tagline'] = 'Want to write for ' . $data['site_title'] . '? Create an account.';
$data['categories'] = $this->Categories_model->get_categories();
$this->form_validation->set_rules('first_name', 'First name', 'required');
$this->form_validation->set_rules('last_name', 'Last name', 'required');
$this->form_validation->set_rules('email', 'Email', 'required|trim|valid_email');
$this->form_validation->set_rules('password', 'Password', 'required|min_length[6]');
$this->form_validation->set_rules('cpassword', 'Confirm password', 'required|matches[password]');
$this->form_validation->set_rules('terms', 'Terms and Conditions', 'required', array('required' => 'You have to accept the Terms and Conditions'));
$this->form_validation->set_error_delimiters('', '');
// If validation fails
if ($this->form_validation->run() === FALSE) {
$this->load->view('partials/header', $data);
$this->load->view('auth/register');
$this->load->view('partials/footer');
} else {
// If the provided email does not already
// exist in the authors table, register user
if (!$this->Usermodel->email_exists()) {
// Encrypt the password
$enc_password = md5($this->input->post('password'));
// Give the first author admin privileges
if ($this->Usermodel->get_num_rows() < 1) {
$active = 1;
$is_admin = 1;
} else {
$active = 0;
$is_admin = 0;
}
// Register user
$this->Usermodel->register_user($enc_password, $active, $is_admin);
if ($this->Usermodel->get_num_rows() == 1) {
$this->session->set_flashdata('user_registered', "You are now registered as an admin. You can sign in");
} else {
$this->session->set_flashdata('user_registered', "You are now registered. Your account needs the admin's aproval before you can sign in.");
}
redirect('login');
} else {
// The user is already registered
$this->session->set_flashdata('already_registered', "The email you provided already exists in our database. Please login.");
redirect('login');
}
}
}
}
用户模型模型:
class Usermodel extends CI_Model {
public function email_exists() {
$query = $this->db->get_where('authors', ['email' => $this->input->post('email')]);
return $query->num_rows() > 0;
}
public function get_num_rows() {
$query = $this->db->get('authors');
return $query->num_rows();
}
public function getAuthors(){
$query = $this->db->get('authors');
return $query->result();
}
public function deleteAuthor($id) {
return $this->db->delete('authors', array('id' => $id));
}
public function activateAuthor($id) {
$author = null;
$updateQuery = $this->db->where(['id' => $id, 'is_admin' => 0])->update('authors', array('active' => 1));
if ($updateQuery !== false) {
$authorQuery = $this->db->get_where('authors', array('id' => $id));
$author = $authorQuery->row();
}
return $author;
}
public function deactivateAuthor($id) {
$author = null;
$updateQuery = $this->db->where(['id' => $id, 'is_admin' => 0])->update('authors', array('active' => 0));
if ($updateQuery !== false) {
$authorQuery = $this->db->get_where('authors', array('id' => $id));
$author = $authorQuery->row();
}
return $author;
}
public function register_user($enc_password, $active, $is_admin) {
// User data
$data = [
'first_name' => $this->input->post('first_name'),
'last_name' => $this->input->post('last_name'),
'email' => $this->input->post('email'),
'password' => $enc_password,
'register_date' => date('Y-m-d H:i:s'),
'active' => $active,
'is_admin' => $is_admin
];
return $this->db->insert('authors', $data);
}
public function user_login($email, $password)
{
$query = $this->db->get_where('authors', ['email' => $email, 'password' => md5($password)]);
return $query->row();
}
}
<#>更新:
我决定从Login控制器中发布login()
方法,因为更改Register
类将需要相应地更改登录:
public function login() {
$this->form_validation->set_rules('email', 'Email', 'required|trim|valid_email');
$this->form_validation->set_rules('password', 'Password', 'required|trim');
$this->form_validation->set_error_delimiters('', '');
if ($this->form_validation->run()) {
$email = $this->input->post('email');
$password = $this->input->post('password');
$this->load->model('Usermodel');
$current_user = $this->Usermodel->user_login($email, $password);
// If we find a user
if ($current_user) {
// If the user found is active
if ($current_user->active == 1) {
$this->session->set_userdata(
array(
'user_id' => $current_user->id,
'user_email' => $current_user->email,
'user_first_name' => $current_user->first_name,
'user_is_admin' => $current_user->is_admin,
'user_active' => $current_user->active,
'is_logged_in' => TRUE
)
);
// After login, display flash message
$this->session->set_flashdata('user_signin', 'You have signed in');
//and redirect to the posts page
redirect('/dashboard');
} else {
// If the user found is NOT active
$this->session->set_flashdata("login_failure_activation", "Your account has not been activated yet.");
redirect('login');
}
} else {
// If we do NOT find a user
$this->session->set_flashdata("login_failure_incorrect", "Incorrect email or password.");
redirect('login');
}
}
else {
$this->index();
}
}
寻求反馈和改进的想法。
发布于 2019-09-01 04:27:41
有很多事情我会以不同的方式去做,第一件是必须。
首先,使用md5()
对密码进行编码被普遍认为是一种糟糕的做法。至少从2004年起,如果不是更早的话,它就被认为太弱了。相反,使用密码_哈希函数。
我会在模型中加密密码。事实上,我会将几乎所有的注册逻辑移到模型上。这是我的Usermodel::register_user()
版本
public function register_user()
{
// Get the entire $_POST array with one call then extract what we need
$posted = $this->input->post();
$field_list = ['first_name', 'last_name', 'email', 'password'];
foreach($field_list as $field){
$data[$field] = $posted[$field];
}
$enc_password = password_hash($data['password']);
// if password_hash fails it's time to bail
if( ! $enc_password)
{
return false;
}
// update $data with encrypted password
$data['password'] = $enc_password;
// Put additional items in the $data array
$data['register_date'] = date('Y-m-d H:i:s');
// CodeIgniter has a method to count records - use it. Remove get_num_rows() from model
$data['is_admin'] = $this->db->count_all('authors') === 0 ? 1 : 0;
$data['active'] = $data['is_admin'];
if($inserted = $this->db->insert('authors', $data) === TRUE)
{
if($data['is_admin'] === 1)
{
$msg = "You are now registered as an admin. You can sign in";
}
else
{
$msg = "You are now registered. Your account needs the admin's approval before you can sign in.";
}
$this->session->set_flashdata('user_registered', $msg);
}
return $inserted;
}
通过这个新方法和控制器中的一些重新排列,我们可以使代码更简洁一些。
public function index()
{
$this->form_validation
->set_rules('first_name', 'First name', 'required')
->set_rules('last_name', 'Last name', 'required')
->set_rules('email', 'Email', 'required|trim|valid_email')
->set_rules('password', 'Password', 'required|min_length[6]')
->set_rules('cpassword', 'Confirm password', 'required|matches[password]')
->set_rules('terms', 'Terms and Conditions', 'required',
array('required' => 'You have to accept the Terms and Conditions'))
->set_error_delimiters('', '');
if($this->form_validation->run())
{
// If the provided email isn't on record then register user
if( ! $this->Usermodel->email_exists())
{
// Register user
if($this->Usermodel->register_user())
{
// worked - go to login
redirect('login');
}
// register_user() returned false - set an error message for use in 'auth/register' view
$data['registration_error_msg'] = "Registration Failed! Please contact Administrator.";
}
else
{
// The user is already registered
$this->session->set_flashdata('user_registered',
"The email you provided already exists in our database. Please login.");
redirect('login');
}
}
// Validation or registration failed or it's the first load of this page
$data = $this->Static_model->get_static_data();
$data['pages'] = $this->Pages_model->get_pages();
$data['tagline'] = 'Want to write for '.$data['site_title'].'? Create an account.';
$data['categories'] = $this->Categories_model->get_categories();
$this->load->view('partials/header', $data);
$this->load->view('auth/register');
$this->load->view('partials/footer');
}
使用password_hash()
也意味着需要重构登录逻辑,并且必须使用password_verify()
函数。password_verify
-确认密码与该密码的散列匹配。考虑到这一点,`Usermodel::user_login可能看起来是这样的。
public function user_login()
{
$email = filter_var($this->input->post('email'), FILTER_SANITIZE_EMAIL);
if(filter_var($email, FILTER_VALIDATE_EMAIL))
{
$user = $this->db
->select('password, user_id, active')
->get_where('authors', ['email' => $email])
->row();
if(password_verify($this->input->post('password'), $user->password))
{
if($user->active)
{
return $user->user_id;
}
$this->session->set_flashdata("login_failure_activation", "Your account has not been activated yet.");
}
else
{
$this->session->set_flashdata("login_failure_incorrect", "Incorrect email or password.");
}
}
return false;
}
上面的内容将意味着控制器的login()
方法也需要重新工作。
一个不可打破的安全规则是“永不信任用户输入!”
因此,发送到服务器的任何数据都必须经过消毒和验证。即使是某些标识符(如user_id)也是如此,而这些标识符是您在屏幕上放置的URL的一部分。考虑到这一点,您应该始终(最低限度)确保任何具有$id
参数的模型函数都得到正确的数据类型。
让我们假设您的user_id类型是整数。例如,激活作者的控制器方法可能在将其发送到模型之前进行这种检查。
public function activate_author($id)
{
if(filter_var($id, FILTER_VALIDATE_INT))
{
$this->Usermodel->activateAuthor($id)
// other code
}
else
{
//respond in a way that doesn't give a bad-actor too much info
}
}
虽然CodeIgniter的验证库很好,但它还不够深入。尤其是在消毒的时候。建议认真研究过滤器上的文档。还有很多关于使用filter_var
的在线教程。
关于PHP安全性的进一步阅读:
发布于 2019-09-02 18:55:47
DFriend的回答很好,但听起来需要更清楚地说明如何使用内置的password_hash
函数,还有一些可以改进的体系结构。
电子邮件地址是authors
表的主键吗?这不是一个很好的选择,但我们会接受的。
要正确使用密码_哈希,还必须使用密码_验证和密码_需求_重散。这意味着您不能仅仅重新散列密码并在数据库中查找哈希。您必须识别数据库中的用户行,将存储的散列加载到PHP层中,并根据所提供的密码检查它。我还没有测试过下面的内容,但这或多或少是我写它的方式。
class Password_helper {
const ALGO = PASSWORD_DEFAULT;
public static set_password(CI_Controller $CI, string $email, string $p):bool{
// We could do any password-strength rules or setting of optional arguments here,
// but I'll assume you're not doing that.
return $CI->db->update(
'authors',
['password' => password_hash($p, self::ALGO)],
['email' => $email]);
}
public static check_and_upkeep(CI_Controller $CI, string $email, string $p):bool{
$_user = $CI->db->get_where(
'authors',
['email' => $email],
1)
->result();
if($_user){
$user = $_user[0];
if(password_verify($p, $user->password)){
if(password_needs_rehash($user->password, self::ALGO)){
self::set_password($CI, $email, $p);
}
return true;
}
else{
return false;
}
}
else{
return false;
}
}
}
以上可能与您正在使用的“模型”的概念并不完美;CodeIgniter的“模型”概念坦率地说是有问题的。另外,要使用上面的内容,您需要先将用户插入数据库,然后设置他们的密码。这很好,您可以暂时将他们的密码设置为"“。
使第一作者成为(an?)管理员不是很好。假设有可能有人攻击你,不管怎么说,它会永远把代码库弄乱。根据您所说的"admin“的含义,您甚至不应该将"admins”与“用户”放在同一个表中。无论如何,将整个业务从注册堆栈中删除;您可以使用其他工具手动设置数据库中的第一个管理员。
index()
)的一个关键和特定角色是解析、清理和验证输入,所有这些都是如此。您和DFriend都在模型层中向下读取POST
值;这些值应该作为参数传递下来。如果参数的数量开始感到超凡脱俗,您可以为不同的数据结构(如作者)创建类(“模型”)。$this->load->view()
调用以及相关的$data
构建。所有这些都放在一个地方,或者作为Controller基类的方法,或者作为Loader扩展类的方法。还请注意,您可以从视图内部加载视图,因此一个主模板可能比单独的页眉和页脚“部分”视图更好。$this->Usermodel->get_num_rows() == 1
来检查用户是管理员还是活动的;此时您应该有用户的数据,并且能够进行字面上的检查。login()
端点方法将调用index()
端点方法。这是个奇怪的选择。$inserted
分配一个值。别干那事。redirect
或load->view()
来停止执行。即使它起作用了,也会使事情更难读懂。使用显式返回语句,或者将备用路径放置在显式else
中,或者两者都使用。你提出了几点意见,这听起来像是你希望得到回应。
redirect()
停止执行,但您必须知道并记住这一点,才能理解依赖它的代码。CI->load->view()
没有停止执行,但几乎总是流中的最后一个调用。在所有这些事件之后抛出一个return;
有点冗长,但这是显而易见的。if
失败时才运行代码,那么不管您是否这样说,您都在其他部分。https://codereview.stackexchange.com/questions/226795
复制相似问题