通过修改Laravel Auth使用salt和password进行认证用户详解

前言

本文主要给大家介绍了通过修改Laravel Auth用salt和password进行认证用户的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍:

Laraval自带的用户认证系统Auth非常强大易用,不过在Laravel的用户认证系统中用户注册、登录、找回密码这些模块中用到密码加密和认证算法时使用的都是bcrypt,而很多之前做的项目用户表里都是采用存储salt + password加密字符串的方式来记录用户的密码的,这就给使用Laravel框架来重构之前的项目带来了很大的阻力,不过最近自己通过在网上找资料、看社区论坛、看源码等方式完成了对Laravel Auth的修改,在这里分享出来希望能对其他人有所帮助。 开篇之前需要再说明下如果是新项目应用Laravel框架,那么不需要对Auth进行任何修改,默认的bcrypt加密算法是比salt + password更安全更高效的加密算法。

修改用户注册

首先,在laravel 里启用验证是用的artisan命令

php artisan make:auth

执行完命令后在routes文件(位置:app/Http/routes.php)会多一条静态方法调用

Route::auth();

这个Route是Laravel的一个Facade (位于Illuminate\Support\Facades\Route), 调用的auth方法定义在Illuminate\Routing\Router类里, 如下可以看到auth方法里就是定义了一些Auth相关的路由规则

/**
 * 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的register方法, 该方法定义在\Illuminate\Foundation\Auth\RegistersUsers这个traits里,AuthController在类定义里引入了这个traits.

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

在register方法里首先会对request里的用户输入数据进行验证,你只需要在AuthController的validator方法里定义自己的每个输入字段的验证规则就可以

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的create方法来生成新用户,然后拿着新用户的数据去登录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');

验证登录的操作是在\App\Http\Controllers\Auth\AuthController类的login方法里。打开AuthController发现Auth相关的方法都是通过性状(traits)引入到类内的,在类内use 要引入的traits,在编译时PHP就会把traits里的代码copy到类中,这是PHP5.5引入的特性具体适用场景和用途这里不细讲。 所以AuthController@login方法实际是定义在
\Illuminate\Foundation\Auth\AuthenticatesUsers这个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()) 获取到的是\Illuminate\Auth\SessionGuard (具体如何获取的看Auth这个Facade \Illuminate\Auth\AuthManager里的源码)

看一下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这个provider是一个实现了\Illuminate\Contracts\Auth\UserProvider类的provider, 我们看到目录Illuminate\Auth下面有两个UserProvider的实现,分别为DatabaseUserProvider和EloquentUserProvider, 但是我们验证密码的时候是通过那个来验证的呢,看一下auth的配置文件

'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

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

(0)

相关推荐

  • Laravel中的Auth模块详解

    前言 本文主要给大家介绍的是关于Laravel中Auth模块的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. 本文是基于Laravel 5.4 版本的本地化模块代码进行分析书写: 模块组成 Auth模块从功能上分为用户认证和权限管理两个部分:从文件组成上,Illuminate\Auth\Passwords目录下是密码重置或忘记密码处理的小模块,Illuminate\Auth是负责用户认证和权限管理的模块,Illuminate\Foundation\Auth提供了登录.

  • laravel容器延迟加载以及auth扩展详解

    昨天按照手册教程,动手写一个Auth扩展,按照包独立性的原则,我不希望将Auth::extend()这种方法写在 start.php 中,毫无疑问,我选择了在服务提供器register()方法中注册扩展驱动.然而,事与愿违-- 发现问题 当我在 LoauthServiceProvider 中这样写的时候: 复制代码 代码如下: public function register() {     //     \Auth::extend('loauth',function($app){}); } 报

  • 通过修改Laravel Auth使用salt和password进行认证用户详解

    前言 本文主要给大家介绍了通过修改Laravel Auth用salt和password进行认证用户的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍: Laraval自带的用户认证系统Auth非常强大易用,不过在Laravel的用户认证系统中用户注册.登录.找回密码这些模块中用到密码加密和认证算法时使用的都是bcrypt,而很多之前做的项目用户表里都是采用存储salt + password加密字符串的方式来记录用户的密码的,这就给使用Laravel框架来重构之前的项目带来了

  • laravel使用Redis实现网站缓存读取的方法详解

    redis的简介 Redis 是完全开源免费的,遵守BSD协议,是一个高性能的key-value数据库. Redis 与其他 key - value 缓存产品有以下三个特点: Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用. Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储. Redis支持数据的备份,即master-slave模式的数据备份. Redis 优势 性能极高 – Red

  • Laravel自动生成UUID,从建表到使用详解

    gitHub地址: https://github.com/EmadAdly/laravel-uuid.git 1.安装依赖 composer require emadadly/laravel-uuid 2.然后在config/app.php的providers里添加ServiceProvider 'providers' => [ ... Emadadly\LaravelUuid\LaravelUuidServiceProvider::class, ], 3.然后根目录执行 php artisan

  • 安装mysql8.0.11及修改root密码、连接navicat for mysql的思路详解

    1.1. 下载: 官网下载zip包,我下载的是64位的: 下载地址:https://dev.mysql.com/downloads/mysql/ 下载zip的包: 下载后解压:(解压在哪个盘都可以的) 我放在了这里 E:\web\mysql-8.0.11-winx64 ,顺便缩短了文件名,所以为 E:\web\mysql-8.0.11. 1.3. 生成data文件: 以管理员身份运行cmd 程序--输入cmd 找到cmd.exe 右键以管理员身份运行 进入E:\web\mysql-8.0.11\

  • laravel Task Scheduling(任务调度)在windows下的使用详解

    前言 laravel的任务调度是很好用的,因为Laravel提供了平滑而又富有表现力地调度器,并且服务器上只需要一个Cron条目即可,这使我们从编写手动写crontab中解放出来,使得程序重新获得定时任务的控制权. 但是,我们发现laravel的文档中只提供了linux/unix下的解决方案,但是我们大多数时候本地开发环境都是在windows下搭建的,所以,我们需要解决这些问题. 问题 本篇博客主要是围绕着如下几个问题展开 1. windows下,怎么使用laravel任务调度? 2. wind

  • Laravel中Facade的加载过程与原理详解

    前言 本文主要给大家介绍了关于Laravel中Facade加载过程与原理的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. 简介 Facades(读音:/fəˈsäd/ )为应用程序的 服务容器 中可用的类提供了一个「静态」接口.你不必 use 一大串的命名空间,也不用实例化对象,就能访问对象的具体方法. use Config; class Test { public function index() { return Config::get('app.name');

  • Laravel构建即时应用的一种实现方法详解

    即时交互的应用 大家应该都有所体会,在现代的 Web 应用中很多场景都需要运用到即时通讯,比如说最常见的支付回调,与三方登录.这些业务场景都基本需要遵循以下流程: 客户端触发相关业务,并产生第三方应用的操作(比如支付) 客户端等待服务端响应结果(用户完成第三方应用的操作) 第三方应用通知服务端处理结果(支付完成) 服务端通知客户端处理结果 客户端依据结果做出反馈 (跳转到支付成功页面) 在过去,为了实现这种即时通讯,能让客户端正确响应处理结果,最为常用的技术就是轮询,因为 HTTP 协议的单向性

  • Laravel ORM对Model::find方法进行缓存示例详解

    前言 前段时间做项目时候,想要在不改变方法签名的情况下,给 Model::find 方法做个缓存.而且想要做到即插即用.下面话不多说了,来一起看看详细的介绍哦. 1.先看一下当我们调用 find 方法时,框架干了什么? 找到 Illuminate\Database\Eloquent\Model 的代码,搜索 find,没有该方法.看来是走了 __callStatic 这个魔术方法.该方法里只有一行代码: return (new static)->$method(...$parameters);

  • Laravel框架在本地虚拟机快速安装的方法详解

    本文实例讲述了Laravel框架在本地虚拟机快速安装的方法.分享给大家供大家参考,具体如下: 一直想学习larvael,可似乎发现单纯的安装就要浪费很长时间,也许我是新手的关系吧. 我是本地虚拟机安装的linux系统,centos7加php7 第一步:要保证你的机器上正确安装有curl,具体方法可参见本文附录说明 第二步:使用curl来安装composer,你要明白composer是一个基于php的安装包管理工具,服务于php生态系统. 安装命令如下: curl -sS https://getc

  • laravel 5.3中自定义加密服务的方案详解

    前言 本文介绍的是laravel 5.3中自定义加密服务的方案,利用laravel的服务容器,实现自定义加密服务注册(示例是支持长字符串的RSA加密),下面来看看详细的介绍: 创建加密解密服务类 文件地址 /app/Service/Common/CryptService.php 代码如下 下面这个是个人写的支持长字符串的RSA加密类作为示例,自定义加密的话只需更改这个文件的代码就好,其它操作只是为了实现依赖注入. <?php namespace App\Service\Common; class

随机推荐