我的站内有一些类似用户和用户之间转账的功能,如何保证这个功能的安全

如题我的站内有一些类似用户和用户之间转账的功能,如何保证这个功能的安全,请问:我这个功能算API吗? 看了很多API安全方面的文章,那么我这个转账功能是否安全,我看有些文章说什么要加上密钥,进行加密。 但我这个功能好像又不算api吧? 属于站内功能,附上该转账功能的代码,求指点
前端就是一个正常的form表单,已经带上了csrf

public function user_transfer(Request $request){

        $request->validate([
            'amount' => 'required|numeric|min:1',
            'phone' => 'required|numeric|exists:users,phone',
        ]);

        //判断用户余额是否大于等于转账金额
        if(Auth::user()->money >= $request->amount){
        $user = Auth::user();
        //扣除用户余额
        $user->decrement('money',$request->amount);

        $mod = new User();
        $mod = $mod->where('phone',$request->phone)->first();
        //给转账对象增加余额
        $mod->increment('money',$request->amount);

        session()->flash('success','转账成功');    
        return redirect()->back()->withInput();

        }else{
            session()->flash('fail','余额不足');    
            return redirect()->back()->withInput();
        }

    }
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
讨论数量: 10

从代码上来看,你这不是前后端分离的项目,前后端分离项目是不会有 CSRF-TOKEN 的,这里不需要考虑安全性问题,CSRF-TOKEN 已经够安全了,你这个转账代码,最大的问题就是:
同时两个不同 SESSION 的窗口登录同一个账号,可能会发生扣款一次,转账两次的情况

综合楼上几位所说的,一共三点:

  1. 开启事务

  2. 添加并发锁

  3. 添加转账记录

    public function user_transfer(Request $request){
    
         $request->validate([
             'amount' => 'required|numeric|min:1',
             'phone' => 'required|numeric|exists:users,phone',
         ]);
    
         //判断用户余额是否大于等于转账金额
         if(Auth::user()->money >= $request->amount){
         $user = Auth::user();
         //扣除用户余额
         // 开启数据库事务
         DB::beginTransaction();
         if($lock = Cache::lock('user_transfer_'. $user->id))   // 获取原子锁,原子锁以用户 id 为区分,避免阻塞
         {
               if(!$res = $user->where('money' '>=' $request->amount)->decrement('money',$request->amount)){
                 DB::rollback(); 
                 $lock->release();
                 throw new \Exception('转账失败,CODE 你的业务码');
               } // 原子锁,杜绝恶意并发请求 + 乐观锁,防止其他业务更新了 user 数据,造成余额不足的情况
                 $mod = new User();
                 $mod = $mod->where('phone',$request->phone)->first();
         //给转账对象增加余额
                 $mod->increment('money',$request->amount);
    
         // 写入 UserTransferBill 模型,自己创建表,需要自动维护 create 和 update 时间戳
                 UserTransferBill::create([
                         'user_id' => $user->id,
                         'to' => $mod->id,
                         'number' => $request->amount,
                 ]);
               DB::commit(); 
               $lock->release();
         } else {    // 没有获取到原子锁,说明有并发操作,其他登录同账号的用户也在发起转账,直接驳回请求
                 DB::rollback(); 
                 $lock->release();  // 无论成功与失败,都要释放掉锁,避免出现死锁情况
                 throw new \Exception('转账失败,CODE 你的业务码');
         }
    
         session()->flash('success','转账成功');    
         return redirect()->back()->withInput();
    
         }else{
             session()->flash('fail','余额不足');    
             return redirect()->back()->withInput();
         }
    
     }

    代码使用了 乐观锁

    where('money' '>=' $request->amount)

    扣除用户余额时,增加判断,当前的用于余额是否足够支付扣除这次转账,避免其他业务更新用户余额,比如管理后台手动扣除用户余额,造成余额不足的情况。

差不多了,我没测试,逻辑上是这样的,你可以自己再改改。

3年前 评论
lddtime 3年前
MArtian (作者) 3年前
pndx 3年前
MArtian (作者) 3年前
MArtian (作者) 3年前
Cooper

最最最起码加个事务吧 :speak_no_evil:

3年前 评论

你这个随便来个并发就能把你打爆,最基本的也一定要加where,加事务吧,扣前实时校验,后面还有频次限制,最大值限制,还有其它很多的

3年前 评论

🌚 请问下网站地址多少

3年前 评论

同意楼上两个老哥说的,赶紧加个事务,涉及到钱的操作一定要确保安全,还要加个操作日志!再进一步的话就考虑并发和次数限制,想象一下假如遇到很多人同时付款,那真的就乱套了,还有由于网络原因一直重复提交支付申请,这个问题也要注意!

3年前 评论

从代码上来看,你这不是前后端分离的项目,前后端分离项目是不会有 CSRF-TOKEN 的,这里不需要考虑安全性问题,CSRF-TOKEN 已经够安全了,你这个转账代码,最大的问题就是:
同时两个不同 SESSION 的窗口登录同一个账号,可能会发生扣款一次,转账两次的情况

综合楼上几位所说的,一共三点:

  1. 开启事务

  2. 添加并发锁

  3. 添加转账记录

    public function user_transfer(Request $request){
    
         $request->validate([
             'amount' => 'required|numeric|min:1',
             'phone' => 'required|numeric|exists:users,phone',
         ]);
    
         //判断用户余额是否大于等于转账金额
         if(Auth::user()->money >= $request->amount){
         $user = Auth::user();
         //扣除用户余额
         // 开启数据库事务
         DB::beginTransaction();
         if($lock = Cache::lock('user_transfer_'. $user->id))   // 获取原子锁,原子锁以用户 id 为区分,避免阻塞
         {
               if(!$res = $user->where('money' '>=' $request->amount)->decrement('money',$request->amount)){
                 DB::rollback(); 
                 $lock->release();
                 throw new \Exception('转账失败,CODE 你的业务码');
               } // 原子锁,杜绝恶意并发请求 + 乐观锁,防止其他业务更新了 user 数据,造成余额不足的情况
                 $mod = new User();
                 $mod = $mod->where('phone',$request->phone)->first();
         //给转账对象增加余额
                 $mod->increment('money',$request->amount);
    
         // 写入 UserTransferBill 模型,自己创建表,需要自动维护 create 和 update 时间戳
                 UserTransferBill::create([
                         'user_id' => $user->id,
                         'to' => $mod->id,
                         'number' => $request->amount,
                 ]);
               DB::commit(); 
               $lock->release();
         } else {    // 没有获取到原子锁,说明有并发操作,其他登录同账号的用户也在发起转账,直接驳回请求
                 DB::rollback(); 
                 $lock->release();  // 无论成功与失败,都要释放掉锁,避免出现死锁情况
                 throw new \Exception('转账失败,CODE 你的业务码');
         }
    
         session()->flash('success','转账成功');    
         return redirect()->back()->withInput();
    
         }else{
             session()->flash('fail','余额不足');    
             return redirect()->back()->withInput();
         }
    
     }

    代码使用了 乐观锁

    where('money' '>=' $request->amount)

    扣除用户余额时,增加判断,当前的用于余额是否足够支付扣除这次转账,避免其他业务更新用户余额,比如管理后台手动扣除用户余额,造成余额不足的情况。

差不多了,我没测试,逻辑上是这样的,你可以自己再改改。

3年前 评论
lddtime 3年前
MArtian (作者) 3年前
pndx 3年前
MArtian (作者) 3年前
MArtian (作者) 3年前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!