thinkphp實現短信驗證註冊

前言

註冊時常常須要用到短信驗證碼,本文記錄一下思路和具體實現。
短信驗證平臺使用雲片,短信驗證碼的生成使用thinkphp。php

思路

一、用戶輸入手機號,請求獲取短信驗證碼。
二、thinkphp生成短信驗證碼,存儲,同時和其餘參數一塊兒發送請求給雲片。
三、雲片發送短信驗證碼到指定手機號。
四、用戶輸入短信驗證碼。
五、thinkphp根據驗證碼是否正確、驗證碼是否過時兩個條件判斷是否驗證經過。html

代碼實現

驗證接口

接口地址:https://sms.yunpian.com/v1/sms/send.json
使用postman,輸入三個必須的參數apikeymobiletext
前端

php發起http/https請求

使用php的curl函數發起https請求,帶入參數apikeymobiletextthinkphp

// 獲取短信驗證碼
public function getSMSCode(){

    // create curl resource 
    $ch = curl_init(); 

    // set url
    $url = 'https://sms.yunpian.com/v1/sms/send.json'; 
    curl_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 string 
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 

    // $output contains the output string 
    $output = curl_exec($ch); 

    // close curl resource to free up system resources 
    curl_close($ch); 

    echo $output;
}

生成隨機短信驗證碼

默認生成四位的隨機短信驗證碼。數據庫

// 生成短信驗證碼
public function createSMSCode($length = 4){
    $min = pow(10 , ($length - 1));
    $max = pow(10, $length) - 1;
    return rand($min, $max);
}

整合

在數據庫新建表sun_smscode:json

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 url
    $url = 'https://sms.yunpian.com/v1/sms/send.json'; 
    curl_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 string 
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 

    // $output contains the output string 
    $output = curl_exec($ch); 

    // close curl resource to free up system resources 
    curl_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);
                }
            }
        }
        
    }
}

驗證短信驗證碼

驗證短信驗證碼時間是否過時,驗證短信驗證碼是否正確。api

// 驗證短信驗證碼是否有效
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提供了生成圖片驗證碼的函數,下面咱們來實現驗證碼的生成、刷新和驗證。session

生成和刷新圖片驗證碼

// 獲取圖片驗證碼,刷新圖片驗證碼
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屬性便可。

驗證圖片驗證碼

// 驗證驗證碼是否正確
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方法。

跑通先後端

後端修改

驗證圖片驗證碼函數,改成被調用函數:

public function checkPicCode($picCode){
    $verify = new \Think\Verify();
    if($verify->check($picCode, 2333)){
        return true;
    }else{
        return false;
    };
}

在獲取短信驗證碼函數的最頂部,添加調用圖片驗證碼函數,只有經過驗證,才發送請求給雲片。

// 獲取短信驗證碼
public function getSMSCode(){
    $picCode = $_POST['picCode'];
    if(!$this->checkPicCode($picCode)){
        $result = array(
            'code' => '1',
            'ext' => '驗證碼錯誤,請從新輸入'
        );
        echo json_encode($result,JSON_UNESCAPED_UNICODE);
        return;
    }
    /*省略*/
}

前端核心代碼

<!--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.js
angular.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(function successCallback(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);
            }
        }, function errorCallback(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(function successCallback(response) {
            console.log(response.data);
            if(response.data.code == '0'){
                $scope.isShow1 = false;
                $scope.isShow2 = true;
            }
        }, function errorCallback(response) {
            console.log(response.data);
        });
    }

    // 刷新圖片驗證碼
    $scope.refresh = function(){
        $scope.picCodeUrl = '/owner-bd/index.php/Home/CheckCode/getPicCode?'+Math.random();
    }

});

優化

以上代碼,安全性不是很好,咱們能夠利用工具繞過前端驗證。爲了不這個問題,能夠在checkPicCode和checkSMSCode函數中添加session值來標記。

$_SESSION['checkPicCode'] = true;
$_SESSION['checkSMSCode'] = true;

在最後一步,向數據庫中添加用戶時,先驗證一下兩個session值是否都爲true,都爲true時再添加。

成果

後記

之後也許有用的代碼:

echo json_encode($_SESSION);// 打印出session中的數據
echo session_id();// 打印當前session的id

書籤

雲片網
https://www.yunpian.com/

cURL函數
http://php.net/manual/zh/ref....

curl 基礎例子
http://php.net/manual/zh/curl...

在PHP語言中使用JSON
http://www.ruanyifeng.com/blo...

thinkphp驗證碼
http://document.thinkphp.cn/m...

修改ThinkPHP的驗證碼類
http://www.cnblogs.com/BTMast...

ThinkPHP 3.2版本 , 沒法讀取$_SESSION['verify_code']
http://www.cnblogs.com/lovezb...

LICEcap - Download
http://licecap.en.softonic.com/

gif動態圖局部加馬賽克模糊廣告文字
http://www.leawo.cn/space-138...

相關文章
相關標籤/搜索