前言
注册时经常需要用到短信验证码,本文记录一下思路和具体实现。
短信验证平台使用云片,短信验证码的生成使用thinkphp。
思路
1、用户输入手机号,请求获取短信验证码。
2、thinkphp生成短信验证码,存储,同时和其他参数一起发送请求给云片。
3、云片发送短信验证码到指定手机号。
4、用户输入短信验证码。
5、thinkphp根据验证码是否正确、验证码是否过期两个条件判断是否验证通过。
代码实现
验证接口
接口地址:https://sms.yunpian.com/v1/sms/send.json。
使用postman,输入三个必须的参数apikey、mobile和text。
php发起http/https请求
使用php的curl函数发起https请求,带入参数apikey、mobile和text。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
// 获取短信验证码public
function getSMSCode(){
// create curl resource$ch
= curl_init();
// set urlcurl_setopt($ch, CURLOPT_URL,$url);
// set param$paramArr=
array(
'apikey'
=> '******',
'mobile'
=> '******',
'text'
=> '【小太阳】您的验证码是1234'
);$param
= '';
foreach
($paramArr as$key
=> $value) {
$param
.= urlencode($key).'='.urlencode($value).'&';
}$param
= substr($param, 0,strlen($param)-1);
curl_setopt($ch, CURLOPT_POSTFIELDS,$param);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_POST, 1);
//curl默认不支持https协议,设置不验证协议curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
//return the transfer as a stringcurl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
// $output contains the output string$output= curl_exec($ch);
// close curl resource to free up system resourcescurl_close($ch);
echo
$output;
} |
生成随机短信验证码
默认生成四位的随机短信验证码。
|
1
2
3
4
5
6
|
// 生成短信验证码public
function createSMSCode($length= 4){
$min
= pow(10 , ($length
- 1));
$max
= pow(10, $length) - 1;
return
rand($min,
$max);
} |
整合
在数据库新建表sun_smscode:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
|
DROP TABLE IF EXISTS `sun_smscode`;CREATE TABLE `sun_smscode` (`id` int(8) NOT NULL AUTO_INCREMENT,`mobile` varchar(11) NOT NULL,`code` int(4) NOT NULL,`create_at` datetime NOT NULL,`update_at` datetime NOT NULL,PRIMARY KEY (`id`)) ENGINE=MyISAM AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;thinkphp代码:// 获取短信验证码public
function getSMSCode(){
// create curl resource$ch
= curl_init();
// set urlcurl_setopt($ch, CURLOPT_URL,$url);
// set param$mobile=
$_POST['mobile'];
$code
= $this->createSMSCode();
$paramArr=
array(
'apikey'
=> '******',
'mobile'
=> $mobile,
'text'
=> '【小太阳】您的验证码是'.$code
);$param
= '';
foreach
($paramArr as$key
=> $value) {
$param
.= urlencode($key).'='.urlencode($value).'&';
}$param
= substr($param, 0,strlen($param)-1);
curl_setopt($ch, CURLOPT_POSTFIELDS,$param);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);//不验证证书下同
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
//return the transfer as a stringcurl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
// $output contains the output string$output= curl_exec($ch);
// close curl resource to free up system resourcescurl_close($ch);
//$outputJson = json_decode($output);$outputArr= json_decode($output, true);
//echo $outputJson->code;//echo $outputArr['code'];if($outputArr['code'] =='0'){
$data['mobile'] =$mobile;
$data['code'] =$code;
$smscode= D('smscode');
$smscodeObj=
$smscode->where("mobile='$mobile'")->find();
if($smscodeObj){
$data['update_at'] =date('Y-m-d
H:i:s');
$success=
$smscode->where("mobile='$mobile'")->save($data);
if($success!== false){
$result=
array(
'code'
=> '0',
'ext'
=> '修改成功',
'obj'
=> $smscodeObj
);}echo
json_encode($result,JSON_UNESCAPED_UNICODE);
}else{
$data['create_at'] =date('Y-m-d
H:i:s');
$data['update_at'] =$data['create_at'];
if($smscode->create($data)){
$id
= $smscode->add();
if($id){
$smscode_temp=
$smscode->where("id='$id'")->find();
$result=
array(
'code'=>'0',
'ext'=>'创建成功',
'obj'=>$smscode_temp
);echo
json_encode($result,JSON_UNESCAPED_UNICODE);
}}}}} |
验证短信验证码
验证短信验证码时间是否过期,验证短信验证码是否正确。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
// 验证短信验证码是否有效public
function checkSMSCode(){
$mobile
= $_POST['mobile'];
$code
= $_POST['code'];
$nowTimeStr=
date('Y-m-d H:i:s');
$smscode
= D('smscode');
$smscodeObj=
$smscode->where("mobile='$mobile'")->find();
if($smscodeObj){
$smsCodeTimeStr=
$smscodeObj['update_at'];
$recordCode=
$smscodeObj['code'];
$flag
= $this->checkTime($nowTimeStr,$smsCodeTimeStr);
if(!$flag){
$result=
array(
'code'
=> '1',
'ext'
=> '验证码过期,请刷新后重新获取'
);echo
json_encode($result,JSON_UNESCAPED_UNICODE);
return;
}if($code!=
$recordCode){
$result=
array(
'code'
=> '2',
'ext'
=> '验证码错误,请重新输入'
);echo
json_encode($result,JSON_UNESCAPED_UNICODE);
return;
}$result=
array(
'code'
=> '0',
'ext'
=> '验证通过'
);echo
json_encode($result,JSON_UNESCAPED_UNICODE);
}}// 验证验证码时间是否过期public
function checkTime($nowTimeStr,$smsCodeTimeStr){
//$nowTimeStr = '2016-10-15 14:39:59';//$smsCodeTimeStr = '2016-10-15 14:30:00';$nowTime=
strtotime($nowTimeStr);
$smsCodeTime=
strtotime($smsCodeTimeStr);
$period=
floor(($nowTime-$smsCodeTime)/60);//60s
if($period>=0 &&$period<=20){
return
true;
}else{
return
false;
}} |
改进
为了防止短信轰炸,在请求获取短信验证码时,需要加入图片验证码。
thinkphp提供了生成图片验证码的函数,下面我们来实现验证码的生成、刷新和验证。
生成和刷新图片验证码
|
1
2
3
4
5
6
7
8
9
10
11
|
// 获取图片验证码,刷新图片验证码public
function getPicCode(){
$config
= array(
'fontSize'=>30,// 验证码字体大小
'length'=>4,// 验证码位数
'useNoise'=>false,// 关闭验证码杂点
'expire'=>600
);$Verify
= new \Think\Verify($config);
$Verify->entry(2333);//2333是验证码标志
} |
假设,该函数的对应url为http://localhost/owner-bd/index.php/Home/CheckCode/getPicCode,那么,图片验证码的地址就是这个url,放入页面图片标签的src属性即可。
验证图片验证码
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
// 验证验证码是否正确public
function checkPicCode($code){
$verify
= new \Think\Verify();
if($verify->check($code, 2333)){
$result
= array(
'code'
=> '0',
'ext'
=> '验证通过'
);echo
json_encode($result,JSON_UNESCAPED_UNICODE);
}else{
$result=
array(
'code'
=> '1',
'ext'
=> '验证码错误,请重新输入'
);echo
json_encode($result,JSON_UNESCAPED_UNICODE);
};} |
以上方法,我们利用了thinkphp提供的check方法,实现起来很简单。但是,如果想要得到验证细节,就没有办法了。比如,验证码错误,可能验证码超时,可能因为输入验证码错误,可能因为验证码已经使用过等等。必要的时候,可以重写thinkphp的验证码类,或者重写thinkphp的check方法。
跑通前后端
后端修改
验证图片验证码函数,改为被调用函数:
|
1
2
3
4
5
6
7
8
|
public
function checkPicCode($picCode){
$verify
= new \Think\Verify();
if($verify->check($picCode, 2333)){
return
true;
}else{
return
false;
};} |
在获取短信验证码函数的最顶部,添加调用图片验证码函数,只有通过验证,才发送请求给云片。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
// 获取短信验证码public
function getSMSCode(){
$picCode
= $_POST['picCode'];
if(!$this->checkPicCode($picCode)){
$result
= array(
'code'
=> '1',
'ext'
=> '验证码错误,请重新输入'
);echo
json_encode($result,JSON_UNESCAPED_UNICODE);
return;
}/*省略*/} |
前端核心代码
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
|
<!--register.html--><!DOCTYPE html><html lang="zh"ng-app="sunApp">
<head><meta charset="UTF-8">
<title>注册</title></head><body ng-controller="registerController">
<form action=""class="register-form"ng-show="isShow1">
<div class="input-group">
<input type="text"class="mobile"ng-model="mobile"
placeholder="手机号">
</div><div class="input-group">
<input type="text"class="pic-code"ng-model="picCode"placeholder="图片验证码">
<img class="img"src="{{picCodeUrl}}"alt=""
ng-click="refresh()">
</div><div class="input-group">
<input type="text"class="sms-code"ng-model="SMSCode"placeholder="短信验证码">
<button class="btn-sms"ng-click="getSMSCode()"ng-disabled="btnSMSDisabled">{{btnSMSText}}</button>
</div><button class="confirm-btn"ng-click="next()">下一步</button>
</form><form action=""class="register-form"ng-show="isShow2">
<div class="input-group">
<input type="text"class="mobile"ng-model="mobile"
placeholder="手机号"
disabled="true">
</div><div class="input-group">
<input type="password"class="password"ng-model="password"placeholder="请输入密码">
<input type="password"class="password"ng-model="password2"placeholder="请再次输入密码">
</div><button class="confirm-btn"ng-click="getSMSCode()">注册</button>
</form></body></html>// register.jsangular.module('sunApp').controller('registerController',function
($scope,$http,$httpParamSerializer,$state,$interval)
{
$scope.picCodeUrl ='/owner-bd/index.php/Home/CheckCode/getPicCode';
$scope.isShow1 = true;
$scope.isShow2 = false;
$scope.btnSMSText ='获取验证码';
$scope.btnSMSDisabled = false;
$scope.checkOver = false;
// 获取短信验证码$scope.getSMSCode =function(){
var
param = {
mobile: $scope.mobile,
picCode: $scope.picCode
};$http({
method:'POST',
url:'/owner-bd/index.php/Home/SMS/getSMSCode',
//url: '/owner-fd/mock/common.json',headers:{'Content-Type':'application/x-www-form-urlencoded'
},dataType:
'json',
data: $httpParamSerializer(param)
}).then(functionsuccessCallback(response) {
console.log(response.data);if(response.data.code =='0'){
$scope.checkOver = true;
$scope.btnSMSDisabled = true;
var
time = 60;
var
timer = null;
timer = $interval(function(){
time = time - 1;$scope.btnSMSText = time+'秒';
if(time == 0) {
$interval.cancel(timer);
$scope.btnSMSDisabled = false;
$scope.btnSMSText ='重新获取';
}}, 1000);}}, functionerrorCallback(response) {
console.log(response.data);});}// 验证短信验证码$scope.next =function(){
if(!$scope.checkOver){
console.log('未通过验证');
return;
}var
param = {
mobile: $scope.mobile,
code: $scope.SMSCode
};$http({
method:'POST',
url:'/owner-bd/index.php/Home/SMS/checkSMSCode',
//url: '/owner-fd/mock/common.json',headers:{'Content-Type':'application/x-www-form-urlencoded'
},dataType:
'json',
data: $httpParamSerializer(param)
}).then(functionsuccessCallback(response) {
console.log(response.data);if(response.data.code =='0'){
$scope.isShow1 = false;
$scope.isShow2 = true;
}}, functionerrorCallback(response) {
console.log(response.data);});}// 刷新图片验证码$scope.refresh =function(){
$scope.picCodeUrl ='/owner-bd/index.php/Home/CheckCode/getPicCode?'+Math.random();
}}); |
优化
以上代码,安全性不是很好,我们可以利用工具绕过前端验证。为了避免这个问题,可以在checkPicCode和checkSMSCode函数中添加session值来标记。
|
1
2
|
$_SESSION['checkPicCode'] = true;
$_SESSION['checkSMSCode'] = true;
|
在最后一步,向数据库中添加用户时,先验证一下两个session值是否都为true,都为true时再添加。
成果
后记
以后也许有用的代码:
|
1
2
|
echo
json_encode($_SESSION);// 打印出session中的数据
echo
session_id();// 打印当前session的id
|