이 글에서는 Laravel Auth를 수정하여 사용자를 인증하기 위해 솔트와 비밀번호를 사용하는 것에 대한 관련 정보를 주로 소개합니다. 이 글에서는 샘플 코드를 통해 자세히 소개합니다. 공부나 업무에 필요한 모든 사람에게 도움이 되는 학습 가치가 있습니다. 아래에서 함께 살펴보겠습니다.
머리말
이 글은 주로 Laravel Auth를 수정하여 사용자를 인증하기 위해 솔트와 비밀번호를 사용하는 관련 내용을 소개합니다. 아래에서는 많은 언급을 하지 않겠습니다. 소개:
라라발의 자체 사용자 인증 시스템 Auth는 매우 강력하고 사용하기 쉽습니다. 그러나 Laravel의 사용자 인증 시스템에서는 사용자 등록, 로그인, 및 비밀번호 검색 모듈을 사용했으며 이전 프로젝트에서는 사용자 테이블에 사용자 비밀번호를 기록하기 위해 솔트 + 비밀번호 암호화 문자열을 저장하는 방법을 사용했는데, 이는 이전 프로젝트를 재구성하기 위해 Laravel 프레임워크를 사용하는 데 큰 저항을 가져왔습니다. 인터넷에서 정보를 검색하고, 커뮤니티 포럼을 읽고, 소스 코드를 읽어보고 Laravel Auth를 수정한 내용을 여기에 공유하여 다른 사람들에게 도움이 되기를 바랍니다. 시작하기 전에 Laravel 프레임워크를 새 프로젝트에서 사용하는 경우 Auth를 수정할 필요가 없다는 점을 설명해야 합니다. 기본 bcrypt 암호화 알고리즘은 salt + 비밀번호보다 더 안전하고 효율적인 암호화 알고리즘입니다.
사용자 등록 수정
먼저 artisan 명령을 사용하여 laravel에서 확인을 활성화합니다
php artisan make:auth
명령을 실행한 후 경로 파일(위치: app/Http/routes.dll)에 더 많은 내용이 있습니다. php) 정적 메소드 호출
Route::auth();
이 경로는 Laravel의 Facade(IlluminateSupportFacadesRoute에 위치)이며, 호출된 인증 메소드는 IlluminateRoutingRouter 클래스에 정의되어 있습니다. 아래에서 볼 수 있듯이 인증 관련 라우팅 규칙은 다음과 같습니다. 인증 메소드에 정의됨
/** * Register the typical authentication routes for an application. * * @return void */ public function auth() { // Authentication Routes... $this->get('login', 'Auth\AuthController@showLoginForm'); $this->post('login', 'Auth\AuthController@login'); $this->get('logout', 'Auth\AuthController@logout'); // Registration Routes... $this->get('register', 'Auth\AuthController@showRegistrationForm'); $this->post('register', 'Auth\AuthController@register'); // Password Reset Routes... $this->get('password/reset/{token?}', 'Auth\PasswordController@showResetForm'); $this->post('password/email', 'Auth\PasswordController@sendResetLinkEmail'); $this->post('password/reset', 'Auth\PasswordController@reset'); }
등록 중에 요청된 컨트롤러 메소드가 AuthController의 등록 메소드라는 것을 라우팅 규칙에서 볼 수 있습니다. 이 메소드는 IlluminateFoundationAuthRegistersUsers 특성에 정의되어 있습니다.
/** * Handle a registration request for the application. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ public function register(Request $request) { $validator = $this->validator($request->all()); if ($validator->fails()) { $this->throwValidationException( $request, $validator ); } Auth::guard($this->getGuard())->login($this->create($request->all())); return redirect($this->redirectPath()); }
등록 방법에서는 요청의 사용자 입력 데이터가 먼저 확인됩니다. AuthController
protected function validator(array $data) { return Validator::make($data, [ 'name' => 'required|max:255', 'email' => 'required|email|max:255|unique:user', 'password' => 'required|size:40|confirmed', ]); }
의 유효성 검사기 메서드에서 각 입력 필드에 대한 자체 확인 규칙만 정의하면 됩니다. 확인이 통과된 후 Laravel을 살펴보고 AuthController의 생성 메소드를 사용하여 새 사용자를 생성한 다음 새 사용자의 데이터를 사용하여 Auth::guard($this->getGuard()에 로그인합니다. )->login($this->create( $request->all()));
Auth::guard($this->getGuard())->login($this->create($request->all()));
所以我们要自定义用户注册时生成用户密码的加密方式只需要修改AuthController的create方法即可。
比如:
/** * Create a new user instance after a valid registration. * * @param array $data * @return User */ protected function create(array $data) { $salt = Str::random(6); return User::create([ 'nickname' => $data['name'], 'email' => $data['email'], 'password' => sha1($salt . $data['password']), 'register_time' => time(), 'register_ip' => ip2long(request()->ip()), 'salt' => $salt ]); }
修改用户登录
修改登录前我们需要先通过路由规则看一下登录请求的具体控制器和方法,在上文提到的auth方法定义里可以看到
$this->get('login', 'Auth\AuthController@showLoginForm'); $this->post('login', 'Auth\AuthController@login'); $this->get('logout', 'Auth\AuthController@logout');
验证登录的操作是在AppHttpControllersAuthAuthController类的login方法里。打开AuthController发现Auth相关的方法都是通过性状(traits)引入到类内的,在类内use 要引入的traits,在编译时PHP就会把traits里的代码copy到类中,这是PHP5.5引入的特性具体适用场景和用途这里不细讲。 所以AuthController@login
方法实际是定义在
IlluminateFoundationAuthAuthenticatesUsers这个traits里的
/** * Handle a login request to the application. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ public function login(Request $request) { $this->validateLogin($request); $throttles = $this->isUsingThrottlesLoginsTrait(); if ($throttles && $lockedOut = $this->hasTooManyLoginAttempts($request)) { $this->fireLockoutEvent($request); return $this->sendLockoutResponse($request); } $credentials = $this->getCredentials($request); if (Auth::guard($this->getGuard())->attempt($credentials, $request->has('remember'))) { return $this->handleUserWasAuthenticated($request, $throttles); } if ($throttles && ! $lockedOut) { $this->incrementLoginAttempts($request); } return $this->sendFailedLoginResponse($request); }
登录验证的主要操作是在Auth::guard($this->getGuard())->attempt($credentials, $request->has('remember'));
这个方法调用中来进行的,Auth::guard($this->getGuard())
获取到的是IlluminateAuthSessionGuard (具体如何获取的看Auth这个Facade IlluminateAuthAuthManager里的源码)
看一下SessionGuard里attempt 方法是如何实现的:
public function attempt(array $credentials = [], $remember = false, $login = true) { $this->fireAttemptEvent($credentials, $remember, $login); $this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials); if ($this->hasValidCredentials($user, $credentials)) { if ($login) { $this->login($user, $remember); } return true; } if ($login) { $this->fireFailedEvent($user, $credentials); } return false; } /** * Determine if the user matches the credentials. * * @param mixed $user * @param array $credentials * @return bool */ protected function hasValidCredentials($user, $credentials) { return ! is_null($user) && $this->provider->validateCredentials($user, $credentials); }
retrieveByCredentials是用传递进来的字段从数据库中取出用户数据的,validateCredentials是用来验证密码是否正确的实际过程。
这里需要注意的是$this->provider
'providers' => [ 'users' => [ 'driver' => 'eloquent', 'model' => App\User::class, //这个是driver用的Model ], ],
AuthController@login
메소드는 실제로 🎜IlluminateFoundationAuthAuthenticatesUsers.🎜🎜🎜🎜/** * Retrieve a user by the given credentials. * * @param array $credentials * @return \Illuminate\Contracts\Auth\Authenticatable|null */ public function retrieveByCredentials(array $credentials) { if (empty($credentials)) { return; } $query = $this->createModel()->newQuery(); foreach ($credentials as $key => $value) { if (! Str::contains($key, 'password')) { $query->where($key, $value); } } return $query->first(); } /** * Validate a user against the given credentials. * * @param \Illuminate\Contracts\Auth\Authenticatable $user * @param array $credentials * @return bool */ public function validateCredentials(UserContract $user, array $credentials) { $plain = $credentials['password']; return $this->hasher->check($plain, $user->getAuthPassword()); }
Auth::guard($this->getGuard)에 있습니다. ())->attempt($credentials, $request->has('remember'));
이 메소드는 Auth::guard($this->getGuard ())라고 합니다.
얻는 것은 IlluminateAuthSessionGuard입니다(얻는 방법에 대한 자세한 내용은 Auth Facade IlluminateAuthAuthManager의 소스 코드를 참조하세요)🎜🎜SessionGuard의 시도 메소드가 어떻게 구현되는지 살펴보세요:🎜🎜🎜🎜/** * The table associated to this model */ protected $table = 'user';//用户表名不是laravel约定的这里要指定一下
$this->provider
입니다. 이 공급자는 IlluminateContractsAuthUserProvider 클래스를 구현하는 공급자입니다. IlluminateAuth 디렉토리 아래에 두 개의 UserProvider 구현, 즉 DatabaseUserProvider가 있다는 것을 알 수 있습니다. EloquentUserProvider. 비밀번호를 확인할 때 인증 구성 파일을 살펴보세요🎜🎜🎜🎜'providers' => [ 'users' => [ 'driver' => 'eloquent', 'model' => App\User::class, //这个是driver用的Model ], ],
这里配置的是driver => eloquent
, 那么就是通过EloquentUserProvider的retrieveByCredentials来验证的, 这个EloquentUserProvider 是在SessionGuard实例化时被注入进来的, (具体是怎么通过读取auth配置文件, 实例化相应的provider注入到SessionGuard里的请查阅\Illuminate\Auth\AuthManager 里createSessionDriver方法的源代码)
接下来我们继续查看EloquentUserProvider中retrieveByCredentials和validateCredentials方法的实现:
/** * Retrieve a user by the given credentials. * * @param array $credentials * @return \Illuminate\Contracts\Auth\Authenticatable|null */ public function retrieveByCredentials(array $credentials) { if (empty($credentials)) { return; } $query = $this->createModel()->newQuery(); foreach ($credentials as $key => $value) { if (! Str::contains($key, 'password')) { $query->where($key, $value); } } return $query->first(); } /** * Validate a user against the given credentials. * * @param \Illuminate\Contracts\Auth\Authenticatable $user * @param array $credentials * @return bool */ public function validateCredentials(UserContract $user, array $credentials) { $plain = $credentials['password']; return $this->hasher->check($plain, $user->getAuthPassword()); }
上面两个方法retrieveByCredentials用除了密码以外的字段从数据库用户表里取出用户记录,比如用email查询出用户记录,然后validateCredentials方法就是通过$this->haser->check
来将输入的密码和哈希的密码进行比较来验证密码是否正确。
好了, 看到这里就很明显了, 我们需要改成自己的密码验证就是自己实现一下validateCredentials就可以了, 修改$this->hasher->check为我们自己的密码验证规则就可以了。
首先我们修改$user->getAuthPassword()
把数据库中用户表的salt和password传递到validateCredentials中
修改App\User.php 添加如下代码
/** * The table associated to this model */ protected $table = 'user';//用户表名不是laravel约定的这里要指定一下
/** * 禁用Laravel自动管理timestamp列 */ public $timestamps = false; /** * 覆盖Laravel中默认的getAuthPassword方法, 返回用户的password和salt字段 * @return type */ public function getAuthPassword() { return ['password' => $this->attributes['password'], 'salt' => $this->attributes['salt']]; }
然后我们在建立一个自己的UserProvider接口的实现,放到自定义的目录中:
新建app/Foundation/Auth/AdminEloquentUserProvider.php
namespace App\Foundation\Auth; use Illuminate\Auth\EloquentUserProvider; use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Support\Str; class AdminEloquentUserProvider extends EloquentUserProvider { /** * Validate a user against the given credentials. * * @param \Illuminate\Contracts\Auth\Authenticatable $user * @param array $credentials */ public function validateCredentials(Authenticatable $user, array $credentials) { $plain = $credentials['password']; $authPassword = $user->getAuthPassword(); return sha1($authPassword['salt'] . $plain) == $authPassword['password']; } }
最后我们修改auth配置文件让Laravel在做Auth验证时使用我们刚定义的Provider,
修改config/auth.php:
'providers' => [ 'users' => [ 'driver' => 'admin-eloquent', 'model' => App\User::class, ] ]
修改app/Provider/AuthServiceProvider.php
public function boot(GateContract $gate) { $this->registerPolicies($gate); \Auth::provider('admin-eloquent', function ($app, $config) { return New \App\Foundation\Auth\AdminEloquentUserProvider($app['hash'], $config['model']); }); }
Auth::provider方法是用来注册Provider构造器的,这个构造器是一个Closure,provider方法的具体代码实现在AuthManager文件里
public function provider($name, Closure $callback) { $this->customProviderCreators[$name] = $callback; return $this; }
闭包返回了AdminEloquentUserProvider对象供Laravel Auth使用,好了做完这些修改后Laravel的Auth在做用户登录验证的时候采用的就是自定义的salt + password的方式了。
修改重置密码
Laravel 的重置密码的工作流程是:
向需要重置密码的用户的邮箱发送一封带有重置密码链接的邮件,链接中会包含用户的email地址和token。
用户点击邮件中的链接在重置密码页面输入新的密码,Laravel通过验证email和token确认用户就是发起重置密码请求的用户后将新密码更新到用户在数据表的记录里。
第一步需要配置Laravel的email功能,此外还需要在数据库中创建一个新表password_resets来存储用户的email和对应的token
CREATE TABLE `password_resets` ( `email` varchar(255) COLLATE utf8_unicode_ci NOT NULL, `token` varchar(255) COLLATE utf8_unicode_ci NOT NULL, `created_at` timestamp NOT NULL, KEY `password_resets_email_index` (`email`), KEY `password_resets_token_index` (`token`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
通过重置密码表单的提交地址可以看到,表单把新的密码用post提交给了/password/reset,我们先来看一下auth相关的路由,确定/password/reset对应的控制器方法。
$this->post('password/reset', 'Auth\PasswordController@reset');
可以看到对应的控制器方法是\App\Http\Controllers\Auth\PasswordController类的reset方法,这个方法实际是定义在\Illuminate\Foundation\Auth\ResetsPasswords 这个traits里,PasswordController引入了这个traits
/** * Reset the given user's password. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ public function reset(Request $request) { $this->validate( $request, $this->getResetValidationRules(), $this->getResetValidationMessages(), $this->getResetValidationCustomAttributes() ); $credentials = $this->getResetCredentials($request); $broker = $this->getBroker(); $response = Password::broker($broker)->reset($credentials, function ($user, $password) { $this->resetPassword($user, $password); }); switch ($response) { case Password::PASSWORD_RESET: return $this->getResetSuccessResponse($response); default: return $this->getResetFailureResponse($request, $response); } }
方法开头先通过validator对输入进行验证,接下来在程序里传递把新密码和一个闭包对象传递给Password::broker($broker)->reset();方法,这个方法定义在\Illuminate\Auth\Passwords\PasswordBroker类里.
/** * Reset the password for the given token. * * @param array $credentials * @param \Closure $callback * @return mixed */ public function reset(array $credentials, Closure $callback) { // If the responses from the validate method is not a user instance, we will // assume that it is a redirect and simply return it from this method and // the user is properly redirected having an error message on the post. $user = $this->validateReset($credentials); if (! $user instanceof CanResetPasswordContract) { return $user; } $pass = $credentials['password']; // Once we have called this callback, we will remove this token row from the // table and return the response from this callback so the user gets sent // to the destination given by the developers from the callback return. call_user_func($callback, $user, $pass); $this->tokens->delete($credentials['token']); return static::PASSWORD_RESET; }
在PasswordBroker的reset方法里,程序会先对用户提交的数据做再一次的认证,然后把密码和用户实例传递给传递进来的闭包,在闭包调用里完成了将新密码更新到用户表的操作, 在闭包里程序调用了的PasswrodController类的resetPassword方法
function ($user, $password) { $this->resetPassword($user, $password); });
PasswrodController类resetPassword方法的定义
protected function resetPassword($user, $password) { $user->forceFill([ 'password' => bcrypt($password), 'remember_token' => Str::random(60), ])->save(); Auth::guard($this->getGuard())->login($user); }
在这个方法里Laravel 用的是bcrypt 加密了密码, 那么要改成我们需要的salt + password的方式,我们在PasswordController类里重写resetPassword方法覆盖掉traits里的该方法就可以了。
/** * 覆盖ResetsPasswords traits里的resetPassword方法,改为用sha1(salt + password)的加密方式 * Reset the given user's password. * * @param \Illuminate\Contracts\Auth\CanResetPassword $user * @param string $password * @return void */ protected function resetPassword($user, $password) { $salt = Str::random(6); $user->forceFill([ 'password' => sha1($salt . $password), 'salt' => $salt, 'remember_token' => Str::random(60), ])->save(); \Auth::guard($this->getGuard())->login($user); }
结语
到这里对Laravel Auth的自定义就完成了,注册、登录和重置密码都改成了sha1(salt + password)的密码加密方式, 所有自定义代码都是通过定义Laravel相关类的子类和重写方法来完成没有修改Laravel的源码,这样既保持了良好的可扩展性也保证了项目能够自由迁移。
注:使用的Laravel版本为5.2
위 내용은 Laravel Auth를 솔트와 비밀번호를 사용하도록 수정하여 사용자 인증에 대한 자세한 설명의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!