原文地址:http://www.php.cn/php-weizijiaocheng-406076.html

这篇文章主要介绍了关于Laravel用户认证系统的实现细节,有着一定的参考价值,现在分享给大家,有需要的朋友可以参考一下

用户认证系统的实现细节

上一节我们介绍来Laravel Auth系统的基础知识,说了他的核心组件都有哪些构成,这一节我们会专注Laravel Auth系统的实现细节,主要关注Auth也就是AuthManager是如何装载认证用的看守器(Guard)和用户提供器(UserProvider)以及默认的用户注册和登录的实现细节,通过梳理这些实现细节我们也就能知道应该如何定制Auth认证来满足我们自己项目中用户认证的需求的。

通过AuthManager装载看守器和用户提供器

AuthManager装载看守器和用户提供器用到的方法比较多,用文字描述不太清楚,我们通过注解这个过程中用到的方法来看具体的实现细节。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

namespace Illuminate\Auth;

class AuthManager implements FactoryContract

{

    /**

     * 尝试从$guards属性中获取指定的Guard

     *

     * @param  string  $name

     * @return \Illuminate\Contracts\Auth\Guard|\Illuminate\Contracts\Auth\StatefulGuard

     */

    public function guard($name = null)

    {

        $name = $name ?: $this->getDefaultDriver();

        return isset($this->guards[$name])

                    ? $this->guards[$name]

                    : $this->guards[$name] = $this->resolve($name);

    }

    

    /**

     * 解析出给定name的Guard

     *

     * @param  string  $name

     * @return \Illuminate\Contracts\Auth\Guard|\Illuminate\Contracts\Auth\StatefulGuard

     *

     * @throws \InvalidArgumentException

     */

    protected function resolve($name)

    {

        //获取Guard的配置

        //$config = ['driver' => 'session', 'provider' => 'users']

        $config = $this->getConfig($name);

        if (is_null($config)) {

            throw new InvalidArgumentException("Auth guard [{$name}] is not defined.");

        }

       //如果通过extend方法为guard定义了驱动器,这里去调用自定义的Guard驱动器

        if (isset($this->customCreators[$config['driver']])) {

            return $this->callCustomCreator($name, $config);

        }

        //Laravel auth默认的配置这里是执行createSessionDriver

        $driverMethod = 'create'.ucfirst($config['driver']).'Driver';

        if (method_exists($this, $driverMethod)) {

            return $this->{$driverMethod}($name, $config);

        }

        throw new InvalidArgumentException("Auth guard driver [{$name}] is not defined.");

    }

    

    /**

     * 从config/auth.php中获取给定名称的Guard的配置

     *

     * @param  string  $name

     * @return array

     */

    'guards' => [

        'web' => [

            'driver' => 'session',

            'provider' => 'users',

        ],

        'api' => [

            'driver' => 'token',

            'provider' => 'users',

        ],

    ],

    protected function getConfig($name)

    {

        //'guards' => [

        //    'web' => [

        //        'driver' => 'session',

        //        'provider' => 'users',

        //    ],

        //    'api' => [

        //        'driver' => 'token',

        //        'provider' => 'users',

        //    ],

        //],

        // 根据Laravel默认的auth配置, 这个方法会获取key "web"对应的数组

        return $this->app['config']["auth.guards.{$name}"];

    }

    

    /**

     * 调用自定义的Guard驱动器

     *

     * @param  string  $name

     * @param  array  $config

     * @return mixed

     */

    protected function callCustomCreator($name, array $config)

    {

        return $this->customCreators[$config['driver']]($this->app, $name, $config);

    }

    

    /**

     * 注册一个自定义的闭包Guard 驱动器 到customCreators属性中

     *

     * @param  string  $driver

     * @param  \Closure  $callback

     * @return $this

     */

    public function extend($driver, Closure $callback)

    {

        $this->customCreators[$driver] = $callback;

        return $this;

    }

    

    /**

     * 注册一个自定义的用户提供器创建器到 customProviderCreators属性中

     *

     * @param  string  $name

     * @param  \Closure  $callback

     * @return $this

     */

    public function provider($name, Closure $callback)

    {

        $this->customProviderCreators[$name] = $callback;

        return $this;

    }

    

    /**

     * 创建基于session的认证看守器 SessionGuard

     *

     * @param  string  $name

     * @param  array  $config

     * @return \Illuminate\Auth\SessionGuard

     */

    public function createSessionDriver($name, $config)

    {

        //$config['provider'] == 'users'

        $provider = $this->createUserProvider($config['provider'] ?? null);

        $guard = new SessionGuard($name, $provider, $this->app['session.store']);

        if (method_exists($guard, 'setCookieJar')) {

            $guard->setCookieJar($this->app['cookie']);

        }

        if (method_exists($guard, 'setDispatcher')) {

            $guard->setDispatcher($this->app['events']);

        }

        if (method_exists($guard, 'setRequest')) {

            $guard->setRequest($this->app->refresh('request', $guard, 'setRequest'));

        }

        return $guard;

    }

    

    //创建Guard驱动依赖的用户提供器对象

    public function createUserProvider($provider = null)

    {

        if (is_null($config = $this->getProviderConfiguration($provider))) {

            return;

        }

        //如果通过Auth::provider方法注册了自定义的用户提供器creator闭包则去调用闭包获取用户提供器对象

        if (isset($this->customProviderCreators[$driver = ($config['driver'] ?? null)])) {

            return call_user_func(

                $this->customProviderCreators[$driver], $this->app, $config

            );

        }

        switch ($driver) {

            case 'database':

                return $this->createDatabaseProvider($config);

            case 'eloquent':

                //通过默认的auth配置这里会返回EloquentUserProvider对象,它实现了Illuminate\Contracts\Auth 接口

                return $this->createEloquentProvider($config);

            default:

                throw new InvalidArgumentException(

                    "Authentication user provider [{$driver}] is not defined."

                );

        }

    }

    

    /**

     * 会通过__call去动态地调用AuthManager代理的Guard的用户认证相关方法

     * 根据默认配置,这里__call会去调用SessionGuard里的方法

     * @param  string  $method

     * @param  array  $parameters

     * @return mixed

     */

    public function __call($method, $parameters)

    {

        return $this->guard()->{$method}(...$parameters);

    }

}

注册用户

Laravel Auth系统中默认的注册路由如下:

1

$this->post('register', 'Auth\RegisterController@register');

所以用户注册的逻辑是由RegisterController的register方法来完成的

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

class RegisterController extends Controller

{

    //方法定义在Illuminate\Foundation\Auth\RegisterUsers中

    public function register(Request $request)

    {

        $this->validator($request->all())->validate();

        event(new Registered($user = $this->create($request->all())));

        $this->guard()->login($user);

        return $this->registered($request, $user)

        

     }

     

    protected function validator(array $data)

    {

        return Validator::make($data, [

            'name' => 'required|string|max:255',

            'email' => 'required|string|email|max:255|unique:users',

            'password' => 'required|string|min:6|confirmed',

        ]);

    }

    

    protected function create(array $data)

    {

        return User::create([

            'name' => $data['name'],

            'email' => $data['email'],

            'password' => bcrypt($data['password']),

        ]);

    }

         

}

register的流程很简单,就是验证用户输入的数据没问题后将这些数据写入数据库生成用户,其中密码加密采用的是bcrypt算法,如果你需要改成常用的salt加密码明文做哈希的密码加密方法可以在create方法中对这部分逻辑进行更改,注册完用户后会调用SessionGuard的login方法把用户数据装载到应用中,注意这个login方法没有登录认证,只是把认证后的用户装载到应用中这样在应用里任何地方我们都能够通过Auth::user()来获取用户数据啦。

用户登录认证

Laravel Auth系统的登录路由如下

1

$this->post('login', 'Auth\LoginController@login');

我们看一下LoginController里的登录逻辑

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

class LoginController extends Controller

{

    /**

     * 处理登录请求

     */

    public function login(Request $request)

    {

        //验证登录字段

        $this->validateLogin($request);

        //防止恶意的多次登录尝试

        if ($this->hasTooManyLoginAttempts($request)) {

            $this->fireLockoutEvent($request);

            return $this->sendLockoutResponse($request);

        }

        //进行登录认证

        if ($this->attemptLogin($request)) {

            return $this->sendLoginResponse($request);

        }

        $this->incrementLoginAttempts($request);

        return $this->sendFailedLoginResponse($request);

    }

    

    //尝试进行登录认证

    protected function attemptLogin(Request $request)

    {

        return $this->guard()->attempt(

            $this->credentials($request), $request->filled('remember')

        );

    }

    

    //获取登录用的字段值

    protected function credentials(Request $request)

    {

        return $request->only($this->username(), 'password');

    }

}

可以看到,登录认证的逻辑是通过SessionGuardattempt方法来实现的,其实就是Auth::attempt(), 下面我们来看看attempt方法里的逻辑:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

class SessionGuard implements StatefulGuard, SupportsBasicAuth

{

    public function attempt(array $credentials = [], $remember = false)

    {

        $this->fireAttemptEvent($credentials, $remember);

        $this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials);

       //如果登录认证通过,通过login方法将用户对象装载到应用里去

        if ($this->hasValidCredentials($user, $credentials)) {

            $this->login($user, $remember);

            return true;

        }

        //登录失败的话,可以触发事件通知用户有可疑的登录尝试(需要自己定义listener来实现)

        $this->fireFailedEvent($user, $credentials);

        return false;

    }

    

    protected function hasValidCredentials($user, $credentials)

    {

        return ! is_null($user) && $this->provider->validateCredentials($user, $credentials);

    }

}

SessionGuardattempt方法首先通过用户提供器的retriveBycredentials方法通过用户名从用户表中查询出用户数据,认证用户信息是通过用户提供器的validateCredentials来实现的,所有用户提供器的实现类都会实现UserProvider契约(interface)中定义的方法,通过上面的分析我们知道默认的用户提供器是EloquentUserProvider

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

class EloquentUserProvider implements UserProvider

{

    从数据库中取出用户实例

    public function retrieveByCredentials(array $credentials)

    {

        if (empty($credentials) ||

           (count($credentials) === 1 &&

            array_key_exists('password', $credentials))) {

            return;

        }

        $query = $this->createModel()->newQuery();

        foreach ($credentials as $key => $value) {

            if (! Str::contains($key, 'password')) {

                $query->where($key, $value);

            }

        }

        return $query->first();

    }

    

    //通过给定用户认证数据来验证用户

    public function validateCredentials(UserContract $user, array $credentials)

    {

        $plain = $credentials['password'];

        return $this->hasher->check($plain, $user->getAuthPassword());

    }

}

class BcryptHasher implements HasherContract

{

    //通过bcrypt算法计算给定value的散列值

    public function make($value, array $options = [])

    {

        $hash = password_hash($value, PASSWORD_BCRYPT, [

            'cost' => $this->cost($options),

        ]);

        if ($hash === false) {

            throw new RuntimeException('Bcrypt hashing not supported.');

        }

        return $hash;

    }

    

    //验证散列值是否给定明文值通过bcrypt算法计算得到的

    public function check($value, $hashedValue, array $options = [])

    {

        if (strlen($hashedValue) === 0) {

            return false;

        }

        return password_verify($value, $hashedValue);

    }

}

用户密码的验证是通过EloquentUserProvider依赖的hasher哈希器来完成的,Laravel认证系统默认采用bcrypt算法来加密用户提供的明文密码然后存储到用户表里的,验证时haser哈希器的check方法会通过PHP内建方法password_verify来验证明文密码是否是存储的密文密码的原值。

用户认证系统的主要细节梳理完后我们就知道如何定义我们自己的看守器(Guard)或用户提供器(UserProvider)了,首先他们必须实现各自遵守的契约里的方法才能够无缝接入到Laravel的Auth系统中,然后还需要将自己定义的Guard或Provider通过Auth::extendAuth::provider方法注册返回Guard或者Provider实例的闭包到Laravel中去,Guard和UserProvider的自定义不是必须成套的,我们可以单独自定义Guard仍使用默认的EloquentUserProvider,或者让默认的SessionGuard使用自定义的UserProvider。

Laravel用户认证系统的实现细节的更多相关文章

  1. Laravel核心解读 -- 扩展用户认证系统

    扩展用户认证系统 上一节我们介绍了Laravel Auth系统实现的一些细节知道了Laravel是如何应用看守器和用户提供器来进行用户认证的,但是针对我们自己开发的项目或多或少地我们都会需要在自带的看 ...

  2. django用户认证系统——拓展 User 模型

    Django 用户认证系统提供了一个内置的 User 对象,用于记录用户的用户名,密码等个人信息.对于 Django 内置的 User 模型, 仅包含以下一些主要的属性: username,即用户名 ...

  3. django用户认证系统——拓展 User 模型2

    Django 用户认证系统提供了一个内置的 User 对象,用于记录用户的用户名,密码等个人信息.对于 Django 内置的 User 模型, 仅包含以下一些主要的属性: username,即用户名 ...

  4. “Django用户认证系统”学习资料收集

    首推追梦人物——Django用户认证系统 待续……

  5. Django Authentication 用户认证系统

    一. Django的认证系统 Django自带一个用户认证系统,用于处理用户账户.群组.许可和基于cookie的用户会话. 1.1 概览 Django的认证系统包含了身份验证和权限管理两部分.简单地说 ...

  6. django用户认证系统——重置密码7

    当用户不小心忘记了密码时,网站需要提供让用户找回账户密码的功能.在示例项目中,我们将发送一封含有重置用户密码链接的邮件到用户注册时的邮箱,用户点击收到的链接就可以重置他的密码,下面是具体做法. 发送邮 ...

  7. django用户认证系统——修改密码6

    再此之前我们已经完成了用户登录.注册.注销等功能,接下来让我们继续为用户提供修改密码的功能.该功能 Django 的 auth 应用也已经为我们提供,过程几乎和之前的登录功能完全一样. 编写修改密码模 ...

  8. django用户认证系统——登录4

    用户已经能够在我们的网站注册了,注册就是为了登录,接下来我们为用户提供登录功能.和注册不同的是,Django 已经为我们写好了登录功能的全部代码,我们不必像之前处理注册流程那样费劲了.只需几分钟的简单 ...

  9. django用户认证系统——基本设置1

    网站提供登录.注册等用户认证功能是一个常见的需求.因此,Django 提供了一套功能完整的.灵活的.易于拓展的用户认证系统:django.contrib.auth.在本教程中,我将向你展示 auth ...

随机推荐

  1. 洛谷P2396 yyy loves Maths VII【状压dp】

    题目:https://www.luogu.org/problemnew/show/P2396 题意:有n个数,每次选择一个表示走$a[i]$步,每个数只能选一次. 最多有两个厄运数字,如果走到了厄运数 ...

  2. 50、[源码]-Spring容器创建-Bean创建完成

    50.[源码]-Spring容器创建-Bean创建完成 11.finishBeanFactoryInitialization(beanFactory);初始化所有剩下的单实例bean: beanFac ...

  3. java+上传+文件夹

    我们平时经常做的是上传文件,上传文件夹与上传文件类似,但也有一些不同之处,这次做了上传文件夹就记录下以备后用. 首先我们需要了解的是上传文件三要素:   1.表单提交方式:post (get方式提交有 ...

  4. [APIO2012]派遣 左偏树

    P1552 [APIO2012]派遣 题面 考虑枚举每个节点作为管理者,计算所获得的满意程度以更新答案.对于每个节点的计算,贪心,维护一个大根堆,每次弹出薪水最大的人.这里注意,一旦一个人被弹出,那么 ...

  5. CSP 2019 退役记

    声明:博主不会时空穿越,也没有造成恐慌,不应禁赛三年 Day0 上午:打板子 Polya定理; exkmp; exbsgs; 乘法逆元; 矩阵快速幂; 扫描线; ST表; excrt; Dirichl ...

  6. Python里面如何拷贝一个对象?(赋值,浅拷贝,深拷贝的区别)

    答:赋值(=),就是创建了对象的一个新的引用,修改其中任意一个变量都会影响到另一个. 浅拷贝:创建一个新的对象,但它包含的是对原始对象中包含项的引用(如果用引用的方式修改其中一个对象,另外一个也会修改 ...

  7. 【SPOJ】Distinct Substrings

    [SPOJ]Distinct Substrings 求不同子串数量 统计每个点有效的字符串数量(第一次出现的) \(\sum\limits_{now=1}^{nod}now.longest-paren ...

  8. Web API 跨域

    1. NuGet下载## microsoft.aspnet.webapi.cors 2 . Web API 路由中 config.EnableCors(new EnableCorsAttribute( ...

  9. Nginx之URL重写(rewrite)配置

    Nginx URL重写(rewrite)配置及信息详解1)if判断指令 语法为if(condition){…}     #对给定的条件condition进行判断.如果为真,大括号内的rewrite指令 ...

  10. Spring框架各模块功能介绍

    一. Spring是什么? Spring由Rod johnson开发: 是一个非常活跃的开源框架: 它帮助分离项目组件(对象)之间的依赖关系: 它的主要目的是简化企业开发 二. Spring的核心概念 ...