PHP接口数据安全解决方案(一)

前言

目的:

  • 1.实现前后端代码分离,分布式部署
  • 2.利用token替代session实现状态保持,token是有时效性的满足退出登录,token存入redis可以解决不同服务器之间session不同步的问题,满足分布式部署
  • 3.利用sign,前端按照约定的方式组合加密生成字符串来校验用户传递的参数跟后端接收的参数是否一直,保障接口数据传递的安全
  • 4.利用nonce,timestamp来保障每次请求的生成sign不一致,并将sign与nonce组合存入redis,来防止api接口重放

目录介绍



├── Core
│   ├── Common.php(常用的公用方法)
│   ├── Controller.php (控制器基类)
│   └── RedisService.php (redis操作类)
├── config.php (redis以及是否开启关闭接口校验的配置项)
├── login.php (登录获取token入口)
└── user.php(获取用户信息,执行整个接口校验流程)

登录鉴权图


接口请求安全性校验整体流程图


代码展示

common.php

<?php
namespace Core;
/**
 * @desc 公用方法
 * Class Common
 */
class Common{
    /**
     * @desc 输出json数据
     * @param $data
     */
    public static function outJson($code,$msg,$data=null){
        $outData = [
            'code'=>$code,
            'msg'=>$msg,
        ];
        if(!empty($data)){
            $outData['data'] = $data;
        }
        echo  json_encode($outData);
        die();
    }

    /***
     * @desc 创建token
     * @param $uid
     */
    public static function createToken($uid){
        $time = time();
        $rand = mt_rand(100,999);
        $token = md5($time.$rand.'jwt-token'.$uid);
        return $token;
    }

    /**
     * @desc 获取配置信息
     * @param $type 配置信息的类型,为空获取所有配置信息
     */
    public static function getConfig($type=''){
        $config = include "./config.php";
        if(empty($type)){
            return $config;
        }else{
            if(isset($config[$type])){
                return $config[$type];
            }
            return [];
        }
    }

}

RedisService.php

<?php
namespace Core;
/*
 *@desc redis类操作文件
 **/
class RedisService{
    private $redis;
    protected $host;
    protected $port;
    protected $auth;
    protected $dbId=0;
    static private $_instance;
    public $error;

    /*
     *@desc 私有化构造函数防止直接实例化
     **/
    private function __construct($config){
        $this->redis    =    new \Redis();
        $this->port        =    $config['port'] ? $config['port'] : 6379;
        $this->host        =    $config['host'];
        if(isset($config['db_id'])){
            $this->dbId = $config['db_id'];
            $this->redis->connect($this->host, $this->port);
        }
        if(isset($config['auth']))
        {
            $this->redis->auth($config['auth']);
            $this->auth    =    $config['auth'];
        }
        $this->redis->select($this->dbId);
    }

    /**
     *@desc 得到实例化的对象
     ***/
    public static function getInstance($config){
        if(!self::$_instance instanceof self) {
            self::$_instance = new self($config);
        }
        return self::$_instance;

    }

    /**
     *@desc 防止克隆
     **/
    private function __clone(){}

    /*
     *@desc 设置字符串类型的值,以及失效时间
     **/
    public function set($key,$value=0,$timeout=0){
        if(empty($value)){
            $this->error = "设置键值不能够为空哦~";
            return $this->error;
        }
        $res = $this->redis->set($key,$value);
        if($timeout){
            $this->redis->expire($key,$timeout);
        }
        return $res;
    }

    /**
     *@desc 获取字符串类型的值
     **/
    public function get($key){
        return $this->redis->get($key);
    }

}

Controller.php

<?php
namespace Core;
use Core\Common;
use Core\RedisService;

/***
 * @desc 控制器基类
 * Class Controller
 * @package Core
 */
class Controller{
    //接口中的token
    public $token;
    public $mid;
    public $redis;
    public $_config;
    public $sign;
    public $nonce;

    /**
     * @desc 初始化处理
     * 1.获取配置文件
     * 2.获取redis对象
     * 3.token校验
     * 4.校验api的合法性check_api为true校验,为false不用校验
     * 5.sign签名验证
     * 6.校验nonce,预防接口重放
     */
    public function __construct()
    {
        //1.获取配置文件
        $this->_config = Common::getConfig();
        //2.获取redis对象
        $redisConfig = $this->_config['redis'];
        $this->redis = RedisService::getInstance($redisConfig);

        //3.token校验
        $this->checkToken();
        //4.校验api的合法性check_api为true校验,为false不用校验
        if($this->_config['checkApi']){
            // 5. sign签名验证
            $this->checkSign();

            //6.校验nonce,预防接口重放
            $this->checkNonce();
        }
    }

    /**
     * @desc 校验token的有效性
     */
    private  function checkToken(){
        if(!isset($_POST['token'])){
            Common::outJson('10000','token不能够为空');
        }
        $this->token = $_POST['token'];
        $key = "token:".$this->token;
        $mid = $this->redis->get($key);
        if(!$mid){
            Common::outJson('10001','token已过期或不合法,请先登录系统  ');
        }
        $this->mid = $mid;
    }

    /**
     * @desc 校验签名
     */
    private function checkSign(){
        if(!isset($_GET['sign'])){
            Common::outJson('10002','sign校验码为空');
        }
        $this->sign = $_GET['sign'];
        $postParams = $_POST;
        $params = [];
        foreach($postParams as $k=>$v) {
            $params[] = sprintf("%s%s", $k,$v);
        }
        sort($params);
        $apiSerect = $this->_config['apiSerect'];
        $str = sprintf("%s%s%s", $apiSerect, implode('', $params), $apiSerect);
        if ( md5($str) != $this->sign ) {
            Common::outJson('10004','传递的数据被篡改,请求不合法');
        }
    }

    /**
     * @desc nonce校验预防接口重放
     */
    private function checkNonce(){
        if(!isset($_POST['nonce'])){
            Common::outJson('10003','nonce为空');
        }
        $this->nonce = $_POST['nonce'];
        $nonceKey = sprintf("sign:%s:nonce:%s", $this->sign, $this->nonce);
        $nonV = $this->redis->get($nonceKey);
        if ( !empty($nonV)) {
            Common::outJson('10005','该url已经被调用过,不能够重复使用');
        } else {
            $this->redis->set($nonceKey,$this->nonce,360);
        }
    }

}

config.php

<?php
return [
    //redis的配置
    'redis' => [
        'host' => 'localhost',
        'port' => '6379',
        'auth' => '123456',
        'db_id' => 0,//redis的第几个数据库仓库
    ],
    //是否开启接口校验,true开启,false,关闭
    'checkApi'=>true,
    //加密sign的盐值
    'apiSerect'=>'test_jwt'
];

login.php

<?php
/**
 * @desc 自动加载类库
 */
spl_autoload_register(function($className){
    $arr = explode('\\',$className);
    include $arr[0].'/'.$arr[1].'.php';
});

use Core\Common;
use Core\RedisService;

if(!isset($_POST['username']) || !isset($_POST['pwd'])  ){
    Common::outJson(-1,'请输入用户名和密码');
}
$username = $_POST['username'];
$pwd = $_POST['pwd'];
if($username!='admin' || $pwd!='123456' ){
    Common::outJson(-1,'用户名或密码错误');
}
//创建token并存入redis,token对应的值为用户的id
$config = Common::getConfig('redis');
$redis = RedisService::getInstance($config);
//假设用户id为2
$uid = 2;
$token = Common::createToken($uid);
$key = "token:".$token;
$redis->set($key,$uid,3600);
$data['token'] = $token;
Common::outJson(0,'登录成功',$data);

user.php

<?php
/**
 * @desc 自动加载类库
 */
spl_autoload_register(function($className){
    $arr = explode('\\',$className);
    include $arr[0].'/'.$arr[1].'.php';
});

use Core\Controller;
use Core\Common;
class UserController extends Controller{

    /***
     * @desc 获取用户信息
     */
    public function getUser(){
        $userInfo = [
            "id"=>2,
            "name"=>'巴八灵',
            "age"=>30,
        ];
        if($this->mid==$_POST['mid']){
            Common::outJson(0,'成功获取用户信息',$userInfo);
        }else{
            Common::outJson(-1,'未找到该用户信息');
        }
    }
}
//获取用户信息
$user = new  UserController();
$user->getUser();

演示用户登录


简要描述:

  • 用户登录接口

请求URL:

  • http://localhost/login.php

请求方式:

  • POST

参数:

参数名必选类型说明
usernamestring用户名
pwdstring密码

返回示例

{
    "code": 0,
    "msg": "登录成功",
    "data": {
        "token": "86b58ada26a20a323f390dd5a92aec2a"
    }
}

{
    "code": -1,
    "msg": "用户名或密码错误"
}

演示获取用户信息

简要描述:

  • 获取用户信息,校验整个接口安全的流程

请求URL:

  • http://localhost/user.php?sign=f39b0f2dea817dd9dbef9e6a2bf478de

请求方式:

  • POST

参数:

参数名必选类型说明
tokenstringtoken
midint用户id
noncestring防止用户重放字符串 md5加密串
timestampint当前时间戳

返回示例

{
    "code": 0,
    "msg": "成功获取用户信息",
    "data": {
        "id": 2,
        "name": "巴八灵",
        "age": 30
    }
}

{
    "code": "10005",
    "msg": "该url已经被调用过,不能够重复使用"
}

{
    "code": "10004",
    "msg": "传递的数据被篡改,请求不合法"
}
{
    "code": -1,
    "msg": "未找到该用户信息"
}

文章完整代码地址


点击查看源代码

后记


上面完整的实现了整个api的安全过程,包括接口token生成时效性合法性验证,接口数据传输防篡改,接口防重放实现。仅仅靠这还不能够最大限制保证接口的安全。条件满足的情况下可以使用https协议从数据底层来提高安全性,另外本实现过程token是使用redis存储,下一篇文章我们将使用第三方开发的库实现JWT的规范操作,来替代redis的使用。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 传奇Java支付平台提供了POST接口,可以用于实现在线支付功能。POST接口使用HTTP协议,通过POST方法向PayAPI服务器提交支付请求。 使用POST接口的前提条件是用户已经在传奇Java支付平台注册并且已经开通API功能。用户需要获取商户号、商户秘钥和支付网关地址等信息,并按照要求进行配置。 在使用POST接口提交支付请求时,用户需要提供以下信息: 1. 商户号:由传奇Java支付平台提供,用于标识用户身份。 2. 订单号:由用户自行生成,用于标识唯一订单。 3. 订单金额:支付的金额,单位为分。 4. 商品描述:支付的商品名称或描述。 5. 回调地址:支付成功后,PayAPI服务器向用户提供的回调地址发送支付结果通知。 6. 签名:对请求参数使用商户秘钥进行签名生成的字符串,用于保证请求的安全性。 提交支付请求后,PayAPI服务器会对请求参数进行验证,验证通过后会向支付网关发送支付请求。用户在支付网关中完成支付后,PayAPI服务器会向用户提供的回调地址发送支付结果通知,用户可以通过解析通知信息来确认支付是否成功。 总的来说,传奇Java支付平台的POST接口提供了便捷的在线支付功能,可广泛应用于电商、游戏、金融等领域。 ### 回答2: Java支付平台一般使用的是HTTP协议,Post接口是其中较为常用的一种方式。这种方式通常使用form表单的方式,将数据以键值对的方式进行传递。 参考代码如下: ``` <form action="http://pay.javapay.com/post.php" method="post"> <input type="hidden" name="order_id" value="123456"> <input type="hidden" name="order_name" value="测试订单"> <input type="hidden" name="amount" value="100"> <input type="hidden" name="nonce_str" value="abc123"> <input type="hidden" name="timestamp" value="1572591449"> <input type="hidden" name="sign_method" value="md5"> <input type="hidden" name="sign" value="2324234fsdfsd4fsdf23423"> <input type="submit" value="提交"> </form> ``` 上述代码中,我们可以看到HTTP请求的地址为 http://pay.javapay.com/post.php 。该地址可以根据实际情况进行修改。接着,我们可以将需要传递的支付参数,按照键值对的方式,以隐藏的方式进行传递。其中包括了支付订单ID,订单名称,支付金额,随机字符串,时间戳等参数。最后,我们需要将所有参数进行签名,以确保数据传输的安全性。 通过以上方式,我们可以通过Post接口将支付需要的数据进行传递,并完成支付。 ### 回答3: Java支付平台是一种业内领先的在线支付解决方案。它可以帮助商家快速接入多种支付渠道,支持多种支付方式,并且还具有强大的安全和风险控制功能。 Post接口是Java支付平台中最常用的接口之一。通过Post接口,商家可以将订单信息提交给Java支付平台进行支付处理。该接口采用HTTP协议进行通信,具有可扩展性和易于集成的优点。 要使用Post接口进行支付,商家需要向Java支付平台发送POST请求,并包含必要的订单信息。Java支付平台将返回支付结果,并根据结果更新订单状态。 在使用Post接口时,商家需要注意以下几点: 首先,商家需要在Java支付平台上注册并获得商户号。商户号是商家在Java支付平台上的唯一标识,用于识别商家身份并处理支付请求。 其次,商家需要构造正确的POST请求。请求中需要包含商户号、订单号、订单金额、支付方式等必要信息。这些信息需要经过签名处理和加密传输,确保信息安全性。 最后,商家需要根据支付结果更新订单状态,并向用户反馈支付结果。如果支付成功,商家需要向用户发送确认信息并完成订单流程;如果支付失败,商家需要提供相应的错误提示并引导用户重新发起支付或选择其他支付方式。 总之,传奇Java支付平台Post接口是商家接入Java支付平台的必备工具,具有简单易用、高效稳定、强大安全等优点,可以为商家提供优质的在线支付体验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值