추가 보안
PHP와 MySQL에서 일회용 암호 (OTP)를 만드는 방법에 대한 단계별 자습서를 시작합니다. 시스템에 추가적인 보안이 필요하십니까? OTP는 그렇게 하는 방법 중 하나이며, 사용자 계정 재설정, 이메일 주소 확인, 지불 확인, 거래 보안 등 문자 그대로 어떤 용도로든 사용할 수 있습니다. OTP의 일반적인 프로세스는 다음과 같습니다.
그러나 PHP에서 일회용 암호를 어떻게 구현합니까? 이 안내서는 정확한 단계를 안내합니다. 계속 읽으십시오.
ⓘ이 튜토리얼의 시작 부분에 모든 소스 코드가 포함 된 zip 파일이 포함되어 있으므로 모든 것을 복사하여 붙여 넣을 필요가 없습니다.
소스 코드 다운로드
먼저 약속 한대로 소스 코드에 대한 다운로드 링크가 있습니다.
소스 코드 다운로드
소스 코드를 다운로드하려면 여기를 클릭하십시오. MIT 라이센스에 따라 릴리스되었으므로 그 위에 빌드하거나 자신의 프로젝트에서 자유롭게 사용하십시오.
폴더
다음은 zip 파일에서 폴더를 구성하는 방법에 대한 간략한 소개입니다.
빠른 시작
데이터베이스
먼저 생성 된 OTP, 타임 스탬프 및 항목을 저장하기 위해 간단한 데이터베이스 테이블을 설정해야 합니다.
OTP TABLE
CREATE TABLE `otp` ( `id` int(11) NOT NULL, `otp_pass` varchar(12) NOT NULL, `otp_timestamp` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `otp_tries` tinyint(1) NOT NULL DEFAULT '0' ) ENGINE=InnoDB DEFAULT CHARSET=latin1; ALTER TABLE `otp` ADD PRIMARY KEY (`id`);
Field | Description |
id | The primary key, unique identifier. This can be the user ID, transaction ID, order ID – Whatever you want to track. |
otp_pass | The one-time password. |
otp_timestamp | Time at which the OTP is generated, used to calculate the expiry time. |
otp_tries | The number of challenge attempts. Used to stop brute force attacks. |
요컨대, 자신의 프로젝트에 맞게 자유롭게 변경하십시오. 예를 들어, ID 대신 이메일은 사용자를 위해 OTP를 생성하는 일부 사람들에게 더 적합 할 수 있습니다.
라이브러리 파일
다음으로, OTP 매직 및 프로세스를 처리하기 위해 PHP 라이브러리를 만들어야 합니다.
구성 파일
// MUTE NOTICES error_reporting(E_ALL & ~E_NOTICE); // PATH define('PATH_LIB', __DIR__ . DIRECTORY_SEPARATOR); // DATABASE SETTINGS - CHANGE THESE TO YOUR OWN! define('DB_HOST', 'localhost'); define('DB_NAME', 'test'); define('DB_CHARSET', 'utf8'); define('DB_USER', 'root'); define('DB_PASSWORD', ''); // ONE-TIME PASSWORD define('OTP_VALID', "15"); // VALID FOR X MINUTES define('OTP_TRIES', "3"); // MAX TRIES define('OTP_LEN', "6"); // PASSWORD LENGTH
이것은 모든 설정을 유지하기 위한 구성 파일일 뿐입니다. – 데이터베이스 및 OTP 설정을 원하는 대로 변경하십시오.
OTP LIBRARY
class OTP { /* PART 1 - DATABASE SUPPORT FUNCTIONS */ protected $pdo = null; protected $stmt = null; public $error = ""; public $lastID = null; function __construct() { // __construct() : connect to the database // PARAM : DB_HOST, DB_CHARSET, DB_NAME, DB_USER, DB_PASSWORD // ATTEMPT CONNECT try { $str = "mysql:host=" . DB_HOST . ";charset=" . DB_CHARSET; if (defined('DB_NAME')) { $str .= ";dbname=" . DB_NAME; } $this->pdo = new PDO( $str, DB_USER, DB_PASSWORD, [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_EMULATE_PREPARES => false ] ); return true; } // ERROR - DO SOMETHING HERE // THROW ERROR MESSAGE OR SOMETHING catch (Exception $ex) { print_r($ex); die(); } } function __destruct() { // __destruct() : close connection when done if ($this->stmt !== null) { $this->stmt = null; } if ($this->pdo !== null) { $this->pdo = null; } } function exec($sql, $data=null) { // exec() : run insert, replace, update, delete query // PARAM $sql : SQL query // $data : array of data try { $this->stmt = $this->pdo->prepare($sql); $this->stmt->execute($data); $this->lastID = $this->pdo->lastInsertId(); } catch (Exception $ex) { $this->error = $ex; return false; } $this->stmt = null; return true; } function fetch ($sql, $cond=null, $sort=null) { // fetch() : perform select query (single row expected) // returns an array of column => value // PARAM $sql : SQL query // $cond : array of conditions // $sort : custom sort function $result = []; try { $this->stmt = $this->pdo->prepare($sql); $this->stmt->execute($cond); if (is_callable($sort)) { while ($row = $this->stmt->fetch(PDO::FETCH_NAMED)) { $result = $sort($row); } } else { while ($row = $this->stmt->fetch(PDO::FETCH_NAMED)) { $result = $row; } } } catch (Exception $ex) { $this->error = $ex; return false; } // Return result $this->stmt = null; return count($result)==0 ? false : $result ; } /* PART 2 - GENERATE RANDOM OTP */ function generate ($id) { // generate() : create a new OTP // PARAM $id : user ID, order ID, or just any unique transaction ID // Create random password $alphabets = "abcdefghijklmnopqrstuwxyzABCDEFGHIJKLMNOPQRSTUWXYZ0123456789"; // Change this alphabets set as needed. For example, if you want numbers only // $alphabets = "0123456789"; $count = strlen($alphabets) - 1; $pass = ""; for ($i = 0; $i < OTP_LEN; $i++) { $pass .= $alphabets[rand(0, $count)]; } // Database entry $sql = "REPLACE INTO `otp` (`id`, `otp_pass`, `otp_timestamp`, `otp_tries`) VALUES (?, ?, ?, ?)"; $data = [$id, $pass, date("Y-m-d H:i:s"), 0]; $ok = $this->exec($sql, $data); // Result return [ "status" => $ok ? 1 : 0, "pass" => $ok ? $pass : "" ]; } /* PART 3 - CHALLENGE OTP */ function challenge ($id, $pass) { // challenge() : challenge the OTP // Get the OTP entry $sql = "SELECT * FROM `otp` WHERE `id`=?"; $data = [$id]; $otp = $this->fetch($sql, $data); // OTP entry not found!? if ($otp === false) { $this->error = "OTP transaction not found."; return false; } // Too many tries if ($otp['otp_tries'] >= OTP_TRIES) { $this->error = "Too many tries for OTP."; return false; } // Expired $validTill = strtotime($otp['otp_timestamp']) + (OTP_VALID * 60); if (strtotime("now") > $validTill) { $this->error = "OTP has expired."; return false; } // Incorrect password if ($pass != $otp['otp_pass']) { // Add a strike to number of tries $strikes = $otp['otp_tries'] + 1; $sql = "UPDATE `otp` SET `otp_tries`=? WHERE `id`=?"; $data = [$strikes, $id]; $this->exec($sql, $data); // Security lock down - Too many tries if ($strikes >= OTP_TRIES) { $this->lockdown(); } // Return result $this->error = "Incorrect OTP."; return false; } // OK - Correct OTP // You can delete the OTP at this point if you want /* * $sql = "DELETE FROM `otp` WHERE `id`=?"; * $data = [$id]; * $this->exec($sql, $data); */ return true; } function lockdown () { // lockdown() : failed challenge multiple times // DO YOUR OWN SECURITY LOCKDOWN! // Suspend the order? // Suspend the user account? // Maybe temporary lock for a few hours to prevent spam? // Send warning email + SMS? // All up to what you want to do. } }
설명
이 라이브러리는 처음에는 복잡해 보이지만 실제로는 3 개의 파트 만 있습니다.
그러나 자체 PHP 프레임 워크를 사용하는 경우 데이터베이스 지원 기능을 제거하고 프레임 워크를 사용하도록 이 라이브러리를 수정하십시오.
Function | Description |
__construct | The constructor. Automatically connects to the database when the OTP object is created. |
__destruct | The destructor. Automatically disconnects from the database when the OTP object is destroyed. |
exec | Runs an insert, replace, update, or delete SQL query. |
fetch | Perform a select query. |
Function | Description |
generate | Initiates and creates a new OTP. |
challenge | Challenge the OTP. |
lockdown | Security lockdown. An empty function for you to fill in – This will run after the user has failed the OTP challenge too many times. |
구현 단계
기초가 확립되었으므로 마지막 단계는 프로젝트에 OTP 라이브러리를 사용하는 것입니다. 모든 사람이 OTP를 다르게 사용하므로 이 섹션에서는 매우 일반적인 "구현 방법"만 안내합니다.
1 단계) OTP 생성
// (1) INCLUDE OTP LIBRARY require __DIR__ . DIRECTORY_SEPARATOR . "lib" . DIRECTORY_SEPARATOR . "config.php"; require PATH_LIB . "lib-otp.php"; $libOTP = new OTP(); // (2) GENERATE OTP // Using a dummy ID of 999 here // It can be the user ID, order ID, whatever is applicable to your project $result = $libOTP->generate(999); // (3) SEND THE ONE-TIME PASSWORD TO THE USER // OTP generation OK if ($result['status'] == 1) { // Send message via email, SMS, messaging, or whatever communication gateway you use $message = "Please visit https://your-site.com/2-challenge.php to verify. This password is valid for " . OTP_VALID . " minutes - " . $result['pass']; // mail("john@doe.com", "Verify to continue", $message); // For testing - We will just output the OTP as text here. echo $message; } // OTP generation Failed else { echo "ERROR - ". $libOTP->error; }
프로세스의 첫 번째 단계는 generate() 함수를 사용하여 OTP를 생성하는 것입니다. 추적하려는 고유 ID를 전달하고 OTP 및 확인 페이지를 사용자에게 보내는 것을 잊지 마십시오.
2 단계) OTP 챌린지 방문 페이지
<html> <head> <title> OTP Challenge Demo Page </title> <script> function verify () { // verify() : verify OTP // APPEND FORM DATA var data = new FormData(); data.append('pass', document.getElementById("otp_pass").value); data.append('id', document.getElementById("otp_id").value); // INIT AJAX var xhr = new XMLHttpRequest(); xhr.open('POST', "3-verify.php", true); // WHEN THE PROCESS IS COMPLETE xhr.onload = function(){ var res = JSON.parse(this.response); if (res.status) { // OK - DO SOMETHING - REDIRECT USER TO ANOTHER PAGE OR CONTINUE TRANSACTION alert("OK"); } else { // ERROR - DO SOMETHING alert(res.message); } }; // SEND xhr.send(data); return false; } </script> </head> <body> <form onsubmit="return verify();"> OTP Password <input type="password" id="otp_pass" required/> <br> ID (This should be hidden or kept in the session) <input type="text" id="otp_id" readonly value="999"/> <br> <input type="submit" value="Go"/> </form> </body> </html>
사용자가 OTP를 입력 할 수 있는 랜딩 페이지의 예입니다. 이는 간단한 HTML 양식일뿐입니다.
3 단계) OTP 검증
// (1) INCLUDE OTP LIBRARY require __DIR__ . DIRECTORY_SEPARATOR . "lib" . DIRECTORY_SEPARATOR . "config.php"; require PATH_LIB . "lib-otp.php"; $libOTP = new OTP(); // (2) CHECK GIVEN OTP $result = false; if (isset($_POST['pass']) && isset($_POST['id'])) { $result = $libOTP->challenge($_POST['id'], $_POST['pass']); } else { $libOTP->error = "Invalid OTP password and ID"; } // (3) RESULTS echo json_encode([ "status" => $result ? 1 : 0, "message" => $result ? "OK" : $libOTP->error ]);
마지막으로 사용자가 제공 한 비밀번호만 확인하면 됩니다.
등록된 댓글이 없습니다.