在实际开发中,使用框架可以使开发者在底层代码花费的时间,将主要精力放在业务逻辑上,同时还能保证项目的可升级性和可维护性。市面上常见的的PHP框架有很多,本书选择市面上非常流行的laravel框架进行辩解。
Laravel是一套简洁、优雅的框架,具有简洁且富于表达性的语法。Laravel秉承不要重复的理念,提倡代码的重用。Laravel还为开发大型应用提供了各种强大的支持功能,这些功能包括自动验证、路由、Session、缓存、数据库迁移等。
无论在学习还是在工作中,开发环境的不同可能会产生很多不必要的问题。为了保证项目的稳定运行,在开始使用Laravel之前,应先进行开发环境的搭建。Laravel常见的开发环境有WAMP环境和LAMP环境。WAMP环境是由Windows操作系统、ApacheHTTP服务器、MySQL数据库和拍PHP软件组成的环境,而LAMP环境是将操作系统换成了Lniux系统,其他软件与WAMP环境相同。
搭建开发环境:一、先安装Apache安装并配置好,使用代码编辑器打开Apache的配置文件conf\httpd.conf找到第37行配置,具体如下:Define SRVROOT "C:/Apache2.4"。启动服务并进入浏览器进行访问测试(http://localhost)。二、安装PHP,创建PHP.nin配置文件,在Apache中引入PHP模块,测试PHP是否安装成功,开启常用的PHP扩展,配置索引页。三、安装mysql,创建MySQL的配置文件,初始化数据库具体命令:mysql --initialize-insecure,启动mysql服务,登录MySQL服务器,设置用户密码,配置虚拟主机。安装Composer依赖管理工具,安装Visual Studio Code编辑器。四、安装Laravel框架,开启必要的扩展,使用composer安装laravel框架。
一、学习方法的探索
在学习Laravel这个全新的框架时,我主要采用了以下几种学习方法:
1. 官方文档:Laravel的官方文档非常详尽,且与最新版本同步更新。通过阅读官方文档,我快速掌握了框架的基本概念、功能和用法。
2. 视频教程:通过观看一些优秀的Laravel教程,可以更直观地理解框架特性,了解其背后的设计思想,并掌握实际开发技巧。
3. 实践项目:参加实际项目中,逐步运用Laravel解决实际问题,巩固和拓展已学知识。
4. 与他人交流:通过加入Laravel社区、技术论坛及开发者群组,与其他开发者交流并分享学习经验和技巧。
二、Laravel框架的基础知识
在学习Laravel过程中,我深入学习了以下基础知识:
1. 路由:Laravel的路由系统是其核心功能之一,可让开发者快速定义HTTP请求的映射关系。Laravel提供了多种路由类型,如基本路由、控制器路由、资源路由等。
2. 中间件:中间件用于处理HTTP请求在处理前后需要执行的操作。在Laravel中,中间件是按照定义顺序依次执行的,这种设计有利于保证代码的整洁有序。
3. 控制器:Laravel的控制器用于组织应用逻辑,将复杂的业务代码与路由定义分离。同时,Laravel提供了 RESTful 资源控制器用于优雅地处理 CRUD 操作。
4. 依赖注入与服务容器:Laravel框架采用了依赖注入方式来实现解除类与外部资源的耦合,可以方便的replace掉原有的实现。同时, Laravel的服务容器为类的实例化和管理提供了便利,是一种实现‘控制反转’的设计模式。
5. Eloquent ORM:Laravel的ORM(对象关系映射)系统Eloquent让数据库操作变得简单而直观。通过Eloquent,开发者可以采用面向对象的方式处理数据库中的记录。
6. Blade模板引擎:Laravel使用Blade模板引擎实现视图层,帮助开发者轻松组织和输出HTML代码。Blade的语法简洁灵活,支持布局、继承、模板组件等功能。
三、项目实践中的学习
参与项目实践,让我更深入地认识到Laravel的优点以及在实际项目中的应用:
1. 模块化开发:Laravel提供了包管理和服务提供者,帮助我们将应用的代码模块化,便于维护和扩展。
2. 高可扩展性:Laravel 提供了多种功能,如队列、事件、广播等,可以根据项目需求灵活扩展。
3. 安全性:Laravel提供了严格的安全机制,如CSRF令牌验证、输入过滤、Hash密码加密等,加强Web应用的安全性能。
4. 社区支持:Laravel有着庞大的社区支持。遇到问题时,可以查找Laravel的GitHub仓库、Stack Overflow 等社区资源寻求帮助。
四、总结
学习Laravel框架的过程使我深刻体会到了很多先进的设计思想和优雅的编程方式,使我的开发效率得到显著提升。面对不断发展的技术,我会继续努力,跟上时代步伐,不断学习,成为一个更优秀的开发者。
Laravel路由实现:
路由:vendor\laravel\framework\src\Illuminate\Routing\Route.php
代码:
<?php
/**
* Create a new Route instance. 创建一个Route实例。
*
* @param array|string $methods
* @param string $uri
* @param \Closure|array $action
* @return void
*/
public function __construct($methods, $uri, $action)
{
$this->uri = $uri;
$this->methods = (array) $methods;
$this->action = $this->parseAction($action);
if (in_array('GET', $this->methods) && ! in_array('HEAD', $this->methods)) {
$this->methods[] = 'HEAD';
}
if (isset($this->action['prefix'])) {
$this->prefix($this->action['prefix']);
}
}
/**
* Parse the route action into a standard array. 将路线动作解析成数组。
*
* @param callable|array|null $action
* @return array
*
* @throws \UnexpectedValueException
*/
protected function parseAction($action)
{
return RouteAction::parse($this->uri, $action);
}
/**
* Run the route action and return the response. 运行route操作并返回响应。
*
* @return mixed
*/
public function run()
{
$this->container = $this->container ?: new Container;
try {
if ($this->isControllerAction()) {
return $this->runController();
}
return $this->runCallable();
} catch (HttpResponseException $e) {
return $e->getResponse();
}
}
/**
* Checks whether the route's action is a controller.
*
* @return bool
*/
protected function isControllerAction() //确定动作是否是控制器
{
return is_string($this->action['uses']);
}
/**
* Run the route action and return the response.
*
* @return mixed
*/
protected function runCallable() //执行动作并且返回 响应
{
$callable = $this->action['uses'];
return $callable(...array_values($this->resolveMethodDependencies(
$this->parametersWithoutNulls(), new ReflectionFunction($this->action['uses'])
)));
}
/**
* Run the route action and return the response.
*
* @return mixed
*
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
*/
protected function runController() //执行路由动作并且返回响应
{
return $this->controllerDispatcher()->dispatch( // 将请求分配给指定的控制器和方法。
$this, $this->getController(), $this->getControllerMethod()
);
}
/**
* Get the controller instance for the route.
*
* @return mixed
*/
public function getController() // 获取执行的控制器
{
if (! $this->controller) {
$class = $this->parseControllerCallback()[0];
$this->controller = $this->container->make(ltrim($class, '\\'));
}
return $this->controller;
}
/**
* Get the controller method used for the route.
*
* @return string
*/
protected function getControllerMethod() // 获取控制器执行的方法
{
return $this->parseControllerCallback()[1];
}
/**
* Parse the controller.
*
* @return array
*/
protected function parseControllerCallback() //解析控制器, 分解成控制器类以及操作的方法
{
return Str::parseCallback($this->action['uses']);
}
/**
* Get the dispatcher for the route's controller.
*
* @return \Illuminate\Routing\Contracts\ControllerDispatcher
*/
public function controllerDispatcher() //获取路由控制器的调度程序。
{
if ($this->container->bound(ControllerDispatcherContract::class)) {
return $this->container->make(ControllerDispatcherContract::class);
}
return new ControllerDispatcher($this->container);
}
路由调用到控制器:
代码:// 得到应用核心实例
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
// 对请求处理后获得响应
$response = $kernel->handle(
$request = Illuminate\Http\Request::capture() // 获得请求参数
);
// 发送响应
$response->send();
// 终止
$kernel->terminate($request, $response);
我们想知道路由在哪分配调用就在 $kernel->handle() 这里,其中调用了 $this-sendRequestThroughRouter(),这里就将请求发送给了中间件和路由。
代码:
/**
* Send the given request through the middleware / router.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
protected function sendRequestThroughRouter($request)
{
$this->app->instance('request', $request);
Facade::clearResolvedInstance('request');
$this->bootstrap();
return (new Pipeline($this->app))
->send($request)
->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
->then($this->dispatchToRouter());
}
/**
* Get the route dispatcher callback.
*
* @return \Closure
*/
protected function dispatchToRouter()
{
return function ($request) {
$this->app->instance('request', $request);
return $this->router->dispatch($request);
};
}
$this->dispatchToRouter() 中 $this->router->dispatch($request) 就将请求传入到 Router 类中了
代码:
/**
* Dispatch the request to the application.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse
*/
public function dispatch(Request $request)
{
$this->currentRequest = $request;
return $this->dispatchToRoute($request);
}
/**
* Dispatch the request to a route and return the response.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse
*/
public function dispatchToRoute(Request $request)
{
return $this->runRoute($request, $this->findRoute($request));
}
/**
* Find the route matching a given request.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Routing\Route
*/
protected function findRoute($request)
{
$this->current = $route = $this->routes->match($request);
$this->container->instance(Route::class, $route);
return $route;
}
然后再 Router 类的 dispatch() 的方法中调用了 Router 类的 dispatchToRoute() 方法将请求向下传递
代码:
/**
* Find the first route matching a given request.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Routing\Route
*
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
*/
public function match(Request $request)
{
$routes = $this->get($request->getMethod());
// First, we will see if we can find a matching route for this current request
// method. If we can, great, we can just return it so that it can be called
// by the consumer. Otherwise we will check for routes with another verb.
$route = $this->matchAgainstRoutes($routes, $request);
if (! is_null($route)) {
return $route->bind($request);
}
// If no route was found we will now check if a matching route is specified by
// another HTTP verb. If it is we will need to throw a MethodNotAllowed and
// inform the user agent of which HTTP verb it should use for this route.
$others = $this->checkForAlternateVerbs($request);
if (count($others) > 0) {
return $this->getRouteForMethods($request, $others);
}
throw new NotFoundHttpException;
}
在dispatchToRoute()中你会发现 $route = $this->findRoute($request);的调用。
findRoute() 中调用 RouteCollection 类中的 match() 方法去匹配在初始化框架时读取到内存中的开发者在路由定义文件里定义的路由,如果未匹配到就抛出 NotFoundHttpException 异常。如匹配到就返回了 Route 类的实例
代码:
/**
* Run the given route within a Stack "onion" instance.
*
* @param \Illuminate\Routing\Route $route
* @param \Illuminate\Http\Request $request
* @return mixed
*/
protected function runRouteWithinStack(Route $route, Request $request)
{
$shouldSkipMiddleware = $this->container->bound('middleware.disable') &&
$this->container->make('middleware.disable') === true;
$middleware = $shouldSkipMiddleware ? [] : $this->gatherRouteMiddleware($route);
return (new Pipeline($this->container))
->send($request)
->through($middleware)
->then(function ($request) use ($route) {
return $this->prepareResponse(
$request, $route->run()
);
});
}
vendor\laravel\framework\src\Illuminate\Routing\Controller.php
代码: /**
* Execute an action on the controller.
*
* @param string $method
* @param array $parameters
* @return \Symfony\Component\HttpFoundation\Response
*/
public function callAction($method, $parameters)
{
return call_user_func_array([$this, $method], $parameters);
}
Laravel用户认证:
、简介
Laravel 中实现用户认证非常简单。实际上,几乎所有东西都已经为你配置好了。配置文件位于config/auth.php
,其中包含了用于调整认证服务行为的、文档友好的选项配置。
在底层代码中,Laravel 的认证组件由“guards”和“providers”组成,Guard 定义了用户在每个请求中如何实现认证,例如,Laravel 通过 session
guard来维护 Session 存储的状态、Cookie 以及 token
guard,token
guard 是认证用户发送请求时带的“API token”。
Provider 定义了如何从持久化存储中获取用户信息,Laravel 底层支持通过 Eloquent 和数据库查询构建器两种方式来获取用户,如果需要的话,你还可以定义额外的 Provider。
如果看到这些名词觉得云里雾里,大可不必太过担心,因为对绝大多数应用而言,只需使用默认认证配置即可,不需要做什么改动。
数据库考量
默认情况下,Laravel 在 app
目录下包含了一个 Eloquent 模型App\User
,这个模型可以和默认的 Eloquent 认证驱动一起使用。如果你的应用不使用 Eloquent,你可以使用 database
认证驱动,该驱动使用了 Laravel 查询构建器。
为 App\User
模型构建数据库表结构的时候,确保 password
字段长度至少有60位。
还有,你应该验证 users
表包含了可以为空的、字符串类型的 remember_token
字段长度为100,该字段用于存储被应用维护的”记住我(remember me)“的 Session 令牌,这可以通过在迁移中使用 $table->rememberToken();
来实现。
2、快速入门
Laravel 开箱提供了两个认证控制器,位于 App\Http\Controllers\Auth
命名空间下,AuthController
处理新用户注册和登录,PasswordController
用于帮助用户找回密码。每个控制器都使用 trait 来引入它们需要的方法。对很多应用而言,你根本不需要修改这两个控制器。
路由
Laravel 通过运行如下命令可快速生成认证所需要的路由和视图:
php artisan make:auth
运行该命令会生成注册和登录视图,以及所有的认证路由,同时生成 HomeController
,因为登录成功后会跳转到该控制器下的动作。当然,你也可以不用这个命令根据应用需求完全自定义或者移除这个控制器。
视图
正如上面所提到的,php artisan make:auth
命令会在 resources/views/auth
目录下创建所有认证需要的视图。
make:auth
命令还创建了 resources/views/layouts
目录,该目录下包含了应用的基础布局文件。所有这些视图都使用了 Bootstrap CSS 框架,你也可以根据需要对其进行自定义。
认证
现在你已经为自带的认证控制器设置好了路由和视图,接下来我们来实现新用户注册和登录认证。你可以在浏览器中访问定义好的路由,认证控制器默认已经包含了注册及登录逻辑(通过trait)。
自定义路径
当一个用户成功进行登录认证后,默认将会跳转到/
,你可以通过在 AuthController
中设置 redirectTo
属性来自定义登录认证成功之后的跳转路径:
protected $redirectTo = '/home';
当一个用户登录认证失败后,默认将会跳转回登录表单对应的页面。
自定义Guard
你还可以自定义实现用户认证的“guard”,要实现这一功能,需要在 AuthController
中定义 guard
属性,该属性的值对应认证配置文件 auth.php
中的相应 guard 配置:
protected $guard = 'admin';
自定义验证/存储
要修改新用户注册所必需的表单字段,或者自定义新用户字段如何存储到数据库,你可以修改 AuthController
类。该类负责为应用验证输入参数和创建新用户。
AuthController
的 validator
方法包含了新用户的验证规则,你可以按需要自定义该方法。
AuthController
的 create
方法负责使用 Eloquent ORM 在数据库中创建新的 App\User
记录。当然,你也可以基于自己的需要自定义该方法。
获取认证用户
你可以通过 Auth
门面访问认证用户:
$user = Auth::user();
此外,用户通过认证后,你还可以通过 Illuminate\Http\Request
实例访问认证用户:
-
<?php
-
namespace App\Http\Controllers;
-
use Illuminate\Http\Request;
-
use Illuminate\Routing\Controller;
-
class ProfileController extends Controller{
-
/**
-
* 更新用户属性.
-
*
-
* @param Request $request
-
* @return Response
-
*/
-
public function updateProfile(Request $request)
-
{
-
if ($request->user()) {
-
// $request->user() 返回认证用户实例...
-
}
-
}
-
}
判断当前用户是否通过认证
要判断某个用户是否登录到应用,可以使用 Auth
门面的 check
方法,如果用户通过认证则返回 true
:
-
if (Auth::check()) {
-
// The user is logged in...
-
}
此外,你还可以在用户访问特定路由/控制器之前使用中间件来验证用户是否通过认证,想要了解更多,可以查看下面的路由保护:
路由保护
路由中间件可用于只允许通过认证的用户访问给定路由。Laravel 通过定义在 app\Http\Middleware\Authenticate.php
的 auth
中间件来处理这一操作。你所要做的仅仅是将该中间件加到相应的路由定义中:
-
// 使用路由闭包...
-
Route::get('profile', ['middleware' => 'auth', function() {
-
// 只有认证用户可以进入...
-
}]);
-
// 使用控制器...
-
Route::get('profile', [
-
'middleware' => 'auth',
-
'uses' => 'ProfileController@show'
-
]);
当然,如果你正在使用控制器类,也可以在控制器的构造方法中调用 middleware
方法而不是在路由器中直接定义:
-
public function __construct(){
-
$this->middleware('auth');
-
}
指定一个Guard
添加 auth
中间件到路由后,还需要指定使用哪个 guard 来实现认证:
-
Route::get('profile', [
-
'middleware' => 'auth:api',
-
'uses' => 'ProfileController@show'
-
]);
指定的 guard 对应配置文件 auth.php
中 guards
数组的某个键。
登录失败次数限制
如果你使用了 Laravel 内置的 AuthController
类, 可以使用 Illuminate\Foundation\Auth\ThrottlesLogins
trait 来限制用户登录失败次数。默认情况下,用户在几次登录失败后将在一分钟内不能登录,这种限制基于用户的用户名/邮箱地址+IP地址:
-
<?php
-
namespace App\Http\Controllers\Auth;
-
use App\User;use Validator;
-
use App\Http\Controllers\Controller;
-
use Illuminate\Foundation\Auth\ThrottlesLogins;
-
use Illuminate\Foundation\Auth\AuthenticatesAndRegistersUsers;
-
class AuthController extends Controller{
-
use AuthenticatesAndRegistersUsers, ThrottlesLogins;
-
// AuthController类的其它部分...
-
}
3、手动认证用户
当然,你也可以不使用 Laravel 自带的认证控制器。如果你选择移除这些控制器,你需要直接使用 Laravel 认证类来管理用户认证。别担心,这很简单!
我们将会通过 Auth
门面来访问认证服务,因此我们需要确保在类的顶部导入了 Auth
门面,让我们看看 attempt
方法:
-
<?php
-
namespace App\Http\Controllers;
-
use Auth;
-
use Illuminate\Routing\Controller;
-
class AuthController extends Controller{
-
/**
-
* 处理登录认证
-
*
-
* @return Response
-
*/
-
public function authenticate()
-
{
-
if (Auth::attempt(['email' => $email, 'password' => $password])) {
-
// 认证通过...
-
return redirect()->intended('dashboard');
-
}
-
}
-
}
attempt
方法接收键值数组对作为第一个参数,数组中的值被用于从数据表中查找用户,因此,在上面的例子中,用户将会通过email
的值获取,如果用户被找到,经哈希运算后存储在数据中的密码将会和传递过来的经哈希运算处理的密码值进行比较。如果两个经哈希运算的密码相匹配那么将会为这个用户开启一个认证Session。
如果认证成功的话 attempt
方法将会返回 true
。否则,返回 false
。
重定向器上的 intended
方法将用户重定向到登录之前用户想要访问的 URL,在目标 URL 无效的情况下备用 URI 将会传递给该方法。
指定额外条件
如果需要的话,除了用户邮件和密码之外还可以在认证查询时添加额外的条件,例如,我们可以验证被标记为有效的用户:
-
if (Auth::attempt(['email' => $email, 'password' => $password, 'active' => 1])) {
-
// The user is active, not suspended, and exists.
-
}
注:在这些例子中,并不仅仅限于使用 email 进行登录认证,这里只是作为演示示例,你可以将其修改为数据库中任何其他可用作“username”的字段。
访问指定 Guard 实例
你可以使用 Auth
门面的 guard
方法指定想要使用的 guard 实例,这种机制允许你在同一个应用中对不同的认证模型或用户表实现完全独立的用户认证。
传递给 guard
方法的 guard 名称对应配置文件 auth.php
中 guards
配置的某个键:
-
if (Auth::guard('admin')->attempt($credentials)) {
-
//
-
}
退出
要退出应用,可以使用 Auth
门面的 logout
方法,这将会清除用户 Session 中的认证信息:
Auth::logout();
记住用户
如果你想要在应用中提供“记住我”的功能,可以传递一个布尔值作为第二个参数到 attempt
方法,这样用户登录认证状态就会一直保持直到他们手动退出。当然,你的 users
表必须包含 remember_token
字段,该字段用于存储“记住我”令牌。
-
if (Auth::attempt(['email' => $email, 'password' => $password], $remember)) {
-
// The user is being remembered...
-
}
如果你要“记住”用户,可以使用 viaRemember
方法来判断用户是否使用“记住我”cookie进行认证:
-
if (Auth::viaRemember()) {
-
//
-
}
其它认证方法
认证一个用户实例
如果你需要将一个已存在的用户实例登录到应用中,可以调用 Auth
门面的 login
方法并传入用户实例,传入实例必须是Illuminate\Contracts\Auth\Authenticatable
契约的实现,当然,Laravel 自带的 App\User
模型已经实现了该接口:
Auth::login($user);
通过ID认证用户
要通过用户ID登录到应用,可以使用 loginUsingId
方法,该方法接收你想要认证用户的主键作为参数:
Auth::loginUsingId(1);
一次性认证用户
你可以使用 once
方法只在单个请求中将用户登录到应用,而不存储任何 Session 和 Cookie,这在构建无状态的 API 时很有用。once
方法和attempt
方法用法差不多:
-
if (Auth::once($credentials)) {
-
//
-
}
4、基于 HTTP 的基本认证
HTTP基本认证能够帮助用户快速实现登录认证而不用设置专门的登录页面,首先要在路由中加上 auth.basic
中间件。该中间件是 Laravel 自带的,所以不需要自己定义:
-
Route::get('profile', ['middleware' => 'auth.basic', function() {
-
// 只有认证用户可以进入...
-
}]);
中间件加到路由中后,当在浏览器中访问该路由时,会自动提示需要认证信息,默认情况下,auth.basic
中间件使用用户记录上的 email
字段作为“用户名”。
FastCGI上注意点
如果你使用 PHP FastCGI,HTTP 基本认证将不能正常工作,需要在 .htaccess
文件加入如下内容:
-
RewriteCond %{HTTP:Authorization} ^(.+)$
-
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
无状态的HTTP基本认证
使用 HTTP 基本认证也不需要在 Session 中设置用户标识 Cookie,这在 API 认证中非常有用。要实现这个,需要定义一个调用 onceBasic
方法的中间件。如果该方法没有返回任何响应,那么请求会继续走下去:
-
<?php
-
namespace Illuminate\Auth\Middleware;
-
use Auth;
-
use Closure;
-
class AuthenticateOnceWithBasicAuth{
-
/**
-
* 处理输入请求.
-
*
-
* @param \Illuminate\Http\Request $request
-
* @param \Closure $next
-
* @return mixed
-
*/
-
public function handle($request, Closure $next)
-
{
-
return Auth::onceBasic() ?: $next($request);
-
}
-
}
接下来,注册路由中间件并将其添加到路由中:
-
Route::get('api/user', ['middleware' => 'auth.basic.once', function() {
-
// 只有认证用户可以进入...
-
}]);
5、重置密码
数据库考量
大多数 web 应用提供了用户重置密码的功能,Laravel 提供了便利方法用于发送密码提示及执行密码重置而不需要你在每个应用中重新实现。
开始之前,先验证 App\User
模型实现了 Illuminate\Contracts\Auth\CanResetPassword
契约。当然,Laravel 自带的 App\User
模型已经实现了该接口,并使用 Illuminate\Auth\Passwords\CanResetPassword
trait来包含实现该接口需要的方法。
生成重置令牌表迁移
接下来,用来存储密码重置令牌的表必须被创建,Laravel 已经自带了这张表的迁移,就存放在 database/migrations
目录。所有,你所要做的仅仅是运行迁移:
php artisan migrate
路由
Laravel 自带了 Auth\PasswordController
,其中包含了重置用户密码的相应逻辑。重置密码所需的路由都已经通过 make:auth
命令自动生成了:
php artisan make:auth
视图
和路由一样,重置密码所需的视图文件也通过 make:auth
命令一并生成了,这些视图文件位于 resources/views/auth/passwords
目录下,你可以按照所需对生成的文件进行相应修改。
重置密码后
定义好重置用户密码路由和视图后,只需要在浏览器中访问这些路由即可。框架自带的 PasswordController
已经包含了发送密码重置链接邮件以及更新数据库中密码的逻辑。
密码被重置后,用户将会自动登录到应用并重定向到 /home
。你可以通过定义上 PasswordController
的redirectTo
属性来自定义密码重置成功后的跳转链接:
protected $redirectTo = '/dashboard';
注意:默认情况下,密码重置令牌一小时内有效,你可以通过修改
config/auth.php
文件中的选项reminder.expire
来改变有效时间。
自定义
自定义认证 Guard
在配置文件 auth.php
中,可以配置多个”guards“,以便用于实现多用户表独立认证,你可以通过添加 $guard
属性到自带的 PasswordController
控制器的方法来使用你选择的 guard:
-
/**
-
* The authentication guard that should be used.
-
*
-
* @var string
-
*/
-
protected $guard = 'admins';
自定义密码 broker
在配置文件 auth.php
中,可以配置多个密码,以便用于重置多个用户表的密码 broker,同样,可以通过在自带的 PasswordController
控制器中添加 $broker
属性来使用你选择的 broker:
-
/**
-
* The password broker that should be used.
-
*
-
* @var string
-
*/
-
protected $broker = 'admins';
6、社会化登录认证
Laravel 中还可以使用 Laravel Socialite 通过 OAuth 提供者进行简单、方便的认证,也就是社会化登录,目前支持使用Facebook、Twitter、LinkedIn、GitHub和Bitbucket进行登录认证。
要使用社会化登录,需要在 composer.json
文件中添加依赖:
composer require laravel/socialite
配置
安装完社会化登录库后,在配置文件 config/app.php
中注册 Laravel\Socialite\SocialiteServiceProvider
:
-
'providers' => [
-
// 其它服务提供者...
-
Laravel\Socialite\SocialiteServiceProvider::class,
-
],
还要在 app
配置文件中添加 Socialite门面到
aliases
数组:
'Socialite' => Laravel\Socialite\Facades\Socialite::class,
你还需要为应用使用的 OAuth 服务添加认证信息,这些认证信息位于配置文件 config/services.php
,而且键为facebook
, twitter
,linkedin
, google
, github
或bitbucket
,这取决于应用需要的提供者。例如:
-
'github' => [
-
'client_id' => 'your-github-app-id',
-
'client_secret' => 'your-github-app-secret',
-
'redirect' => 'http://your-callback-url',
-
],
基本使用
接下来,准备好认证用户!你需要两个路由:一个用于重定向用户到 OAuth 提供者,另一个用户获取认证后来自提供者的回调。我们使用 Socialite
门面访问 Socialite :
-
<?php
-
namespace App\Http\Controllers;
-
use Socialite;
-
use Illuminate\Routing\Controller;
-
class AuthController extends Controller{
-
/**
-
* 将用户重定向到GitHub认证页面.
-
*
-
* @return Response
-
*/
-
public function redirectToProvider()
-
{
-
return Socialite::driver('github')->redirect();
-
}
-
/**
-
* 从GitHub获取用户信息.
-
*
-
* @return Response
-
*/
-
public function handleProviderCallback()
-
{
-
$user = Socialite::driver('github')->user();
-
// $user->token;
-
}
-
}
redirect
方法将用户发送到 OAuth 提供者,user
方法读取请求信息并从提供者中获取用户信息,在重定向用户之前,你还可以在请求上使用 scope
方法设置”作用域”,该方法将会重写已存在的所有作用域:
-
return Socialite::driver('github')
-
->scopes(['scope1', 'scope2'])->redirect();
当然,你需要定义路由到控制器方法:
-
Route::get('auth/github', 'Auth\AuthController@redirectToProvider');
-
Route::get('auth/github/callback', 'Auth\AuthController@handleProviderCallback');
获取用户信息
有了用户实例之后,可以获取用户的更多详情:
-
$user = Socialite::driver('github')->user();
-
// OAuth Two Providers
-
$token = $user->token;
-
// OAuth One Providers
-
$token = $user->token;
-
$tokenSecret = $user->tokenSecret;
-
// All Providers
-
$user->getId();
-
$user->getNickname();
-
$user->getName();
-
$user->getEmail();
-
$user->getAvatar();
7、添加自定义的 Guard
你可以通过 Auth
门面的 extend
方法定义自己的认证 guard,该功能需要在某个服务提供者的 boot
方法中实现:
<?php
namespace App\Providers;
use Auth;
use App\Services\Auth\JwtGuard;
use Illuminate\Support\ServiceProvider;
class AuthServiceProvider extends ServiceProvider{
/**
* Perform post-registration booting of services.
*
* @return void
*/
public function boot()
{
Auth::extend('jwt', function($app, $name, array $config) {
// Return an instance of Illuminate\Contracts\Auth\Guard...
return new JwtGuard(Auth::createUserProvider($config['provider']));
});
}
/**
* Register bindings in the container.
*
* @return void
*/
public function register()
{
//
}
}
正如你在上述例子中所看到的,传递给 extend
方法的闭包回调需要返回 Illuminate\Contracts\Auth\Guard
的实现实例,该接口包含了自定义认证 guard 需要的一些方法。
定义好自己的认证 guard 之后,可以在配置文件的 guards
配置中使用话这个 guard:
-
'guards' => [
-
'api' => [
-
'driver' => 'jwt',
-
'provider' => 'users',
-
],
-
],
8、添加自定义用户提供者
如果你没有使用传统的关系型数据库存储用户信息,则需要使用自己的认证用户提供者来扩展 Laravel。我们使用 Auth 门面上的 provider
方法定义自定义该提供者,在服务提供者调用 provider
方法如下:
<?php
namespace App\Providers;
use Auth;
use App\Extensions\RiakUserProvider;
use Illuminate\Support\ServiceProvider;
class AuthServiceProvider extends ServiceProvider{
/**
* Perform post-registration booting of services.
*
* @return void
*/
public function boot()
{
Auth::provider('riak', function($app,array $config) {
// 返回Illuminate\Contracts\Auth\UserProvider实例...
return new RiakUserProvider($app['riak.connection']);
});
}
/**
* 在容器中注册绑定.
*
* @return void
*/
public function register()
{
//
}
}
通过 provider
方法注册用户提供者后,你可以在配置文件 config/auth.php
中切换到新的用户提供者。首先,在该配置文件定义一个使用新驱动的 providers
数组:
-
'providers' => [
-
'users' => [
-
'driver' => 'riak',
-
],
-
],
然后,可以在你的 guards
配置中使用这个提供者:
-
'guards' => [
-
'web' => [
-
'driver' => 'session',
-
'provider' => 'users',
], ],
UserProvider 契约
Illuminate\Contracts\Auth\UserProvider
实现只负责从持久化存储系统中获取Illuminate\Contracts\Auth\Authenticatable
实现,例如MySQL、Riak等等。这两个接口允许 Laravel 认证机制继续起作用而不管用户数据如何存储或者何种类来展现。
让我们先看看 Illuminate\Contracts\Auth\UserProvider
契约:
-
<?php
-
namespace Illuminate\Contracts\Auth;
-
interface UserProvider {
-
public function retrieveById($identifier);
-
public function retrieveByToken($identifier, $token);
-
public function updateRememberToken(Authenticatable $user, $token);
-
public function retrieveByCredentials(array $credentials);
-
public function validateCredentials(Authenticatable $user, array $credentials);
-
}
retrieveById
方法通常获取一个代表用户的键,例如 MySQL 数据中的自增ID。该方法获取并返回匹配该ID的Authenticatabl
实现。
retrieveByToken
函数通过唯一标识和存储在 remember_token
字段中的“记住我”令牌获取用户。和上一个方法一样,该方法也返回 Authenticatabl
实现。
updateRememberToken
方法使用新的 $token
更新 $user
的 remember_token
字段,新令牌可以是新生成的令牌(在登录是选择“记住我”被成功赋值)或者null(用户退出)。
retrieveByCredentials
方法在尝试登录系统时获取传递给 Auth::attempt
方法的认证信息数组。该方法接下来去底层持久化存储系统查询与认证信息匹配的用户,通常,该方法运行一个带“where”条件($credentials[‘username’]
)的查询。然后该方法返回 UserInterface
的实现。这个方法不做任何密码校验和认证。
validateCredentials
方法比较给定 $user
和$credentials
来认证用户。例如,这个方法比较 $user->getAuthPassword()
字符串和经 Hash::make
处理的 $credentials['password']
。这个方法只验证用户认证信息并返回布尔值。
Authenticatable 契约
既然我们已经探索了 UserProvider
上的每一个方法,接下来让我们看看 Authenticatable
。该提供者应该从 retrieveById
和 retrieveByCredentials
方法中返回接口实现:
-
<?php
-
namespace Illuminate\Contracts\Auth;
-
interface Authenticatable {
-
public function getAuthIdentifier();
-
public function getAuthPassword();
-
public function getRememberToken();
-
public function setRememberToken($value);
-
public function getRememberTokenName();
-
}
这个接口很简单,getAuthIdentifier
方法返回用户“主键”,在 MySQL 后台中是ID,getAuthPassword
返回经哈希处理的用户密码,这个接口允许认证系统处理任何用户类,不管是你使用的是 ORM 还是存储抽象层。默认情况下,Laravel 自带的 app
目录下的 User
类实现了这个接口,所以你可以将这个类作为实现例子。
9、事件
Laravel 支持在认证过程中触发多种事件,你可以在自己的 EventServiceProvider
中监听这些事件:
-
/**
-
* The event listener mappings for the application.
-
*
-
* @var array
-
*/
-
protected $listen = [
-
'Illuminate\Auth\Events\Attempting' => [
-
'App\Listeners\LogAuthenticationAttempt',
-
],
-
'Illuminate\Auth\Events\Login' => [
-
'App\Listeners\LogSuccessfulLogin',
-
],
-
'Illuminate\Auth\Events\Logout' => [
-
'App\Listeners\LogSuccessfulLogout',
-
],
-
];