Mysql
 sql >> Teknologi Basis Data >  >> RDS >> Mysql

Manajemen akun pengguna, peran, izin, otentikasi PHP dan MySQL - Bagian 2

Ini adalah bagian kedua dari seri sistem manajemen akun pengguna, otentikasi, peran, izin. Anda dapat menemukan bagian pertama di sini.

Konfigurasi Basis Data

Buat database MySQL yang disebut akun pengguna. Kemudian di folder root proyek Anda (folder akun pengguna), buat file dan beri nama config.php. File ini akan digunakan untuk mengkonfigurasi variabel database dan kemudian menghubungkan aplikasi kita ke database MySQL yang baru saja kita buat.

config.php:

<?php
	session_start(); // start session
	// connect to database
	$conn = new mysqli("localhost", "root", "", "user-accounts");
	// Check connection
	if ($conn->connect_error) {
	    die("Connection failed: " . $conn->connect_error);
	}
  // define global constants
	define ('ROOT_PATH', realpath(dirname(__FILE__))); // path to the root folder
	define ('INCLUDE_PATH', realpath(dirname(__FILE__) . '/includes' )); // Path to includes folder
	define('BASE_URL', 'http://localhost/user-accounts/'); // the home url of the website
?>

Kami juga telah memulai sesi karena kami akan perlu menggunakannya nanti untuk menyimpan informasi pengguna yang masuk seperti nama pengguna. Di akhir file, kami mendefinisikan konstanta yang akan membantu kami menangani file yang disertakan dengan lebih baik.

Aplikasi kita sekarang terhubung ke database MySQL. Mari buat formulir yang memungkinkan pengguna memasukkan detail dan mendaftarkan akunnya. Buat file signup.php di folder root proyek:

pendaftaran.php:

<?php include('config.php'); ?>
<?php include(INCLUDE_PATH . '/logic/userSignup.php'); ?>
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>UserAccounts - Sign up</title>
  <!-- Bootstrap CSS -->
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" />
  <!-- Custom styles -->
  <link rel="stylesheet" href="assets/css/style.css">
</head>
<body>
  <?php include(INCLUDE_PATH . "/layouts/navbar.php") ?>

  <div class="container">
    <div class="row">
      <div class="col-md-4 col-md-offset-4">
        <form class="form" action="signup.php" method="post" enctype="multipart/form-data">
          <h2 class="text-center">Sign up</h2>
          <hr>
          <div class="form-group">
            <label class="control-label">Username</label>
            <input type="text" name="username" class="form-control">
          </div>
          <div class="form-group">
            <label class="control-label">Email Address</label>
            <input type="email" name="email" class="form-control">
          </div>
          <div class="form-group">
            <label class="control-label">Password</label>
            <input type="password" name="password" class="form-control">
          </div>
          <div class="form-group">
            <label class="control-label">Password confirmation</label>
            <input type="password" name="passwordConf" class="form-control">
          </div>
          <div class="form-group" style="text-align: center;">
            <img src="http://via.placeholder.com/150x150" id="profile_img" style="height: 100px; border-radius: 50%" alt="">
            <!-- hidden file input to trigger with JQuery  -->
            <input type="file" name="profile_picture" id="profile_input" value="" style="display: none;">
          </div>
          <div class="form-group">
            <button type="submit" name="signup_btn" class="btn btn-success btn-block">Sign up</button>
          </div>
          <p>Aready have an account? <a href="login.php">Sign in</a></p>
        </form>
      </div>
    </div>
  </div>
<?php include(INCLUDE_PATH . "/layouts/footer.php") ?>
<script type="text/javascript" src="assets/js/display_profile_image.js"></script>

Pada baris pertama dalam file ini, kami menyertakan file config.php yang kami buat sebelumnya karena kami perlu menggunakan konstanta INCLUDE_PATH yang disediakan config.php di dalam file signup.php kami. Dengan menggunakan konstanta INCLUDE_PATH ini, kami juga menyertakan navbar.php, footer.php, dan userSignup.php yang menyimpan logika untuk mendaftarkan pengguna di database. Kami akan segera membuat file ini.

Di dekat akhir file, ada bidang bundar tempat pengguna dapat mengklik untuk mengunggah gambar profil. Saat pengguna mengklik area ini dan memilih gambar profil dari komputer mereka, pratinjau gambar ini pertama kali ditampilkan.

Pratinjau gambar ini dicapai dengan jquery. Ketika pengguna mengklik tombol unggah gambar, kami akan secara terprogram memicu bidang input file menggunakan JQuery dan ini menampilkan file komputer pengguna agar mereka dapat menelusuri komputer mereka dan memilih gambar profil mereka. Saat mereka memilih gambar, kami menggunakan Jquery untuk menampilkan gambar sementara. Kode yang melakukan ini ditemukan di file display_profile_image.php kami yang akan segera kami buat.

Jangan lihat di browser dulu. Pertama-tama mari kita berikan file ini apa yang menjadi hutang kita. Untuk saat ini, di dalam folder assets/css, mari buat file style.css yang kita tautkan di bagian kepala.

style.css:

@import url('https://fonts.googleapis.com/css?family=Lora');
* { font-family: 'Lora', serif; font-size: 1.04em; }
span.help-block { font-size: .7em; }
form label { font-weight: normal; }
.success_msg { color: '#218823'; }
.form { border-radius: 5px; border: 1px solid #d1d1d1; padding: 0px 10px 0px 10px; margin-bottom: 50px; }
#image_display { height: 90px; width: 80px; float: right; margin-right: 10px; }

Pada baris pertama file ini, kami mengimpor font Google bernama 'Lora' untuk membuat aplikasi kami memiliki font yang lebih indah.

File selanjutnya yang kita butuhkan dalam signup.php ini adalah file navbar.php dan footer.php. Buat dua file berikut di dalam folder include/layouts:

navbar.php:

<div class="container"> <!-- The closing container div is found in the footer -->
  <nav class="navbar navbar-default">
    <div class="container-fluid">
      <div class="navbar-header">
        <a class="navbar-brand" href="#">UserAccounts</a>
      </div>
      <ul class="nav navbar-nav navbar-right">
          <li><a href="<?php echo BASE_URL . 'signup.php' ?>"><span class="glyphicon glyphicon-user"></span> Sign Up</a></li>
          <li><a href="<?php echo BASE_URL . 'login.php' ?>"><span class="glyphicon glyphicon-log-in"></span> Login</a></li>
      </ul>
    </div>
  </nav>

footer.php:

    <!-- JQuery -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <!-- Bootstrap JS -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
  </div> <!-- closing container div -->
</body>
</html>

Baris terakhir dari file signup.php tertaut ke skrip JQuery bernama display_profile_image.js dan melakukan persis seperti namanya. Buat file ini di dalam folder assets/js dan tempel kode ini di dalamnya:

display_profile_image.js:

$(document).ready(function(){
  // when user clicks on the upload profile image button ...
  $(document).on('click', '#profile_img', function(){
    // ...use Jquery to click on the hidden file input field
    $('#profile_input').click();
    // a 'change' event occurs when user selects image from the system.
    // when that happens, grab the image and display it
    $(document).on('change', '#profile_input', function(){
      // grab the file
      var file = $('#profile_input')[0].files[0];
      if (file) {
          var reader = new FileReader();
          reader.onload = function (e) {
              // set the value of the input for profile picture
              $('#profile_input').attr('value', file.name);
              // display the image
              $('#profile_img').attr('src', e.target.result);
          };
          reader.readAsDataURL(file);
      }
    });
  });
});

Dan terakhir, file userSignup.php. File ini adalah tempat pengiriman data formulir pendaftaran untuk diproses dan disimpan dalam database. Buat userSignup.php di dalam folder includes/logic dan tempel kode ini di dalamnya:

userSignup.php:

<?php include(INCLUDE_PATH . "/logic/common_functions.php"); ?>
<?php
// variable declaration
$username = "";
$email  = "";
$errors  = [];
// SIGN UP USER
if (isset($_POST['signup_btn'])) {
	// validate form values
	$errors = validateUser($_POST, ['signup_btn']);

	// receive all input values from the form. No need to escape... bind_param takes care of escaping
	$username = $_POST['username'];
	$email = $_POST['email'];
	$password = password_hash($_POST['password'], PASSWORD_DEFAULT); //encrypt the password before saving in the database
	$profile_picture = uploadProfilePicture();
	$created_at = date('Y-m-d H:i:s');

	// if no errors, proceed with signup
	if (count($errors) === 0) {
		// insert user into database
		$query = "INSERT INTO users SET username=?, email=?, password=?, profile_picture=?, created_at=?";
		$stmt = $conn->prepare($query);
		$stmt->bind_param('sssss', $username, $email, $password, $profile_picture, $created_at);
		$result = $stmt->execute();
		if ($result) {
		  $user_id = $stmt->insert_id;
			$stmt->close();
			loginById($user_id); // log user in
		 } else {
			 $_SESSION['error_msg'] = "Database error: Could not register user";
		}
	 }
}

Saya menyimpan file ini untuk yang terakhir karena memiliki lebih banyak pekerjaan untuk itu. Hal pertama adalah kita menyertakan file lain bernama common_functions.php di bagian atas file ini. Kami menyertakan file ini karena kami menggunakan dua metode yang berasal darinya yaitu:validationUser() dan loginById() yang akan segera kami buat.

Buat file common_functions.php ini di folder include/logic Anda:

common_functions.php:

<?php
  // Accept a user ID and returns true if user is admin and false if otherwise
  function isAdmin($user_id) {
    global $conn;
    $sql = "SELECT * FROM users WHERE id=? AND role_id IS NOT NULL LIMIT 1";
    $user = getSingleRecord($sql, 'i', [$user_id]); // get single user from database
    if (!empty($user)) {
      return true;
    } else {
      return false;
    }
  }
  function loginById($user_id) {
    global $conn;
    $sql = "SELECT u.id, u.role_id, u.username, r.name as role FROM users u LEFT JOIN roles r ON u.role_id=r.id WHERE u.id=? LIMIT 1";
    $user = getSingleRecord($sql, 'i', [$user_id]);

    if (!empty($user)) {
      // put logged in user into session array
      $_SESSION['user'] = $user;
      $_SESSION['success_msg'] = "You are now logged in";
      // if user is admin, redirect to dashboard, otherwise to homepage
      if (isAdmin($user_id)) {
        $permissionsSql = "SELECT p.name as permission_name FROM permissions as p
                            JOIN permission_role as pr ON p.id=pr.permission_id
                            WHERE pr.role_id=?";
        $userPermissions = getMultipleRecords($permissionsSql, "i", [$user['role_id']]);
        $_SESSION['userPermissions'] = $userPermissions;
        header('location: ' . BASE_URL . 'admin/dashboard.php');
      } else {
        header('location: ' . BASE_URL . 'index.php');
      }
      exit(0);
    }
  }

// Accept a user object, validates user and return an array with the error messages
  function validateUser($user, $ignoreFields) {
  		global $conn;
      $errors = [];
      // password confirmation
      if (isset($user['passwordConf']) && ($user['password'] !== $user['passwordConf'])) {
        $errors['passwordConf'] = "The two passwords do not match";
      }
      // if passwordOld was sent, then verify old password
      if (isset($user['passwordOld']) && isset($user['user_id'])) {
        $sql = "SELECT * FROM users WHERE id=? LIMIT 1";
        $oldUser = getSingleRecord($sql, 'i', [$user['user_id']]);
        $prevPasswordHash = $oldUser['password'];
        if (!password_verify($user['passwordOld'], $prevPasswordHash)) {
          $errors['passwordOld'] = "The old password does not match";
        }
      }
      // the email should be unique for each user for cases where we are saving admin user or signing up new user
      if (in_array('save_user', $ignoreFields) || in_array('signup_btn', $ignoreFields)) {
        $sql = "SELECT * FROM users WHERE email=? OR username=? LIMIT 1";
        $oldUser = getSingleRecord($sql, 'ss', [$user['email'], $user['username']]);
        if (!empty($oldUser['email']) && $oldUser['email'] === $user['email']) { // if user exists
          $errors['email'] = "Email already exists";
        }
        if (!empty($oldUser['username']) && $oldUser['username'] === $user['username']) { // if user exists
          $errors['username'] = "Username already exists";
        }
      }

      // required validation
  	  foreach ($user as $key => $value) {
        if (in_array($key, $ignoreFields)) {
          continue;
        }
  			if (empty($user[$key])) {
  				$errors[$key] = "This field is required";
  			}
  	  }
  		return $errors;
  }
  // upload's user profile profile picture and returns the name of the file
  function uploadProfilePicture()
  {
    // if file was sent from signup form ...
    if (!empty($_FILES) && !empty($_FILES['profile_picture']['name'])) {
        // Get image name
        $profile_picture = date("Y.m.d") . $_FILES['profile_picture']['name'];
        // define Where image will be stored
        $target = ROOT_PATH . "/assets/images/" . $profile_picture;
        // upload image to folder
        if (move_uploaded_file($_FILES['profile_picture']['tmp_name'], $target)) {
          return $profile_picture;
          exit();
        }else{
          echo "Failed to upload image";
        }
    }
  }

Biarkan saya menarik perhatian Anda ke 2 fungsi penting dalam file ini. Mereka adalah: getSingleRecord() dan getMultipleRecords(). Fungsi-fungsi ini sangat penting karena di mana saja di seluruh aplikasi kita, ketika kita ingin memilih record dari database, kita hanya akan memanggil fungsi getSingleRecord() dan meneruskan kueri SQL ke sana. Jika kami ingin memilih beberapa record, Anda dapat menebaknya, kami hanya akan memanggil fungsi getMultipleRecords() juga dengan meneruskan kueri SQL yang sesuai.

Kedua fungsi ini mengambil 3 parameter yaitu query SQL, jenis variabel (misalnya, 's' berarti string, 'si' berarti string dan integer, dan seterusnya) dan terakhir parameter ketiga yang merupakan array dari semua nilai yang yang dibutuhkan kueri untuk dieksekusi.

Misalnya, Jika saya ingin memilih dari tabel pengguna di mana nama pengguna adalah 'John' dan usia 24, saya hanya akan menulis kueri saya seperti ini:

$sql = SELECT * FROM users WHERE username=John AND age=20; // this is the query

$user = getSingleRecord($sql, 'si', ['John', 20]); // perform database query

Dalam pemanggilan fungsi, 's' mewakili tipe string (karena nama pengguna 'John' adalah string) dan 'i' berarti integer (usia 20 adalah integer). Fungsi ini membuat pekerjaan kita menjadi sangat mudah karena jika kita ingin melakukan query database di ratusan tempat berbeda dalam aplikasi kita, kita tidak perlu hanya menggunakan dua baris ini. Fungsi-fungsi itu sendiri masing-masing memiliki sekitar 8 - 10 baris kode sehingga kita terhindar dari pengulangan kode. Mari kita terapkan metode ini sekaligus.

File config.php akan disertakan dalam setiap file tempat query database dilakukan karena menyimpan konfigurasi database. Jadi ini adalah tempat yang tepat untuk mendefinisikan metode ini. Buka config.php sekali lagi dan tambahkan saja metode ini di akhir file:

config.php:

// ...More code here ...

function getMultipleRecords($sql, $types = null, $params = []) {
  global $conn;
  $stmt = $conn->prepare($sql);
  if (!empty($params) && !empty($params)) { // parameters must exist before you call bind_param() method
    $stmt->bind_param($types, ...$params);
  }
  $stmt->execute();
  $result = $stmt->get_result();
  $user = $result->fetch_all(MYSQLI_ASSOC);
  $stmt->close();
  return $user;
}
function getSingleRecord($sql, $types, $params) {
  global $conn;
  $stmt = $conn->prepare($sql);
  $stmt->bind_param($types, ...$params);
  $stmt->execute();
  $result = $stmt->get_result();
  $user = $result->fetch_assoc();
  $stmt->close();
  return $user;
}
function modifyRecord($sql, $types, $params) {
  global $conn;
  $stmt = $conn->prepare($sql);
  $stmt->bind_param($types, ...$params);
  $result = $stmt->execute();
  $stmt->close();
  return $result;
}

Kami menggunakan pernyataan yang telah disiapkan dan ini penting untuk alasan keamanan.

Sekarang kembali ke file common_functions.php kita lagi. File ini menyimpan 4 fungsi penting yang nantinya akan digunakan oleh banyak file lainnya.

Saat pengguna mendaftar, kami ingin memastikan bahwa mereka memberikan data yang benar, jadi kami memanggil fungsi validateUser() , yang disediakan oleh file ini. Jika gambar profil dipilih, kami menguploadnya dengan memanggil fungsi uploadProfilePicture() , yang disediakan oleh file ini.

Jika kami berhasil menyimpan pengguna di database, kami ingin mereka segera login, jadi kami memanggil fungsi loginById(), yang disediakan oleh file ini. Saat pengguna masuk, kami ingin mengetahui apakah mereka admin atau normal, jadi kami memanggil fungsi isAdmin() , yang disediakan oleh file ini. Jika kami menemukan bahwa mereka adalah admin (jika isAdmin() mengembalikan nilai true), kami mengarahkan mereka ke dasbor. Jika pengguna biasa, kami mengarahkan ulang ke beranda.

Jadi Anda dapat melihat file common_functions.php kami sangat penting. Kami akan menggunakan semua fungsi ini ketika kami akan mengerjakan bagian admin kami yang sangat mengurangi pekerjaan kami dan menghindari pengulangan kode.

Untuk memungkinkan pengguna mendaftar, mari buat tabel pengguna. Namun karena tabel pengguna terkait dengan tabel peran, Kami akan membuat tabel peran terlebih dahulu.

tabel peran:

CREATE TABLE `roles` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `name` varchar(255) NOT NULL,
 `description` text NOT NULL,
  PRIMARY KEY (`id`)
)

tabel pengguna:

CREATE TABLE `users`(
    `id` INT(11) PRIMARY KEY NOT NULL AUTO_INCREMENT,
    `role_id` INT(11) DEFAULT NULL,
    `username` VARCHAR(255) UNIQUE NOT NULL,
    `email` VARCHAR(255) UNIQUE NOT NULL,
    `password` VARCHAR(255) NOT NULL,
    `profile_picture` VARCHAR(255) DEFAULT NULL,
    `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    `updated_at` TIMESTAMP NOT NULL DEFAULT '0000-00-00 00:00:00',
    CONSTRAINT `users_ibfk_1` FOREIGN KEY(`role_id`) REFERENCES `roles`(`id`) ON DELETE SET NULL ON UPDATE NO ACTION
)

Tabel pengguna terkait dengan tabel peran dalam hubungan Banyak-ke-Satu. Saat peran dihapus dari tabel peran, kami ingin semua pengguna yang sebelumnya memiliki role_id sebagai atribut mereka agar nilainya disetel ke NULL. Artinya, pengguna tidak lagi menjadi admin.

Jika Anda membuat tabel secara manual, tambahkan batasan ini dengan baik. Jika Anda menggunakan PHPMyAdmin, Anda dapat melakukannya dengan mengklik tab struktur pada tabel pengguna, lalu tabel tampilan relasi, dan terakhir mengisi formulir ini seperti ini:

Pada titik ini, sistem kami mengizinkan pengguna untuk mendaftar dan kemudian setelah mendaftar, mereka masuk secara otomatis. Namun setelah masuk, seperti yang ditunjukkan dalam fungsi loginById(), mereka dialihkan ke halaman beranda (index.php). Mari kita buat halaman itu. Di root aplikasi, buat file bernama index.php.

index.php:

<?php include("config.php") ?>
<?php include(INCLUDE_PATH . "/logic/common_functions.php"); ?>
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>UserAccounts - Home</title>
  <!-- Bootstrap CSS -->
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" />
  <!-- Custome styles -->
  <link rel="stylesheet" href="static/css/style.css">
</head>
<body>
    <?php include(INCLUDE_PATH . "/layouts/navbar.php") ?>
    <?php include(INCLUDE_PATH . "/layouts/messages.php") ?>
    <h1>Home page</h1>
    <?php include(INCLUDE_PATH . "/layouts/footer.php") ?>

Sekarang buka browser Anda, buka http://localhost/user-accounts/signup.php, isi formulir dengan beberapa informasi pengujian (dan ingatlah dengan baik karena kami akan menggunakan pengguna nanti untuk masuk), lalu klik tombol pendaftaran. Jika semuanya berjalan dengan baik, pengguna akan disimpan dalam database dan aplikasi kita akan dialihkan ke halaman Beranda.

Di halaman beranda, Anda akan melihat kesalahan yang muncul karena kami menyertakan file messages.php yang belum kami buat. Mari kita membuatnya sekaligus.

Di direktori include/layouts, buat file bernama messages.php:

pesan.php: 

<?php if (isset($_SESSION['success_msg'])): ?>
  <div class="alert <?php echo 'alert-success'; ?> alert-dismissible" role="alert">
    <button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
    <?php
      echo $_SESSION['success_msg'];
      unset($_SESSION['success_msg']);
    ?>
  </div>
<?php endif; ?>

<?php if (isset($_SESSION['error_msg'])): ?>
  <div class="alert alert-danger alert-dismissible" role="alert">
    <button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
    <?php
      echo $_SESSION['error_msg'];
      unset($_SESSION['error_msg']);
    ?>
  </div>
<?php endif; ?>

Sekarang segarkan beranda dan kesalahannya hilang.

Dan itu saja untuk bagian ini. Di bagian selanjutnya kita akan melanjutkan dengan memvalidasi formulir pendaftaran, login/logout pengguna dan mulai bekerja di bagian admin. Ini terdengar seperti terlalu banyak pekerjaan tapi percayalah, ini sangat mudah terutama karena kami telah menulis beberapa kode yang memudahkan pekerjaan kami di bagian Admin.

Terima kasih telah mengikuti. Semoga Anda ikut. Jika Anda memiliki pemikiran, berikan di komentar di bawah. Jika Anda menemukan kesalahan atau tidak memahami sesuatu, beri tahu saya di bagian komentar sehingga saya dapat mencoba dan membantu Anda.

Sampai jumpa di bagian selanjutnya.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Tipe data MySQL apa yang harus digunakan untuk Lintang/Bujur dengan 8 tempat desimal?

  2. Pilih 10 Catatan Teratas untuk Setiap Kategori di MySQL

  3. Pindah dari MySQL 5.7 ke MySQL 8.0 - Yang Harus Anda Ketahui

  4. Pustaka tidak dimuat:kesalahan libmysqlclient.16.dylib saat mencoba menjalankan 'rails server' di OS X 10.6 dengan permata mysql2

  5. Menjatuhkan batasan unik dari tabel MySQL