【问题标题】:Account security by Sending code in email instead of SMS: Laravel 5.2通过电子邮件而不是 SMS 发送代码来确保帐户安全:Laravel 5.2
【发布时间】:2016-12-11 12:01:22
【问题描述】:

当我们第一次登录我们的 gmail 帐户或删除缓存和 cookie 后,我们会在窗口中输入一个发送到我们手机的代码。

我正在尝试通过电子邮件而不是 SMS 来实现这一点。下面是我实现这一点的方法。

I am following this link : https://laravel.com/docs/5.2/session

并在数据库中创建一个Session table。我还可以在会话表记录中查看我的浏览器详细信息。我不确定这是否是正确的方法。

Gmail 可以跟踪多个浏览器。这意味着如果我上次从 Firefox 登录,这次从 Chrome 登录,那么我将再次被要求输入代码。今后,如果未删除缓存/cookie,将不会要求我为 Chrome 和 Firefox 填写代码。

有人可以给我任何链接来解释如何在保存缓存/cookie 时为多个浏览器提供服务吗?这样我就可以发送电子邮件以获取安全码

【问题讨论】:

  • 为什么使用 gmail 作为用户代理,$_SERVER 数组中有。 php.net/manual/en/reserved.variables.server.php$_SERVER['HTTP_USER_AGENT']还有IP,$_SERVER['REMOTE_ADDR']
  • 非常感谢您的评论。那只是解释我的情况的参考。我正在尝试将浏览器的状态保存在某处,以便下次检查是否发送代码。
  • 在会话表中存储的逻辑位置,然后当他们尝试登录时,您可以检查用户的最后一个 IP、UserAgent 等等等。您还可以轻松创建一个单独的表链接给用户存储多对一关系,一个用户多个用户代理/IP地址。
  • 就个人而言,我会自己处理所有数据,否则您需要依赖外部 API 来实现基本功能(登录)才能工作。他们改变了一些东西,你的网站就坏了,它不像我们正在谈论的字体。
  • 但这仅支持一种浏览器详细信息。如果您登录 gmail,然后两次从同一 IP 登录 Firefox,您将第一次被询问。但是下次如果没有保存缓存和cookie,他们会要求提供代码。但在 Session Table 中,我们支持一种浏览器的详细信息。

标签: php laravel laravel-5 laravel-5.1 laravel-5.2


【解决方案1】:

您可以通过发出一个额外的 cookie(比如说 browser_cookie)来记住已经过身份验证的浏览器来实现这一点。

实施:

创建下表(browser_management):

token (pk)| user_id (fk) | series_identifier

地点:

  • token:发给用户的令牌的散列形式(使用 bcrypt 或类似算法)(发给用户的令牌本身是不可猜测的从适当大的空间随机生成的密钥)

  • series_identifier:从适当大的空间中随机生成的不可猜测的密钥

每当用户登录时检查browser_cookie。

案例1:用户第一次登录。

考虑到用户是第一次登录,browser_cookie 将不存在。因此,您将发送一封带有验证码的电子邮件。

通过身份验证后,为token 和series_identifier 分别生成两个随机数。对于user_id 标识的用户,将散列的token 和series_identifier 存储在browser_management 表中。

另外,使用token 和series_identifier 向用户发出browser_cookie。

案例2:用户下次重新登录。

现在,当同一用户下次登录时,获取token 并在browser_management 表中找到带有散列token 的条目。

如果找到,请检查 user_id 和 series_identifier 是否匹配。

案例 2.1: 匹配的条目:

允许用户进入系统而无需重新验证电子邮件代码。

生成另一个令牌并将cookie 和table 中的token 替换为新令牌。 (这将降低会话劫持的风险)。

案例 2.2: 条目不匹配:

按照电子邮件身份验证的步骤并通知用户可能被盗。(就像 gmail 通知新的浏览器登录一样)。

参考资料:

更新:

示例代码:

迁移:

<?php

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class browser_management extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('browser_management', function (Blueprint $table) {
            $table->string('token');
            $table->string('user_id');
            $table->string('series_identifier');            
            $table->timestamps();
            $table->primary('token');
            $table->foreign('user_id')->references('id')->on('users');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::drop('users');
    }
}

中间件:创建一个新的中间件

<?php
namespace App\Http\Middleware;

use Closure;
use Illuminate\Support\Facades\Auth;
use Cookies;

class Email_verification
{
    public function handle($request, Closure $next, $guard = null)
    {
        //retrieve $token from the user's cookie
        $token = $request->cookie('browser_cookie');

        //check id token is present
        if($token == null){
            //if token is not present allow the request to the email_verification
            return $next($request);
        }
        else{
            //Retrieve the series_identifier issued to the user
            $series_identifier = Auth::user()
                                    ->series_identifier(Hash::make($token))
                                    ->first()
                                    ->series_identifier;

            //Check if series_identifier matches            
            if($series_identifier != $request->cookie('series_identifier')){
                //if series_identifier does not match allow the request to the email_verification
                return $next($request);
            }
        }

       return redirect('/dashboard'); //replace this with your route for home page
    }
}

在kernel.php中添加中间件的入口

protected $routeMiddleware = [
        'email_verification' => \App\Http\Middleware\Email_verification::class,
        //your middlewares
];

用户模型:将以下方法添加到您的用户模型中

// method to retrieve series_identifier related to token
public function series_identifier($token){
    return $this->hasMany(Browser_management::class)->where('token',$token);
}

//method to retriev the tokens related to user
public function tokens (){
    return $this->hasMany(Browser_management::class);
}

Browser_management 模型:创建一个模型来表示 browser_managements 表

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;


class Browser_management extends Model
{
    protected $primaryKey = 'token';
    protected $fillable = array('token','series_identifier');

    public function User(){
        return $this->hasOne('App\Models\User');
    }    
}

电子邮件验证方法:将以下方法添加到您的 AuthController 以处理电子邮件验证

public function getVerification(Request $request){
    //Create a random string to represent the token to be sent to user via email. 
    //You can use any string as we are going to hash it in our DB
    $token = str_random(16);

    //Generate random string to represent series_identifier
    $series_identifier = str_random(64);

    //Issue cookie to user with the generated series_identifier
    Cookie::queue('series_identifier', $series_identifier,43200,null,null,true,true);

    //Store the hashed token and series_identifier ini DB
    Auth::user()->tokens()->create(['token'=>Hash::make($token)]);

    //Your code to send an email for authentication

    //return the view with form asking for token
    return view('auth.email_verification');
}

public function postVerification(Request $request){
    //Retrieve the series_identifier issued to the user in above method
    $series_identifier = $request->cookie('series_identifier');

    //Retrieve the token associated with the series_identifier
    $token = Auth::user()
                ->tokens()
                ->where('series_identifier',$series_identifier)
                ->first()
                ->value('token');

    //Check if the user's token's hash matches our token entry
    if(Hash::check($request->token,$token)){
        // If token matched, issue the cookie with token id in it. Which we can use in future to authenticate the user
        Cookie::queue('token', $token,43200,null,null,true,true);
        return redirect('dashboard');
    }

    //If token did not match, redirect user bak to the form with error
    return redirect()->back()
                ->with('msg','Tokens did not match');
}

路由:添加这些路由以处理电子邮件验证请求。我们还将在其中添加 email_verification 中间件。

Route::get('/auth/email_verification',`AuthController@getVerification')->middleware('email_verification');
Route::post('/auth/email_verification',`AuthController@postVerification')->middleware('email_verification');<br/>

更新 2:

关于gmail..的流程..
我按照以下步骤操作:
1)登录gmail,然后进行两步验证。
2)退出
3)清除缓存link
4)再次登录

当我再次登录时,在清除缓存后,它并没有要求我进行两步验证。

不过,如果您清除 cookie,它会要求进行两步验证。 原因:
所有识别用户的用户数据(此处为 token)都存储在 cookie 中。如果您清除 cookie,服务器将无法识别用户。

更新 3:

Gmail 要求进行两步验证:
首先,Gmail 或任何其他网站不会收到有关清除缓存的通知
如给定here:

缓存只不过是硬盘上的一个地方 浏览器保留下载过的内容以备不时之需 再次。

现在,cookies 是服务器发出的用于存储用户相关信息的小文本文件。如给定here

cookie 的主要目的是识别用户并可能做好准备 自定义网页或为您保存网站登录信息。

因此,基本上当您清除浏览器中的 cookie 时,网络服务器将不会获取任何用户数据。因此,该用户将被视为访客并受到相应的对待。

【讨论】:

  • 我用两张数据卡上网。两者都有自己的 IP 地址。我也应该注意 IP 地址吗?
  • 我认为不需要,因为 IP 地址往往会发生变化。这就是我没有在表中包含 IP 地址的原因。
  • 正如您在 gmail 的示例中看到的那样,您的互联网连接来源在此过程中并不重要。
  • 我会使用token是user表中的remember token吗?
  • 你可以。但是,由于这与用户身份验证的复杂性不同,我建议一起使用不同的值。
【解决方案2】:

OP,如果我理解清楚,您只是想了解如何实现 laravel 会话表,以便您可以在同一浏览器中从同一用户多次登录:

Schema::create('sessions', function ($table) {
    $table->string('id')->unique();
    $table->integer('user_id')->nullable();
    $table->string('ip_address', 45)->nullable();
    $table->text('user_agent')->nullable();
    $table->text('payload');
    $table->integer('last_activity');
});

虽然这个问题是answered before here,但我会补充一点,您可以在实际登录方法中轻松实现此功能,而无需修改核心文件。

要做到这一点,你可以遵循以下逻辑

登录前,手动检查请求用户代理头是否与会话中的会话用户代理相同,即:

public function authenticate()
{
    $data = Input::all();
    $user = User::where('email', '=', $data['email'])->first();
    if($user != null)
    {
         //check if user agent different from session
         if($request->header('User-Agent') != session('user_agent'))
         {
             //do some extra login/password validation check
         }
         if(Auth::attempt($data))
         {
             //here you now may manually update the session ID on db
         }
    }
}

你将不得不做比这更多的工作,但我希望你明白这个概念。

【讨论】:

  • 这是完全错误的答案。 为什么?如果您第一次或在清除缓存或 cookie 后登录您的 Gmail 帐户,它会向您发送安全代码。如果您再次清除缓存/cookie,那么它将再次发送代码。因此,如果您清除客户端缓存,Gmail 会使用此概念。那么,建表有什么用呢?
  • 您的会话信息可以保存在表中、内存中或文件中,如 laravel 会话配置所示,我们在这里有一个表的唯一原因是为了提供更大的灵活性。此外,我根据上面明确陈述的假设进行了回答——即当我们不再识别用户的浏览器时,你如何提示用户。
【解决方案3】:

创建一个额外的表(除了会话一个)

类似

用户 ID |用户代理 |知识产权

当他们登录时检查他们在$_SERVER 数组中的当前值。如果它在那里一切都很好,如果没有中断登录,并向他们发送一个链接以确认新数据。您可能希望在原始登录上执行某种 ajax 以检查他们何时登录,然后在发生这种情况后重定向到他们要去的地方。

有道理。

正如我在 cmets 中所说的可维护性,我会自己处理而不使用任何第三方 API,数据很容易验证。那部分是比较琐碎的,继续登录过程没那么多。

【讨论】:

  • 我刚刚意识到即使我们清除浏览器缓存,Gmail 也会要求提供代码。那样的话,Session表或者新建一个带有Session Table的Relational表就没意义了?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-05-21
  • 2020-09-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-19
相关资源
最近更新 更多