22

我正在开发一个 Laravel 应用程序。我的应用程序正在使用 Laravel 内置的身份验证功能。在用户注册时的 Laravel auth 中,会发送一封验证邮件。当用户验证电子邮件时,单击电子邮件内的链接,如果用户尚未登录,则用户必须再次登录以确认电子邮件。

验证控制器

class VerificationController extends Controller
{
    use VerifiesEmails, RedirectsUsersBasedOnRoles;

    /**
     * Create a new controller instance.
     * @return void
     */
    public function __construct()
    {
        $this->middleware('auth');
        $this->middleware('signed')->only('verify');
        $this->middleware('throttle:6,1')->only('verify', 'resend');
    }

    public function redirectPath()
    {
        return $this->getRedirectTo(Auth::guard()->user());
    }
}

我试着评论这条线。

$this->middleware('auth');

但它不起作用,而是抛出一个错误。即使用户未登录,如何使 Laravel 能够验证电子邮件?

4

8 回答 8

36

首先,删除 line $this->middleware('auth');,就像你做的那样。

接下来,将trait 中的verify方法复制到 your 中并稍作修改。该方法应如下所示:VerifiesEmailsVerificationController

public function verify(Request $request)
{
    $user = User::find($request->route('id'));

    if (!hash_equals((string) $request->route('hash'), sha1($user->getEmailForVerification()))) {
        throw new AuthorizationException;
    }

    if ($user->markEmailAsVerified())
        event(new Verified($user));

    return redirect($this->redirectPath())->with('verified', true);
}

这会覆盖VerifiesUsers特征中的方法并删除授权检查。

安全性(如果我错了,请纠正我!)

它仍然是安全的,因为请求已经过签名和验证。如果某人以某种方式获得了验证电子邮件的访问权限,他们可以验证另一个用户的电子邮件地址,但在 99% 的情况下,这根本不是风险。

于 2019-01-15T09:56:57.177 回答
3

这是该问题的更面向未来的解决方案:

class VerificationController extends Controller
{

    // …

    use VerifiesEmails {
        verify as originalVerify;
    }

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('auth'); // DON'T REMOVE THIS
        $this->middleware('signed')->only('verify');
        $this->middleware('throttle:6,1')->only('verify', 'resend');
    }

    /**
     * Mark the authenticated user's email address as verified.
     *
     * @param Request $request
     * @return Response
     *
     * @throws AuthorizationException
     */
    public function verify(Request $request)
    {
        $request->setUserResolver(function () use ($request) {
            return User::findOrFail($request->route('id'));
        });
        return $this->originalVerify($request);
    }
}

因此,当未经身份验证的用户单击电子邮件确认链接时,将发生以下情况:

  1. 用户将被重定向到登录视图1
  2. 用户输入凭据;登录成功2
  3. 用户将被重定向回电子邮件确认 URL
  4. 电子邮件将被标记为已确认

1此时电子邮件不会被标记为已确认。

2用户可能多次输入错误凭据。一旦他输入正确的凭据,他就会被重定向到预期的电子邮件确认 URL。

于 2020-05-28T07:58:35.683 回答
2
// For Laravel 6 and Above 
use Illuminate\Auth\Events\Verified; 
use Illuminate\Http\Request; 
use App\User;

// comment auth middleware
//$this->middleware('auth');

public function verify(Request $request)
{
    $user = User::find($request->route('id'));

    if (!hash_equals((string) $request->route('hash'), sha1($user->getEmailForVerification()))) {
        throw new AuthorizationException;
    }

    if ($user->markEmailAsVerified())
        event(new Verified($user));

    return redirect($this->redirectPath())->with('verified', true);
}
于 2020-03-11T12:24:12.990 回答
2

您不应该完全删除$this->middleware('auth'),因为这会影响重定向。如果您删除它,未经身份验证的用户将被重定向到“/email/verify”而不是“/login”

所以$this->middleware('auth');$this->middleware('auth')->except('verify');在“VerificationController”中改为

还将“验证”功能从“验证电子邮件”复制到“验证控制器”

在函数顶部添加这两行代码

$user = User::find($request->route('id'));

auth()->login($user);

因此您以编程方式登录用户,然后执行进一步的操作

于 2020-08-20T13:07:15.773 回答
2

这是我对这种情况的看法。验证需要用户登录才能完成验证,因此我们可以覆盖验证功能并使用我们在链接中收到的 ID 登录用户。这是安全的,因为如果 Laravel 无法验证来自 URL 的签名,则不会调用验证函数,因此即使有人修改 URL,他们也无法绕过它。

转到您的 VerificationController 并在文件末尾添加以下函数。

public function verify(Request $request)
{
    if (!auth()->check()) {
        auth()->loginUsingId($request->route('id'));
    }

    if ($request->route('id') != $request->user()->getKey()) {
        throw new AuthorizationException;
    }

    if ($request->user()->hasVerifiedEmail()) {
        return redirect($this->redirectPath());
    }

    if ($request->user()->markEmailAsVerified()) {
        event(new Verified($request->user()));
    }

    return redirect($this->redirectPath())->with('verified', true);
}

笔记

确保您将“config/session.php”中的same_site 值设置为“lax”。如果它设置为“严格”,那么如果您从另一个站点重定向,它将不会持续会话。例如,如果您单击 Gmail 中的验证链接,那么您的会话 cookie 将不会保留,因此它不会将您重定向到仪表板,但它会在数据库中设置“email_verified_at”字段,标记验证成功。用户不会知道发生了什么,因为它会将用户重定向到登录页面。当您将其设置为“严格”时,如果您直接在浏览器地址栏中复制验证链接,它将起作用,但如果用户从 Gmail 网络客户端单击该链接,则它不会起作用,因为它使用重定向来跟踪链接。

于 2021-04-14T06:19:47.987 回答
0

如果您想在不登录的情况下激活用户帐户,您可以分两步完成

1- 在 VerificationController 中删除或注释 Auth 中间件

下面的例子:

public function __construct()
{
    //$this->middleware('auth');
    $this->middleware('signed')->only('verify');
    $this->middleware('throttle:6,1')->only('verify', 'resend');
}

2-由于验证通过 {id} 的路由,您可以编辑验证函数以通过路由 id 请求找到用户,如下面的代码:

文件路径:*:\yourproject\vendor\laravel\framework\src\Illuminate\Foundation\Auth\VerifiesEmails.php

$user = User::findOrfail($request->route('id'));

完整示例

public function verify(Request $request)
{
    $user = User::findOrfail($request->route('id'));

    if (! hash_equals((string) $request->route('id'), (string) $user->getKey())) {
        throw new AuthorizationException;
    }

    if (! hash_equals((string) $request->route('hash'), sha1($user->getEmailForVerification()))) {
        throw new AuthorizationException;
    }

    if ($user->hasVerifiedEmail()) {
        return redirect($this->redirectPath())->with('verified', true);
    }

    if ($user->markEmailAsVerified()) {
        event(new Verified($request->user()));
    }

    return redirect($this->redirectPath())->with('registered', true);
}
于 2020-01-04T13:29:51.230 回答
0

允许未登录用户(即没有身份验证)的电子邮件验证的解决方案:

更改为:app/Http/Controllers/Auth/VerificationController.php:

  1. $this->middleware('auth');$this->middleware('auth')->except('verify');
  2. verify()从特征复制方法VerifiesEmails
  3. 编辑验证方法以在没有预期$request->user()数据的情况下工作。

我的verify()方法是VerificationController这样的:

public function verify(\Illuminate\Http\Request $request)
{
    $user = User::find($request->route('id'));

    if ($request->route('id') != $user->getKey()) {
        throw new AuthorizationException;
    }

    if ($user->markEmailAsVerified())
        event(new Verified($user));

    return redirect()->route('login')->with('verified', true);
}

签名中间件

Laravel 使用一个名为的中间件signed来检查应用程序生成的 URL 的完整性。Signed 检查 URL 自创建以来是否已更改。尝试更改 url 中的 id、到期时间或签名,这将导致错误 - 非常有效且有用的中间件来保护 verify() 方法

欲了解更多信息:https ://laravel.com/docs/8.x/urls#signed-urls

(选修的)

我将我的用户重定向到登录路由,而不是预期的路由,原因有两个。1)登录后,会尝试将用户重定向到邮箱验证链接,导致出错;2) 如果用户成功验证了他们的电子邮件地址,我想使用附加到重定向的经过验证的真实闪存数据,在登录页面上显示警报。

我的登录页面警报示例:

@if(session()->has('verified'))
    <div class="alert alert-success">Your email address has been successfully verified.</div>
@endif

建议

如果您对如何改进此代码有任何建议,请告诉我。我很乐意编辑这个答案。

于 2021-02-08T15:52:41.623 回答
0

要使用内部 laravel 逻辑(不覆盖逻辑),我们只需创建 $request->user() 并调用 trait 的 verify 方法。并在验证成功后手动登录用户。

use VerifiesEmails {
    verify as parentVerify;
}

public function verify(Request $request)
{
    $user = User::find($request->route('id'));
    if (!$user) return abort(404);

    $request->setUserResolver(function () use($user) {
        return $user;
    });

    return $this->parentVerify($request);
}


public function verified(Request $request)
{
    Auth::login($request->user());
}
于 2022-01-02T15:48:16.520 回答