You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Laravel企业LMS登录验证后无法重定向至目标课程页面问题

解决Laravel LMS登录后无法跳转至目标课程页面的问题

需求描述

为企业搭建Laravel学习管理系统(LMS),员工通过工作邮箱登录验证后访问学习资料,流程如下:

  • HR发送课程链接(如https://lms.test/course/abcde)给员工;
  • 员工点击链接后跳转至登录页面,输入工作邮箱;
  • 系统发送OTP到员工邮箱,员工跳转至OTP验证页面完成验证;
  • 验证成功后直接跳转至目标课程页面。

现有实现代码

web.php路由配置

<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\CourseController;
use App\Providers\CustomUserProvider;

Route::get('/login', 'App\Http\Controllers\LoginController@showLoginForm')->name('login');
Route::post('/login', 'App\Http\Controllers\LoginController@login')->name('login.submit');
Route::get('/verify', 'App\Http\Controllers\LoginController@verifyOtpForm')->name('otp.verify');
Route::post('/verify', 'App\Http\Controllers\LoginController@verifyOtp')->name('otp.verify.submit');

Route::middleware(['verified'])->group(function () {
    Route::get('/courses/{path}', [CourseController::class, 'show'])->name('courses.show');
});

Route::get('/courses', function () {
    return view('courses');
})->name('courses');

LoginController代码

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Auth;
use App\Providers\CustomUserProvider;
use Illuminate\Auth\SessionGuard;

class LoginController extends Controller
{
    public function showLoginForm(Request $request)
    {
        session(['previous_url' => $request->path()]);

        return view('auth.login');
    }

    public function login(Request $request)
{
    $request->validate([
        'email' => 'required|email|ends_with:fonterra.com'
    ]);

    $user = app(CustomUserProvider::class)->retrieveByWorkEmail($request->email);

    if ($user) {
        // Generate and send OTP to the user's email
        $otp = mt_rand(100000, 999999);
        app(CustomUserProvider::class)->sendOtpByEmail($user, $otp);

        $request->session()->put('user', [
            'email' => $request->email,
            'otp' => $otp,
            'previous_url' => session('previous_url'), // Store the previous URL in session
        ]);

        return redirect()->route('otp.verify');
    }

    return redirect()->back()->withErrors(['email' => 'Invalid work email']);
}


    public function verifyOtpForm(Request $request)
    {
        return view('auth.verify-otp');
    }

    public function verifyOTP(Request $request)
{
    $otp = $request->input('otp');
    $user = session('user');

    if ($user && $user['otp'] == $otp) {
        $user['otp_verified'] = true;

        // Add the allowed paths based on verification
        $allowedPaths = ['frhgfdhtrjejt', 'tyuutiuyiuyi']; // Add the allowed paths based on your logic
        $user['allowed_paths'] = $allowedPaths;

        session(['user' => $user]);

        $previousUrl = session('previous_url');

        //dd($previousUrl );

        if ($previousUrl) {
            session()->forget('previous_url'); // Remove the previous URL from session

            if (in_array($previousUrl, $allowedPaths)) {
                return redirect($previousUrl);
            }
        }

        return redirect()->route('courses');
    } else {
        return redirect()->back()->withInput()->withErrors(['otp' => 'Invalid OTP']);
    }
}

}

VerifyOTP中间件代码

<?php

namespace App\Http\Middleware;

use Closure;

class VerifyOTP
{
    public function handle($request, Closure $next)
    {
        $user = session('user');
        $path = $request->path();
        $allowedPaths = [
            'abcde',
            'zxcvbn',
        ];

        if (
            (!$user || !isset($user['otp_verified']) || !$user['otp_verified']) ||
            (in_array($path, $allowedPaths) && !in_array($path, $user['allowed_paths']))
        ) {
            return redirect()->route('login')->withErrors(['message' => 'You need to log in first.']);
        }

        return $next($request);
    }
}

kernel.php配置

'verified' => \App\Http\Middleware\VerifyOTP::class,

CustomUser模型代码

<?php

namespace App;

use Illuminate\Contracts\Auth\Authenticatable;

class CustomUser implements Authenticatable
{
    public $email;

    protected $allowed_paths;

    public function getAllowedPaths()
    {
        return $this->allowed_paths;
    }

    public function __construct($email)
    {
        $this->email = $email;
    }

    public function getAuthIdentifierName()
    {
        return 'email';
    }

    public function getAuthIdentifier()
    {
        return $this->email;
    }

    public function getAuthPassword()
    {
        // This method is not needed since we are not using passwords
    }

    public function getRememberToken()
    {
        // This method is not needed since we are not using remember tokens
    }

    public function setRememberToken($value)
    {
        // This method is not needed since we are not using remember tokens
    }

    public function getRememberTokenName()
    {
        // This method is not needed since we are not using remember tokens
    }
}

CustomUserProvider代码

<?php

namespace App\Providers;

use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Support\Facades\Mail;
use App\CustomUser;

class CustomUserProvider implements \Illuminate\Contracts\Auth\UserProvider
{
    public function retrieveByCredentials(array $credentials)
    {
        //
    }

    public function validateCredentials(Authenticatable $user, array $credentials)
    {
        //
    }

    public function retrieveByWorkEmail($email)
    {
        return new CustomUser($email);
    }

    public function sendOtpByEmail($user, $otp)
    {
        Mail::raw("Your OTP is: $otp", function ($message) use ($user) {
            $message->to($user->email)
                ->subject('OTP Login');
        });
    }

    public function retrieveByOtp($otp)
    {
        $user = session('user');

        if ($user && $user['otp'] == $otp) {
            return new CustomUser($user['email']);
        }

        return null;
    }

    public function retrieveById($identifier)
    {
        //
    }

    public function retrieveByToken($identifier, $token)
    {
        //
    }

    public function updateRememberToken(Authenticatable $user, $token)
    {
        //
    }
}

CourseController代码(补充)

<?php

namespace App\Http\Controllers;

class CourseController extends Controller
{
    public function index()
    {
        return view('courses.index');
    }

    public function show($path)
{
    $user = session('user');

    if ($user && in_array($path, $user['allowed_paths'])) {
        return view('courses.show', compact('path'));
    } else {
        return redirect()->route('login')->with('message', 'You need to log in first');
    }
}


}

问题描述

直接访问课程链接(如https://lms.test/course/abcde)时,系统会跳转至登录流程,但完成OTP验证后,并未重定向至目标课程页面,而是跳转到了通用课程列表页(https://lms.test/courses/)。

解决方案

1. 修复目标URL的保存逻辑

当前LoginController的showLoginForm方法中,保存的previous_url是login页面的路径,而非用户最初访问的课程路径。修改该方法,保存用户跳转前的完整课程URL:

public function showLoginForm(Request $request)
{
    // 仅在未保存过目标URL时才存储,避免重复覆盖
    if (!$request->session()->has('previous_url')) {
        $previousUrl = url()->previous();
        // 排除登录和OTP验证页面,只保留课程相关URL
        if (!str_contains($previousUrl, route('login')) && !str_contains($previousUrl, route('otp.verify'))) {
            session(['previous_url' => $previousUrl]);
        }
    }

    return view('auth.login');
}

2. 优化OTP验证后的跳转判断

当前验证逻辑中,直接用完整URL和课程路径列表匹配,导致判断失败。修改verifyOTP方法,提取URL中的课程路径部分再做匹配,同时统一允许的课程路径列表:

public function verifyOTP(Request $request)
{
    $otp = $request->input('otp');
    $user = session('user');

    if ($user && $user['otp'] == $otp) {
        $user['otp_verified'] = true;

        // 和中间件中的允许路径保持一致
        $allowedPaths = ['abcde', 'zxcvbn', 'frhgfdhtrjejt', 'tyuutiuyiuyi'];
        $user['allowed_paths'] = $allowedPaths;

        session(['user' => $user]);

        $previousUrl = session('previous_url');

        if ($previousUrl) {
            session()->forget('previous_url');
            
            // 从完整URL中提取课程路径(如从/courses/abcde中提取abcde)
            $pathSegments = trim(parse_url($previousUrl, PHP_URL_PATH), '/');
            $coursePath = last(explode('/', $pathSegments));
            
            // 验证课程路径是否在允许列表中
            if (in_array($coursePath, $allowedPaths)) {
                return redirect($previousUrl);
            }
        }

        return redirect()->route('courses');
    } else {
        return redirect()->back()->withInput()->withErrors(['otp' => 'Invalid OTP']);
    }
}

3. 可选:在中间件中直接保存目标URL

为了更可靠地获取用户最初访问的课程页面,可在VerifyOTP中间件中拦截请求时直接保存当前URL:

class VerifyOTP
{
    public function handle($request, Closure $next)
    {
        $user = session('user');
        $path = $request->path();
        $allowedPaths = [
            'abcde',
            'zxcvbn',
        ];

        if (
            (!$user || !isset($user['otp_verified']) || !$user['otp_verified']) ||
            (in_array($path, $allowedPaths) && !in_array($path, $user['allowed_paths']))
        ) {
            // 保存当前请求的完整URL作为目标路径
            if (!$request->session()->has('previous_url')) {
                session(['previous_url' => $request->fullUrl()]);
            }
            return redirect()->route('login')->withErrors(['message' => 'You need to log in first.']);
        }

        return $next($request);
    }
}

内容的提问来源于stack exchange,提问作者user3391363

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 21:17:00