原文地址: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. Mysql5.7.27.msi的下载与安装

    1.下载地址链接:https://dev.mysql.com/downloads/windows/installer/8.0.html 2. 点击下载后可以选择不用登录直接下载 3.下载的mysql安 ...

  2. SQL server 中rowcount与@@rowcount 的使用

    rowcount的用法: rowcount的作用就是用来限定后面的sql在返回指定的行数之后便停止处理,比如下面的示例,set rowcount 10select * from 表A 这样的查询只会返 ...

  3. Spring security invalid-session-url 的坑(配了permitAll仍然跳转到登录页)

    Spring security session配置中如果配了如下的invalid-session-url,配置了permitAll链接首次链接系统时会跳转到登录页,将该配置删除即可解决此问题. < ...

  4. (1)打鸡儿教你Vue.js

    当今世界不会Vue.js,前端必定路难走 一个JavaScript MVVM库 以数据驱动和组件化的思想构建的 Vue.js是数据驱动 HTML/CSS/JavaScript/ES6/HTTP协议/V ...

  5. Ubuntu 源 (ros)

    deb http://archive.ubuntu.com/ubuntu/ trusty main restricted universe multiverse deb http://archive. ...

  6. P3975 (后缀自动机sort)

    题目链接: https://www.luogu.org/problem/P3975 题意: 求出所有字串的第k大子串 有两种,第一种对于出现在不同位置的相同子串算作一个子串 第二种,对于不同位置的子串 ...

  7. Log4j2 - 日志框架中isDebugEnabled()的作用

    为什么要使用isDebugEnabled() 之前在系统的代码中发现有时候会在打印日志的时候先进行一次判断,如下: if (LOGGER.isDebugEnabled()) { LOGGER.debu ...

  8. 数据结构Java版之查找算法(三)

    关于查找算法,这里只进行两个算法的说明.包括 顺序查找 和 折半查找. 顺序查找: 顺序查找常用于未排序的数据中.查找速度较慢,只能应用于较小的数据量. public int sequentialSe ...

  9. 米津玄師 - Lemon

    Lemon 词:米津玄師 曲:米津玄師 夢(ゆめ)ならば どれほどよかったでしょう 未(いま)だにあなたのことを夢(ゆめ)にみる 忘(わす)れた物(もの)を 取(と)りに帰(かえ)るように 古(ふる) ...

  10. SpringBoot + Vue前后端分离图片上传到本地并前端访问图片

    同理应该可用于其他文件 图片上传 application.yml 配置相关常量 prop: upload-folder: E:/test/ # 配置SpringMVC文件上传限制,默认1M.注意MB要 ...