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
相关产品推荐
相关产品推荐

