1.谷歌类库
A
<?php
namespace Common\Ext;
class GoogleAuthenticator
{
protected $_codeLength = 6;
public function createSecret($secretLength = 16)
{
$validChars = $this->_getBase32LookupTable();
unset($validChars[32]);
$secret = '';
$i = 0;
for (; $i < $secretLength; $i++) {
$secret .= $validChars[array_rand($validChars)];
}
return $secret;
}
public function getCode($secret, $timeSlice = NULL)
{
if ($timeSlice === null) {
$timeSlice = floor(time() / 30);
}
$secretkey = $this->_base32Decode($secret);
$time = chr(0) . chr(0) . chr(0) . chr(0) . pack('N*', $timeSlice);
$hm = hash_hmac('SHA1', $time, $secretkey, true);
$offset = ord(substr($hm, -1)) & 15;
$hashpart = substr($hm, $offset, 4);
$value = unpack('N', $hashpart);
$value = $value[1];
$value = $value & 2147483647;
$modulo = pow(10, $this->_codeLength);
return str_pad($value % $modulo, $this->_codeLength, '0', STR_PAD_LEFT);
}
public function getQRCodeGoogleUrl($name, $secret, $title = NULL)
{
return 'otpauth://totp/' . $name . '?secret=' . $secret;
}
public function verifyCode($secret, $code, $discrepancy = 1, $currentTimeSlice = NULL)
{
if ($currentTimeSlice === null) {
$currentTimeSlice = floor(time() / 30);
}
// $i = -$discrepancy;
$i = -$discrepancy-2;
for (; $i <= $discrepancy; $i++) {
$calculatedCode = $this->getCode($secret, $currentTimeSlice + $i);
$cal .= '='.$calculatedCode;
if ($calculatedCode == $code) {
return true;
}
}
return false;
}
public function setCodeLength($length)
{
$this->_codeLength = $length;
return $this;
}
protected function _base32Decode($secret)
{
if (empty($secret)) {
return '';
}
$base32chars = $this->_getBase32LookupTable();
$base32charsFlipped = array_flip($base32chars);
$paddingCharCount = substr_count($secret, $base32chars[32]);
$allowedValues = array(6, 4, 3, 1, 0);
if (!in_array($paddingCharCount, $allowedValues)) {
return false;
}
$i = 0;
for (; $i < 4; $i++) {
if (($paddingCharCount == $allowedValues[$i]) && (substr($secret, -$allowedValues[$i]) != str_repeat($base32chars[32], $allowedValues[$i]))) {
return false;
}
}
$secret = str_replace('=', '', $secret);
$secret = str_split($secret);
$binaryString = '';
$i = 0;
for (; $i < count($secret); $i = $i + 8) {
$x = '';
if (!in_array($secret[$i], $base32chars)) {
return false;
}
$j = 0;
for (; $j < 8; $j++) {
$x .= str_pad(base_convert(@($base32charsFlipped[@($secret[$i + $j])]), 10, 2), 5, '0', STR_PAD_LEFT);
}
$eightBits = str_split($x, 8);
$z = 0;
for (; $z < count($eightBits); $z++) {
$binaryString .= (($y = chr(base_convert($eightBits[$z], 2, 10))) || (ord($y) == 48) ? $y : '');
}
}
return $binaryString;
}
protected function _base32Encode($secret, $padding = true)
{
if (empty($secret)) {
return '';
}
$base32chars = $this->_getBase32LookupTable();
$secret = str_split($secret);
$binaryString = '';
$i = 0;
for (; $i < count($secret); $i++) {
$binaryString .= str_pad(base_convert(ord($secret[$i]), 10, 2), 8, '0', STR_PAD_LEFT);
}
$fiveBitBinaryArray = str_split($binaryString, 5);
$base32 = '';
$i = 0;
while ($i < count($fiveBitBinaryArray)) {
$base32 .= $base32chars[base_convert(str_pad($fiveBitBinaryArray[$i], 5, '0'), 2, 10)];
$i++;
}
if ($padding && ($x = strlen($binaryString) % 40 != 0)) {
if ($x == 8) {
$base32 .= str_repeat($base32chars[32], 6);
}
else if ($x == 16) {
$base32 .= str_repeat($base32chars[32], 4);
}
else if ($x == 24) {
$base32 .= str_repeat($base32chars[32], 3);
}
else if ($x == 32) {
$base32 .= $base32chars[32];
}
}
return $base32;
}
protected function _getBase32LookupTable()
{
return array('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '2', '3', '4', '5', '6', '7', '=');
}
}
?>
2.谷歌HTML页面
A
<include file="Public:header"/>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>绑定谷歌二维码1</title>
<!-- 引入qrcode.js库 -->
<script type="text/javascript" src="/Public/Static/js/qrcode.js"></script>
<style>
body {
position: relative;
min-height: 100vh;
margin: 0;
display: flex;
justify-content: center;
align-items: center;
font-family: Arial, sans-serif;
background-color: #f5f5f5;
}
.container {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
background-color: #fff;
padding: 20px;
border-radius: 5px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
}
.container p {
margin: 10px 0;
font-size: 18px;
font-weight: bold;
}
#qrcode {
width: 200px;
height: 200px;
margin-top: 20px;
}
.qr-instructions {
margin-top: 20px;
font-size: 16px;
line-height: 1.5;
color: #888;
}
.qr-instructions span {
display: block;
margin-bottom: 10px;
}
.qr-instructions strong {
color: #333;
}
.avatar {
position: absolute;
bottom: 20px;
right: 20px;
width: 60px;
height: 60px;
background-image: url('/Public/Home/static/imgs/gg.jpg'); /* 替换为您上传的图片路径 */
background-size: cover;
background-position: center;
background-repeat: no-repeat;
border-radius: 50%;
}
</style>
</head>
<body>
<div class="container">
<div>
<p>身份验证</p>
<div class="qr-instructions">
<span>1. 在移动设备上安装验证器应用</span>
<span>2. 在验证器应用中扫描以下二维码</span>
</div>
</div>
<!-- 在页面中创建一个容器用于显示二维码 -->
<div id="qrcode"></div>
<div class="avatar">
</div>
</div>
<!-- 在JavaScript中生成二维码 -->
<script>
// 获取二维码文本数据
var qrCodeText = "{$qrCodeUrl}";
// 创建二维码实例
var qrcode = new QRCode(document.getElementById("qrcode"), {
render: "canvas",
text: qrCodeText,
width: 200,
height: 200,
correctLevel : QRCode.CorrectLevel.H
});
</script>
<include file="Public:footer"/>
<block name="script">
<script type="text/javascript" charset="utf-8">
//导航高亮
highlight_subnav("{:U('User/orcode')}");
</script>
</block>
2.1登录页面加
<!--输入谷歌验证码-->
<div class="form-group">
<div class="inputs" style="width:100%; background:#f5f5f5;border: 1px solid #f5f5f5f5;">
<i class="icon-login-google"></i>
<input type="text" name="google_verify" autocomplete="off" placeholder="谷歌验证码" id="google_verify" style="width:75%;background:#f5f5f5;color:#000;">
</div>
<div class="imgcode">
<a href="http://jys.gg/Admin/Login/orcode"></a>
</div>
</div>
3.控制器
<?php
namespace Admin\Controller;
use Common\Ext\GoogleAuthenticator;
class UserController extends AdminController
{
public function adminGroup($id)
{
if (empty($id)) {
$this->data = null;
} else {
$data = array(
'uid' => trim($id),
'group_id' => 1,
);
M('auth_group_access')->add($data);
$result = M("admin")->where(array('id'=>$id))->save(array('is_group'=>1));
if ($result) {
// 新增成功
$this->success("成功");
} else {
// 新增失败
$this->success("失败");
}
}
}
// 谷歌验证码绑定
public function orcode($username = NULL)
{
$username = session('admin_username');
// 当前管理员的信息
$info = M('Admin')->where(array('username' => $username))->find();
$ga = new GoogleAuthenticator();
// 生成密钥
$secret = $ga->createSecret();
// 生成二维码链接
$qrCodeUrl = $ga->getQRCodeGoogleUrl($username, $secret);
if ($info['google'] == '22222222222'){
$this->ajaxReturn(['code'=>100,'msg'=>'请勿重复绑定!']);
}else{
if ($info['google'] == '1')
{
// 将秘钥保存到数据库
$data = array(
'secret' => $secret,
//绑定谷歌 1:未绑定 2:已绑定
'google' => '2'
);
$where = array(
'id' => $info['id'],
);
M('Admin')->where($where)->save($data);
}
}
$this->assign('qrCodeUrl', $qrCodeUrl);
$this->display();
}
}
?>
3.1控制器验证谷歌码
$admin = M('Admin')->where(array('username' => $username))->find();
if($admin['google'] == '2')
{
// 验证谷歌验证码
$googleCode = $_POST['google_verify'];
$authenticator = new GoogleAuthenticator();
if (!$authenticator->verifyCode($admin['secret'], $googleCode)) {
$this->error('谷歌验证码错误');
}
}
4.数据库admin表
CREATE TABLE `tw_admin` (
`id` INT ( 11 ) UNSIGNED NOT NULL AUTO_INCREMENT,
`email` VARCHAR ( 200 ) NOT NULL DEFAULT '',
`username` CHAR ( 16 ) NOT NULL,
`nickname` VARCHAR ( 50 ) NOT NULL,
`moble` VARCHAR ( 50 ) NOT NULL,
`password` CHAR ( 32 ) NOT NULL,
`mass_hair` INT ( 1 ) DEFAULT '0' COMMENT '是否开启群发功能0: 关闭 |1:开启 ',
`sort` INT ( 11 ) UNSIGNED NOT NULL,
`addtime` INT ( 11 ) UNSIGNED NOT NULL,
`last_login_time` INT ( 11 ) UNSIGNED NOT NULL,
`last_login_ip` INT ( 11 ) UNSIGNED NOT NULL,
`endtime` INT ( 11 ) UNSIGNED NOT NULL,
`status` INT ( 4 ) NOT NULL,
`secret` VARCHAR ( 50 ) CHARACTER
SET utf8 COLLATE utf8_bin NOT NULL COMMENT '谷歌秘钥',
`google` INT ( 4 ) NOT NULL DEFAULT '1' COMMENT '绑定谷歌 1:未绑定 2:已绑定',
`is_group` INT ( 1 ) NOT NULL DEFAULT '0' COMMENT '是否绑定了分组 未绑定0 已绑定1',
PRIMARY KEY ( `id` ) USING BTREE,
KEY `status` ( `status` ) USING BTREE,
KEY `username` ( `username` ) USING BTREE
) ENGINE = MyISAM AUTO_INCREMENT = 10 DEFAULT CHARSET = utf8 ROW_FORMAT = DYNAMIC COMMENT = '管理员表';