1、使用场景
某些业务场景需限定一个账号只能同时在一个设备登录。
防异处登录多用于 APP 中,需配合 API 接口使用。
基于 API 接口实现,在实际线上生产环境中,API 接口是需要做验证的,即不是随便被请求就可以拿到数据的。需要对包括但不限于对用户登录成功返回的标识、token等的验证。
2、实现思路
- 用户注册完成后每次登录时,随机生成一个随机码
- 再把用户账号+密码+随机码按照一定顺序和一定的加密方式加密成一个字符串,此字符串用作 token
- 将随机码和 token 储存到表中
- 登录成功,返回客户端用户账号和 token 值,供随后调用其他接口使用
- 用户每次请求接口时在初始化公共方法里面做验证,请求接口需要的公共参数字段有用户账号,和登录所返回的 token,此时用用户账号作为查询条件取出该用户的加密随机字符串和当时生成的 token 值,按照加密时的规则再加密一次获得 token 和客户端传过来的进行比对,错误则驳回此次请求并返回空数据,并提示账号可能存在风险或已在其他设备登录。
- 用户账号可以替换为用户的唯一id,更简短,保证唯一即可
3、效果描述
当用户按照流程注册登录后,生成第一次的加密随机字符串和 token ,返回给客户端正常通过验证获取其他接口数据,当此账号在另一个设备登录时,此账号的随机字符串和 token 被更新,在当前设备请求接口时,token 比对不正确,提示退出登录。
4、部分代码(thinkphp5)
用户登录
$account = $params['account'];
$password = $params['password'];
// 省略判断账号是否存在和密码是否正确
// 生成随机字符串
// createRandStr()需要自己封装
$salt = createRandStr();
// 加密成 token
$token = md5($account.$pwd.$salt);
// 更新到数据库
$update = Db::name('member')
->where('account', $account)
->update(['salt' => $salt, 'token' => $token]);
// 返回到客户端
// result()方法需自己封装
$this->result('登录成功', ['account' => $account, 'token' => $token], 200);
接口初始化公共方法
public function _initialize()
{
parent::_initialize();
$params = $this->request->post();
$account = $params['account'];
$token = $params['tokens'];
if (!$account || !$token) {
$this->result('参数错误', [], 100);
}
// token验证放在了model中
$model = new Check();
if (!$model->check($account, $token)) {
$this->result('token错误', [], 101);
}
}
模型 token 验证
public function check($account, $token)
{
$member = $this->where('account', $account)->find();
if (md5($account.$member['password'].$member['salt']) != $token) {
return false;
} else {
return true;
}
}