如何在不要求用户登录 Laravel 的情况下验证电子邮件

How to Verify Email Without Asking the User to Login to Laravel

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

验证控制器

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 即使用户未登录也能验证电子邮件?

首先,像您一样删除行 $this->middleware('auth');

接下来,将 verify 方法从 VerifiesEmails 特征复制到您的 VerificationController 并稍微改变一下。该方法应如下所示:

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% 的情况下,这根本不是一个风险。

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

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);
}
// 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);
}

这里有一个更适合未来的解决方案:

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);
    }
}

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

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

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

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

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

因此 $this->middleware('auth'); 将在“VerificationController”中更改为 $this->middleware('auth')->except('verify');

同时将“VerifiesEmails”中的“verify”函数复制到“VerificationController”中

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

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

auth()->login($user);

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

允许未登录(即未授权)用户进行电子邮件验证的解决方案:

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

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

我在 VerificationController 中的 verify() 方法如下所示:

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) 登录后,会尝试将用户重定向到邮箱验证link,导致错误; 2) 如果用户成功验证了他们的电子邮件地址,我想使用附加到重定向的经过验证的真实闪存数据在登录页面上显示警报。

我的登录页面警报示例:

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

建议

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

这是我对这种情况的看法。验证需要用户登录才能完成验证,因此我们可以覆盖验证功能并使用我们在 link 中收到的 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'。如果它设置为 'strict',那么如果您从另一个站点重定向,它将不会保留会话。例如,如果您从 Gmail 单击验证 link,那么您的会话 cookie 将不会保留,因此它不会将您重定向到仪表板,但它会在数据库中设置 'email_verified_at' 字段以标记验证成功.用户不会知道发生了什么,因为它会将用户重定向到登录页面。当您将其设置为 'strict' 时,如果您直接在浏览器地址栏中复制验证 link 将起作用,但如果用户从 Gmail 网络客户端单击 link 则不起作用,因为它使用重定向来跟踪 link.

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

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());
}

我更改了 EmailVerificationRequest,但我现在这是错误的,无论如何它都有效。 警告 供应商的这一变化

protected $user;
public function authorize()
{
    $this->user = \App\Models\User::find($this->route('id'));
    if ($this->user != null){
        if (! hash_equals((string) $this->route('id'),
            (string) $this->user->getKey())) {
            return false;
        }

        if (! hash_equals((string) $this->route('hash'),
            sha1($this->user->getEmailForVerification()))) {
            return false;
        }

        return true;
    }
    return false;
}