REST API注册场景下不使用Session的标准数据暂存替代方案
你当前实现的核心问题是把跨请求的用户上下文存在服务端Session中,违反了REST的无状态约束:无状态要求每一次客户端请求必须携带服务端处理该请求所需的全部上下文信息,服务端不应该依赖之前请求存储的会话数据,否则在API多节点部署、跨端调用的场景下会出现Session不一致、跨域失效等问题。
你场景里的注册流程是典型的两步校验流程:第一步提交手机号+可选姓名,发送验证码;第二步提交验证码完成注册,完全可以不用Session实现,以下是三种生产环境常用的合规方案:
方案1:客户端暂存上下文,校验接口全量传参
这是最贴合REST设计原则的实现,服务端只负责存储验证码和手机号的绑定关系(属于全局业务校验数据,不属于会话状态),用户第一步输入的姓名由客户端本地暂存,提交验证码的时候和手机号、验证码一起传给服务端即可。
代码示例:
// 第一步:发送验证码接口 public function sendVerifyCode(Request $request) { $validData = $this->validate($request, [ 'mobile' => 'required|regex:/^09\d{9}$/|size:11', ]); // 生成6位验证码,存入全局缓存,设置5分钟过期 $code = random_int(100000, 999999); Cache::put('sms_verify:' . $validData['mobile'], $code, now()->addMinutes(5)); // 发送短信 $sms = new SendSms($validData['mobile'], 43, ['code' => $code]); $sms->send(); return response()->json([ 'data' => 'verification code is sent', 'status' => 200 ]); } // 第二步:验证码校验+注册接口 public function register(Request $request) { $validData = $this->validate($request, [ 'mobile' => 'required|regex:/^09\d{9}$/|size:11', 'verify_code' => 'required|size:6', 'user_full_name' => 'nullable|max:20|min:3', ]); // 校验验证码 $cacheKey = 'sms_verify:' . $validData['mobile']; $savedCode = Cache::get($cacheKey); if (!$savedCode || $savedCode != $validData['verify_code']) { return response()->json(['error' => '验证码错误或已过期'], 400); } // 校验通过直接创建用户,所有参数均来自当前请求 $user = User::create([ 'mobile' => $validData['mobile'], 'full_name' => $validData['user_full_name'] ]); // 销毁已使用的验证码 Cache::forget($cacheKey); return response()->json([ 'data' => $user, 'access_token' => $user->createToken('register')->plainTextToken, 'status' => 200 ]); }
- 优缺点:实现逻辑最简单,完全符合无状态要求,没有额外加解密开销;仅需要客户端在本地暂存用户输入的姓名即可,由于姓名本身是用户可自主修改的字段,即使被篡改也不存在业务安全问题。
方案2:返回签名临时凭证,服务端验签读取上下文
如果不想让客户端暂存多个字段,可以在发送验证码接口把用户提交的姓名、手机号、过期时间打包做签名,生成一个临时校验令牌返回给客户端,第二步请求时客户端只需要携带这个令牌+验证码即可,服务端验签通过后直接从令牌中解析出姓名数据,不需要存储额外上下文。
核心实现逻辑:
// 发码接口生成签名令牌 $payload = [ 'mobile' => $validData['mobile'], 'user_full_name' => $validData['user_full_name'], 'exp' => now()->addMinutes(5)->timestamp ]; // 使用项目密钥生成签名,防止内容被篡改 $sign = hash_hmac('sha256', json_encode($payload), config('app.key')); $verifyToken = base64_encode(json_encode($payload)) . '.' . $sign; // 把$verifyToken返回给客户端,第二步校验时验签即可
- 优缺点:客户端只需要存储一个令牌字段即可,数据带签名无法被篡改;缺点是有少量加解密开销,令牌长度略长于普通参数。
方案3:全局缓存存储临时注册数据
如果不想让客户端传递姓名参数,也不想做签名,可以在发送验证码时,把用户提交的姓名和验证码一起绑定存入全局共享缓存(Redis等),key直接和手机号绑定,设置和验证码一致的过期时间。校验验证码时直接从缓存中读取对应的姓名即可。
注意:这种实现和Session的本质区别是,缓存是所有服务节点共享的,不依赖客户端携带SessionID识别会话,不属于REST禁止的服务端会话存储,不违反无状态原则。
- 优缺点:客户端不需要暂存任何额外数据,安全性最高;缺点是服务端需要额外存储临时注册数据,缓存占用略高。
普通ToC应用优先选方案1,实现成本最低,维护最简单;如果对参数防篡改有要求选方案2;如果客户端不方便暂存数据选方案3即可。
内容的提问来源于stack exchange,提问作者Pouya Vaghefi

