Laravel: 生成随机唯一令牌

12

我在我的数据库中有一张名为 keys 的表,它的结构如下:

id | user_id | token_id | token_key
每次用户登录我的网站时,我需要为该用户生成一个新的token_idtoken_key。如何生成两个值都是唯一的随机令牌?例如,如果token_iddfbs98641aretwsgtoken_keysdf389dxbf1sdz51fga65dfg74asdf,它们的意义分别是什么?
id | user_id | token_id         | token_key
1  | 1       | dfbs98641aretwsg | sdf389dxbf1sdz51fga65dfg74asdf

表格中不能有与这些标记组合相同的其他行。我该怎么做?

6个回答

25

在生成令牌方面,您可以使用Laravel帮助程序之一 str_random() 方法。

这将生成指定长度的随机字符串,例如 str_random(16) 将生成一个由 16 个字符(大写字母、小写字母和数字)组成的随机字符串。

根据您如何使用令牌,它们是否真的需要完全独特?鉴于它们将匹配到一个用户,或者我假设您可能正在使用 token_id 然后对其进行验证以针对 token_key 进行比较,那么它们是否重复并不重要?尽管这种情况发生的几率极小!

但是,如果您确实需要它们真正独一无二,您始终可以使用带有 unique 约束的验证器。使用 此软件包,您还可以使用 unique_with 测试它们两个是否唯一。然后,如果验证器失败,则根据需要生成新令牌。

根据您的示例,您将使用 str_random(16) 作为 token_id,并使用 str_random(30) 作为 token_key


20

对于这种情况,我会避免添加额外的软件包。可以尝试以下代码:

do {
    $token_id = makeRandomToken();
    $token_key = makeRandomTokenKey();
} while (User::where("token_id", "=", $token_id)->where("token_key", "=", $token_key)->first() instanceof User);

...应该做的事情。如果您的模型名称与“User”不同,请替换它,并使用您的或建议的函数来创建随机字符串。


6
do-while循环会在“while”语句评估为true时一直“执行”(并且它至少会运行一次,不像while-do)。在这个具体的例子中,它将生成令牌ID和密钥对,然后尝试在数据库中查找具有该特定令牌和密钥对的用户。 instanceof检查结果是否是User类的实例,这意味着它正在检查是否找到了这样的用户。如果是,则do将再次执行并生成另一对令牌ID /密钥,依此类推,直到找不到具有生成的令牌ID /密钥对的用户。 - Oliver Maksimovic
13
这是一个非常糟糕的解决方案!每次想要添加新记录时,你都不应该希望循环遍历数据库表中的每个记录。你最好使用基于时间的唯一字符串。 - Marten
7
每次添加新记录时,它不会循环遍历表中的每个记录。请尝试理解do-while循环。 - Oliver Maksimovic
3
我同意 @Marten 的观点,这不是一个好的解决方案。它存在两个问题:首先,每次创建新的令牌时都会至少一次地访问数据库 - 如果令牌已存在于数据库中,则还要再次访问。其次,不能保证唯一性。在当前线程检查其存在并插入它之前,另一个线程可能会将相同的键插入到数据库中。为什么不使用 UUID,它们在唯一性方面具有很高的安全度? - impeto
8
一种可能的改进是仅尝试插入新令牌,而不是选择它们。如果它们不是唯一的(假设数据库正确设置了唯一索引),则请求将失败,您将返回循环并创建新对并重试,直到操作成功。这将保证操作的原子性。 - ivanhoe
显示剩余3条评论

1
你可以使用依赖项来完成此操作。 Dirape laravel-token 运行命令
composer require dirape/token

在你的控制器中使用:
use Dirape\Token\Token;

你可以像这样使用它:

User::create([
        'name'             => $data['name'],
        'email'            => $data['email'],
        'password'         => bcrypt($data['password']),
        'token_key' => (new Token())->Unique('users', 'api_token', 60),
        'active'           => 1
    ])

1

根据@ivanhoe的建议...这是我想出来的:

    $token = new Token;

    //in case there are duplicate
    for ($x = 0; $x < 10; $x ++) {
        $token->access_token = str_random(16);;
        try {
            if ($token->save()) {
                break;
            }
        }catch (QueryException $e) {

        }
    }

0

我认为这样更好:

do {
    $this->slug = strtolower(str()->random(5));
} while (User::where("slug", $this->slug)->exists());

-1

对于看到这篇帖子的其他用户。我发现的最好的解决方案是使用另一个字符串或整数来生成令牌,例如Laravel在创建新用户时所做的操作。

base64_encode($data['email']); // here using the email as base

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接