(1)数据库
CREATE TABLE `otp` (
`email` varchar(255) NOT NULL,
`pass` varchar(255) NOT NULL,
`timestamp` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
ALTER TABLE `otp`
ADD PRIMARY KEY (`_email`);
-
email
用户的电子邮件,主键。您也可以将其与订单,交易或任何要添加otp的订单,交易。 -
pass
otp本身。 -
创建OTP时的
timestamp
时间戳。用于预防到期和蛮力。
(2)PHP库
<?php
class OTP {
// (A) CONSTRUCTOR - CONNECT TO DATABASE
private $pdo = null;
private $stmt = null;
public $error = "";
function __construct() {
$this->pdo = new PDO(
"mysql:host=".DB_HOST.";dbname=".DB_NAME.";charset=".DB_CHARSET,
DB_USER, DB_PASSWORD, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
]);
}
// (B) DESTRUCTOR - CLOSE CONNECTION
function __destruct() {
if ($this->stmt !== null) { $this->stmt = null; }
if ($this->pdo !== null) { $this->pdo = null; }
}
// (C) HELPER - RUN SQL QUERY
function query ($sql, $data=null) : void {
$this->stmt = $this->pdo->prepare($sql);
$this->stmt->execute($data);
}
// (D) GENERATE OTP
function generate ($email) {
// (D1) CHECK EXISTING OTP REQUEST
$this->query("SELECT * FROM `otp` WHERE `email`=?", [$email]);
$otp = $this->stmt->fetch();
if (is_array($otp) && (strtotime("now") < strtotime($otp["timestamp"]) + (OTP_VALID * 60))) {
$this->error = "You already have a pending OTP.";
return false;
}
// (D2) CREATE RANDOM OTP
$alphabets = "abcdefghijklmnopqrstuwxyzABCDEFGHIJKLMNOPQRSTUWXYZ0123456789";
$count = strlen($alphabets) - 1;
$pass = "";
for ($i=0; $i<OTP_LEN; $i++) { $pass .= $alphabets[rand(0, $count)]; }
$this->query(
"REPLACE INTO `otp` (`email`, `pass`) VALUES (?,?)",
[$email, password_hash($pass, PASSWORD_DEFAULT)]
);
// (D3) SEND VIA EMAIL
if (@mail($email, "Your OTP",
"Your OTP is $pass. Enter at <a href='http://localhost/3b-challenge.php'>SITE</a>.",
implode("\r\n", ["MIME-Version: 1.0", "Content-type: text/html; charset=utf-8"])
)) { return true; }
else {
$this->error = "Failed to send OTP email.";
return false;
}
}
// (E) CHALLENGE OTP
function challenge ($email, $pass) {
// (E1) GET THE OTP ENTRY
$this->query("SELECT * FROM `otp` WHERE `email`=?", [$email]);
$otp = $this->stmt->fetch();
// (E2) CHECK - NOT FOUND
if (!is_array($otp)) {
$this->error = "The specified OTP request is not found.";
return false;
}
// (E3) CHECK - EXPIRED
if (strtotime("now") > strtotime($otp["timestamp"]) + (OTP_VALID * 60)) {
$this->error = "OTP has expired.";
return false;
}
// (E4) CHECK - INCORRECT PASSWORD
if (!password_verify($pass, $otp["pass"])) {
$this->error = "Incorrect OTP.";
return false;
}
// (E5) OK - DELETE OTP REQUEST
$this->query("DELETE FROM `otp` WHERE `email`=?", [$email]);
return true;
}
}
// (F) DATABASE SETTINGS - CHANGE TO YOUR OWN!
define("DB_HOST", "localhost");
define("DB_NAME", "test");
define("DB_CHARSET", "utf8mb4");
define("DB_USER", "root");
define("DB_PASSWORD", "");
// (G) OTP SETTINGS
define("OTP_VALID", "15"); // otp valid for n minutes
define("OTP_LEN", "8"); // otp length
// (H) NEW OTP OBJECT
$_OTP = new OTP();
- (a,b,h)创建
$_OTP = new OTP()
时,构造函数将连接到数据库。破坏者关闭连接。 - (c)运行SQL查询的辅助功能。
- (F&G)数据库和OTP设置 - 更改为您自己的。
- (d)OTP进程的步骤1,用户请求OTP。几乎“生成一个随机字符串,保存到数据库中,并通过电子邮件将其发送给用户”。
- (e)OTP进程的步骤2,用户单击电子邮件中的链接并输入OTP。然后,此函数将针对数据库验证输入的OTP。
(3)HTML页面
<!-- (A) OTP REQUEST FORM -->
<form method="post" target="_self">
<label>Email</label>
<input type="email" name="email" required value="jon@doe.com">
<input type="submit" value="Request OTP">
</form>
<?php
// (B) PROCESS OTP REQUEST
if (isset($_POST["email"])) {
require "2-otp.php";
$pass = $_OTP->generate($_POST["email"]);
echo $pass ? "<div class='note'>OTP SENT.</div>" : "<div class='note'>".$_OTP->error."</div>" ;
}
?>
这只是一个虚拟形式。在您自己的项目中,使用$_OTP->generate(EMAIL)
创建OTP并将其发送给用户。
<!-- (A) OTP CHALLENGE FORM -->
<form method="post" target="_self">
<label>Email</label>
<input type="email" name="email" required value="jon@doe.com">
<label>OTP</label>
<input type="password" name="otp" required>
<input type="submit" value="Go">
</form>
<?php
// (B) PROCESS OTP CHALLENGE
if (isset($_POST["email"])) {
require "2-otp.php";
$pass = $_OTP->challenge($_POST["email"], $_POST["otp"]);
// @TODO - DO SOMETHING ON VERIFIED
echo $pass ? "<div class='note'>OTP VERIFIED.</div>" : "<div class='note'>".$_OTP->error."</div>" ;
}
?>
- 用户点击电子邮件中的链接并在此页面上降落。
- 输入OTP,
$_OTP->challenge()
将进行验证。 - 此后,请做任何需要的事情。
改进
这只是一个简化的示例,可以帮助初学者掌握概念和过程流。最低限度,我建议:
- 强制使用https。
- 将
tries
添加到otp
表中。 - 修改
function challenge ()
-在每个错误的密码尝试中,tries + 1
。 - 做一些
if (tries >= N)
。锁定用户的帐户,冻结交易,需要手动管理干预,暂停一定时间等...
结束
这就是这个冷凝的教程。这是要点的链接,等等。
Gist | Simple PHP MYSQL OTP - Code Boxx