【laravel】- 用户认证

3/20/2022 laravel

基于laravel-8,使用Guard创建一个自定义认证系统,redis存储token

# 配置 Auth guard

在 `config/auth.php` 文件中 更新如下

<?php

return [

    'defaults' => [
        'guard' => 'api',
        'passwords' => 'users',
    ],
    
    'guards' => [
        'web' => [
            'driver' => 'session',
            'provider' => 'users',
        ],
        'api' => [
            'driver'   => 'user_token',
            'provider' => 'cache_user',
        ],

        'admin' => [
            'driver'   => 'user_token',
            'provider' => 'cache_user',
        ],
    ],

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

        // 'users' => [
        //     'driver' => 'database',
        //     'table' => 'users',
        // ],
    ],
    'passwords' => [
        'users' => [
            'provider' => 'users',
            'table' => 'password_resets',
            'expire' => 60,
            'throttle' => 60,
        ],
    ],
    
     'password_timeout' => 10800,
];
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

# 配置用户模型

<?php

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;



class User extends Authenticatable
{
    protected $table = 'users';

}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 创建用户token守护器

文件位置:`app/Auth/Guard/CustomTokenGuard.php`

<?php

namespace App\Auth\Guard;

use App\Constants\RedisConst;
use Illuminate\Auth\TokenGuard;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Redis;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;

class CustomTokenGuard extends TokenGuard
{

    public function user()
    {
        $this->inputKey = 'token';
        $this->storageKey = 'id';

        if (!is_null($this->user)) {
            return $this->user;
        }
        $user = null;
        $token = $this->getTokenForRequest();
        if (!$token) throw new UnauthorizedHttpException('user_token', '未包含token!');
        if (!$tokenKey = Redis::get('user_token' . $token)) throw new UnauthorizedHttpException('user_token', 'token不正确!');

        $userId = explode(':', $tokenKey)[1];

        $user = $this->provider->retrieveByCredentials(
            [$this->storageKey => $userId]
        );
        return $this->user = $user;
    }



    /**
     * Get the token for the current request.
     *
     * @return string
     */
    public function getTokenForRequest()
    {
        return $this->request->header($this->inputKey);
    }
}
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

# 配置用户守护器

文件位置:`app/Auth/Guard/CacheUserProvider.php`

<?php

namespace App\Auth\UserProvider;


use Illuminate\Auth\EloquentUserProvider;
use Illuminate\Contracts\Hashing\Hasher as HasherContract;

class CacheUserProvider extends EloquentUserProvider
{
    public function __construct(HasherContract $hasher, $model)
    {
        parent::__construct($hasher, $model);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 注册用户守护实例

更新注册 `app/Providers/AuthServiceProvider.php`

<?php

namespace App\Providers;

use App\Auth\Guard\CustomTokenGuard;
use App\Auth\UserProvider\CacheUserProvider;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Gate;

class AuthServiceProvider extends ServiceProvider
{
    /**
     * The policy mappings for the application.
     *
     * @var array<class-string, class-string>
     */
    protected $policies = [
        // 'App\Models\Model' => 'App\Policies\ModelPolicy',
    ];

    /**
     * Register any authentication / authorization services.
     *
     * @return void
     */
    public function boot()
    {
        $this->registerPolicies();
        // 注册
        $auth = Auth::provider('cache_user', function ($app, $config) {
            return new CacheUserProvider($app['hash'], $config['model']);
        });

        $auth->extend('user_token', function ($app, $name, $config) use ($auth) {
            $guard = new CustomTokenGuard(
                $auth->createUserProvider($config['provider'] ?? null),
                $app['request']
            );
            $app->refresh('request', $guard, 'setRequest');

            return $guard;
        });
    }
}
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

# 进行登录设计

路由地址 `routes/admin.php`

Route::post('authorizations', [AuthorizationsController::class, 'login']);
1
<?php

namespace App\Http\Controllers\Admin;


use App\Constants\ExceptionConst;
use App\Exceptions\InvalidRequestException;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Redis;

class AuthorizationsController extends Controller
{
    public function login(Request $request)
    {
        $account = $request->account;
        $password = $request->password;

        $user = User::where('account', $account)->first();

        if (empty($user->password) || $this->transformPassword($password) !== $user->password) return response(['status' => -1, 'msg' => '账号或者密码错误!']);

        // 生成 token
        $userToken = $this->generateUserToken($user->id);

        return $this->success($userToken);
    }

    private function generateUserToken($reguserId)
    {
        $userToken = sha1($reguserId . time());
        Redis::set('user_token' . $userToken, 'reguser:' . $reguserId, 'EX', 604800); // 一周
        Redis::sAdd('reguser:usertokens:' . $reguserId, 'user?_token' . $userToken);

        return $userToken;
    }
}
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

示例如下

示例

# 检验是否守护成功

接口文件routes/api.php

<?php

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use \App\Http\Controllers\Api\TestController;

Route::group(['middleware' => ['auth:api']], function () {
    Route::prefix('test')->group(function () {
        Route::get('/', [TestController::class, 'ApiTest']); // 测试
    });
});
1
2
3
4
5
6
7
8
9
10
11

控制器内容

<?php

namespace App\Http\Controllers\Api;


use \App\Http\Controllers\Api\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class TestController extends Controller
{

    public function ApiTest(Request $request)
    {
        $userId = Auth::id();
        $user = Auth::user();
        
        return response(['status' => 0, 'msg' => '', 'result' => ['user_id' => $userId, 'user' => $user]]);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

示例

注意上面有抛出UnauthorizedHttpException异常,我们稍作处理去接管这个异常信息,为了友好的输出错误信息 更新app/Exceptions/Handler.php如下:

public function render($request, Throwable $e)
    {
        // 用户认证的异常
        if ($e instanceof UnauthorizedHttpException) {
             return \response(['status' => 1, 'msg' => $e->getMessage()])
             ->setStatusCode(Response::HTTP_UNAUTHORIZED);
        }

        return parent::render($request, $e); // TODO: Change the autogenerated stub
    }
1
2
3
4
5
6
7
8
9
10

当未包含token或者token错误输出如下:

示例2

示例3

Last Updated: 2/13/2023, 4:03:06 PM