Laravel 认证模块 auth

郎和通
2023-12-01

一、配置

config/auth.php

<?php

return [
    'defaults' => [
        'guard' => 'web',
        'passwords' => 'users',
    ],
    'guards' => [
        'web' => [
            'driver' => 'session',//指向llluminate/Auth/SessionGuard.php
            'provider' => 'users',
        ],
        'api' => [
            'driver' => 'token', //指向llluminate/Auth/TokenGuard.php
            'provider' => 'wx_user',
            'hash' => false,//TokenGuard.php文件类属性
            'input' => 'token', //TokenGuard.php文件类属性
            'storage_key' => 'token', //TokenGuard.php文件类属性
        ],
    ],

    'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => App\User::class,
        ],

        'wx_user' => [
            'driver' => 'eloquent',
            'model' =>App\Model\UserModel::class,
        ]
    ],

    'passwords' => [
        'users' => [
            'provider' => 'users',
            'table' => 'password_resets',
            'expire' => 60,
            'throttle' => 60,
        ],
    ],

    'password_timeout' => 10800,
];

二、基础使用

Auth::check()  //判断用户是否登陆,false则重定向/login,为什么用Redirect::guest()而不用Redirect::to()?因为guest() 重定向时会将当前url保存到session中,登陆后用Redirect::intended()方法跳转之前页面。
Auth::attempt(array('email' => $email, 'password' => $password)) //attempt 接收数组作为参数1,该参的值将用于寻找数据库中的用户数据。如用 email 值在数据库中查找,如找到则将 password 值哈希加密并与数据库中密码匹配,如果匹配到,则创建认证通过的会话给用户。当用户身份认证成功 attempt 方法会返回 true,反之则返回 false。
//Auth只帮助实现验证逻辑,如果成功会写入session,下次Auth::check()的时就通过了。
//Redirect::intended(‘/’)跳转到之前页面,如Redirect::guest()方法,那intended会跳转到那时的url,而它的参数只是个默认值,再没有记录历史url的时候会跳转到’/’。
//Auth还有些其他方法,如 Auth::basic() 可实现http basic认证。

三、自定义加密验证

3.1 寻找auth模块

# config/app.php
'aliases' => [
  'App' => Illuminate\Support\Facades\App::class,
  'Artisan' => Illuminate\Support\Facades\Artisan::class,
  'Auth' => Illuminate\Support\Facades\Auth::class,

调用Auth其实是调用 Illuminate\Support\Facades\Auth::class ,打开文件

class Auth extends Facade{
    protected static function getFacadeAccessor(){
        return 'auth';
    }
}

Auth是通过Facade动态绑定的,绑定到 vendor/laravel/framework/src/Illuminate/AuthServiceProvider

class AuthServiceProvider extends ServiceProvider{

    protected function registerAuthenticator(){
        $this->app->singleton('auth', function ($app) {
            $app['auth.loaded'] = true;
            return new AuthManager($app);
        });
 
        $this->app->singleton('auth.driver', function ($app) {
            return $app['auth']->guard();
        });
    }
}

默认Auth绑定了AuthManager,打开AuthManager文件

 <?php
namespace Illuminate\Auth;
 
use Closure;
use InvalidArgumentException;
use Illuminate\Contracts\Auth\Factory as FactoryContract;
class AuthManager implements FactoryContract{
    use CreatesUserProviders;
 
    protected $app;
    protected $guards = [];
 
    public function guard($name = null){
        $name = $name ?: $this->getDefaultDriver();
 
        return $this->guards[$name]??$this->guards[$name] = $this->resolve($name);
    }
 
    public function getDefaultDriver(){
        return $this->app['config']['auth.defaults.guard'];
    }
 
    public function __call($method, $parameters){
        return $this->guard()->{$method}(...$parameters);
    }
}

没找到attempt方法,但有__call魔术方法,直接用 dd(get_class($this->guard())); 真正的attempt被谁调用呢?打印SessionGuard,打开Illuminate\Auth\SessionGuard,终于发现attempt实现

class SessionGuard implements StatefulGuard, SupportsBasicAuth
{
    use GuardHelpers, Macroable;
    public function attempt(array $credentials = [], $remember = false){
        $this->fireAttemptEvent($credentials, $remember);
 
        $this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials);
        if ($this->hasValidCredentials($user, $credentials)) {
            $this->login($user, $remember);
            return true;
        }
        $this->fireFailedEvent($user, $credentials);
        return false;
    }

这是attempt实现,通过 $this->provider->retrieveByCredentials($credentials)获取用户信息,并验证,如果成功则登录,并返回true,所以我们真正做的密码验证肯定在retrieveByCredentials这个方法里面 Laravel 默认提供了 UserProviderEloquentUserProvider 打开改方法

class EloquentUserProvider implements UserProvider{
    protected $hasher;
 
    protected $model;
    public function __construct(HasherContract $hasher, $model){
        $this->model = $model;
        $this->hasher = $hasher;
    }
    public function validateCredentials(UserContract $user, array $credentials){
        $plain = $credentials['password'];
        return $this->hasher->check($plain, $user->getAuthPassword());
    }
    public function setHasher(HasherContract $hasher){
        $this->hasher = $hasher;
        return $this;
    }
}

所以这里的hasher就是系统默认的BcryptHasher了,修改他并注入自己的haser。ok,开始搞它

3.2 编写自己的密码规则hasher

<?php
 
namespace App\Helpers\Hasher;
 
use Illuminate\Contracts\Hashing\Hasher;
 
class MD5Hasher implements Hasher{
    public function check($value, $hashedValue, array $options = []){
        return $this->make($value) === $hashedValue;
    }

    public function needsRehash($hashedValue, array $options = []){
        return false;
    }
 
    public function make($value, array $options = []){
        $value = env('SALT', '').$value;
        return md5($value);  //这里写你自定义的加密方法
    }
}

3.3 用自己的Hasher替换默认的Hasher

创建MD5HashServiceProvider

php artisan make:provider MD5HashServiceProvider

添加如下方法

<?php
 
namespace App\Providers;
 
use App\Helpers\Hasher\MD5Hasher;
use Illuminate\Support\ServiceProvider;
 
class MD5HashServiceProvider extends ServiceProvider{
  
    public function boot(){
        $this->app->singleton('hash', function () {
            return new MD5Hasher;
        });
    }

    public function register(){}
 
    public function provides(){
        return ['hash'];
    }
}

然后在config/app.phpproviders中,将 Illuminate\Hashing\HashServiceProvider::class, 替换为 \App\Providers\MD5HashServiceProvider::class,

OK,大功告成

四、自定义auth 验证

4.1 修改 user model

# 新增,获取需验证的字段
public function getAuthPassword (){
  return [
    'password'=> $this->attributes['password'],
    'salt'=> $this->attributes['code']
  ];
}

4.2 新增 provider

php artisan make:provider UserServiceProvider
# 重写EloquentUserProvider类的validateCredentials方法
class MyUserProvider extends EloquentUserProvider{
    public function __construct (HasherContract $hasher, $model){
        $this->model = $model;
        $this->hasher = $hasher;
    }

    public function register(){}

    public function boot(){}

    // 认证给定的用户和给定的凭证是否符合
    public function validateCredentials (Authenticatable $user, array $credentials){
        $plain = $credentials['password'];
        $secret = $user->getAuthPassword();
        if(password_verify($plain, $secret)){
            return true;
        } elseif($this->think_ucenter_md5($plain) === $secret){
            $user->password = password_hash($plain, PASSWORD_DEFAULT);
            $user->save();
            return true;
        }
    }
    public function think_ucenter_md5 ($str){
        return md5(sha1($str) . 'VvKl0QZBE7nao5xtXqGkWrMPchRbHdwmLF361izT');
    }
}

4.3 在 AppServiceProvider 的 boot 注册

Auth::provider('myuserprovider', function(){
    return new MyUserProvider();    // 返回自定义的 user provider
});

4.4 修改 config/auth.php

config\auth.php 的 guards 数组中添加自定义 guard,一个自定义 guard 包括两部分: driver 和 provider.

'oustn' => [
    'driver' => 'myguard',
    'provider' => 'myusers',
],
...
//在providers 数组中添加自定义 user provider
'myusers' => [
    'driver' => 'myuserprovider' //具体字段根据创建 user provider 的信息添加,通过 Auth::createUserProvider('myuserprovider')创建
],

五、auth常用函数

<?php
Auth::guard("api")->user();// 获取当前认证的用户
Auth::guard("api")->check();// 判断当前用户是否登录
Auth::guard("api")->guest();// 判断当前用户是否是游客(未登录)
Auth::guard("api")->validate();// 根据提供的消息认证用户
Auth::guard("api")->setUser();// 设置当前用户
Auth::guard("api")->attempt();// 根据提供的凭证验证用户是否合法
Auth::guard("api")->id();

六、附录

详解如何修改Laravel Auth使用salt和password来认证用户

 类似资料: