框架本身提供了很多常用的Web应用的机制和库,虽然很好用,但是却不能包容我预期的设计,比如Auth模块,该模块提供了两种很基本的验证驱动,一个是Database,使用QueryBuilder来与数据库进行交互,另一个是Eloquent ORM,是采用其内置的ORM库来进行数据库交互(感觉类似于.Net中的EF),但是不论采取哪种驱动,都存在同样的局限性:
必须采用内置的Hash方案对入库的密码进行保护
这一局限使得原本系统设计中的MD5 + Salt方案受到了阻挠,尽管实现会稍显麻烦,但我仍然认为这种方案更适合于我的项目,因此我参考了Laravel文档中扩展章节所述的Auth模块扩展以及网上查阅的一些资料,来对原框架进行修改,使其能够支持有我自己定义的验证驱动。
首先介绍我所考虑使用的MD5 + Salt方案的具体设计:
数据库中不应该存储密码明码应该是一个基本安全常识,通常的方案是采用MD5算法对密码进行Hash,再存入数据库,从而避免数据库泄露之后,用户的敏感数据也随之泄露,但是随着时间发展,人们存储的MD5的字典已经越来越庞大,对于一些弱口令(例如12345678、passw0d,不足够长的纯数字或字母串),即便是在不可逆算法MD5 Hash过后再入库,对于拥有字典的人而言,仍然与明文无异,对于有经验的人而言,可能看到25d55ad283aa400af464c76d713c07ad,脑子里就已经浮现了12345678这串数字了。于是对密码加Salt的想法就孕育而生,如果说用户自己设定的密码很简单,那么不妨让系统来帮他进行加强,而Salt就是在原来的密码上加的那一点“佐料”,只要能够保证无论曾经的密码多么简单,只要加完Salt之后,字典中就无法直接找到这个Hash对应的原文,那么这个方案就算是成功了。
首先,用户数据库我们需要两个字段:
psw_hash:用来存储Hash过后的密码 psw_salt:用来存储帮助Hash的的附加串
在加入这两个字段后,我们来考虑两个情景:
第一个情景就是注册(或是修改密码)的时候, 第二个情景是验证的时候,这两个情景的流程如下图所示:
左侧是情景一的图示,右侧是情景二的图示,由于在数据库中不存储密码的明文,同时采用了每个用户一种单独Salt的方案,在数据库被攻破后,窃密者必须付出极大的成本才可能破解出用户的密码明文,并且方法只能是为每种Salt都重新建立一份字典,在这样的成本下,我们可以认为这种方案是非常安全的。那么接下来的问题就是,如何在Laravel框架中扩展出这一机制?根据文档,我们可以先找到这个目录:
/vender/laravel/framework/src/illuminate/Auth
这个目录就是Laravel框架所提供的Auth库地址,其中我们首先关注:
UserProviderInterface.php
DatabaseUserProvider.php
EloquentUserProvider.php
第一个文件是一个接口,后面两个文件中的类实现了第一个文件中的接口,而后面两个类就是前文所提到的,默认Auth库所支持的两种验证驱动,我们从他们都实现的UserProviderInterface入手,下面的代码是从文档中摘录的精简过的接口定义代码:
UserProviderInterface
interface UserProviderInterface { public function retrieveById($identifier); public function retrieveByToken($identifier, $token); public function updateRememberToken(UserInterface $user, $token); public function retrieveByCredentials(array $credentials); public function validateCredentials(UserInterface $user, array $credentials);}
可以看到,都是和验证过程有关的方法,也就是说我们只需要实现这些接口方法,就可以添加自己的验证驱动,而从实际使用出发,我们在项目中会使用Eloquent ORM来产生相关实体的数据模型,所以我们无须完全重写驱动,只需要改写EloquentUserProvider中的部分方法即可,在这里我们的代码如下所示:
Custom UserProvider
<?php namespace Illuminate\Auth;class TaskPoolEloquentUserProvider extends EloquentUserProvider { public function __construct($model) { $this->model = $model; } /** * Validate a user against the given credentials. * * @param \Illuminate\Auth\UserInterface $user * @param array $credentials * @return bool */ public function validateCredentials(UserInterface $user, array $credentials) { $plain = $credentials['password']; $authPassword = $user->getAuthPassword(); return $authPassword['psw_hash'] === md5($plain.$authPassword['psw_salt']); }}
通过继承和覆盖的方式,我们改写了原有Eloquent驱动的验证逻辑。根据官方文档的描述,这个文件可以放在任意地方,也可以使用任意命名空间,在这里就不过多叙述了。
之后,注意到验证逻辑中的第一个参数UserInterface $user,这个参数告诉我们,支持Auth的User模型应当实现UserInterface,这个接口的定义文件也在这个目录下,简化的代码如下:
UserInterface
interface UserInterface { public function getAuthIdentifier(); public function getAuthPassword(); public function getRememberToken(); public function setRememberToken($value); public function getRememberTokenName();}
这些都很容易理解,都是简单的getter和setter,只需要让自己的User模型类实现它就可以了。
最后一步就是将这个扩展注册到框架当中,打开这个文件:
app/start/global.php
在其中添加如下代码:
Auth::extend('taskpooleloquent', function($app) { $provider = new \Illuminate\Auth\TaskPoolEloquentUserProvider('User'); return $provider;});
之后,就可以在Config中的Auth配置文件中使用你自己定义的这个驱动了
联系客服