Laravel 自定义认证器实现开放平台 AccessToken 认证

案例场景

比如我们在做开发平台的时候,总会有个 AccessToken 的获取,还有一个过期时间,那么我们在做这种的时候,默认下,我们的Laravel里面并没有这样的操作,你会说用 Passport OAuth 这个,说实在,这种在接口方面,并不是很有友好,那么我们怎么办,现在我可以自己自定义一个认证器。

需求整理

  • 申请app_id
  • 获得app_secret
  • 请求数据去获取 access_token

微信公众平台就是这种模式,还有很多的其他开发平台,也是大致如此
微信公众平台的介绍

场景测试

1.创建一个账号先
黑白课堂

2.获取AccessToken

黑白课堂

3.检验结果
黑白课堂

Laravel 自定义认证器

可以实现我们任何想要实现的登录形式

实现流程

  • 创建认证器
  • 编写认证器类
  • 继承认证器基础类

AuthServiceProvider 自定义一个

app/Providers/AuthServiceProvider.php

<?php

namespace App\Providers;

use App\Guards\OpenApiGuard;
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
     */
    protected $policies = [
        // 'App\Model' => 'App\Policies\ModelPolicy',
    ];

    /**
     * Register any authentication / authorization services.
     *
     * @return void
     */
    public function boot()
    {
        $this->registerPolicies();

        Auth::extend('openApi', function ($app, $name, array $config) {
            $guard = new OpenApiGuard($app['request']);
            return $guard;

        });

    }
}

创建认证器 OpenApiGuard

app/Guards/OpenApiGuard.php

<?php

namespace App\Guards;

use Illuminate\Auth\GuardHelpers;
use Illuminate\Contracts\Auth\Guard;
use Illuminate\Http\Request;

class OpenApiGuard implements Guard
{
    use GuardHelpers;

    protected $app;
    protected $request;
    protected $inputKey;//表单值

    public function __construct(Request $request)
    {
        $this->request = $request;
        $this->inputKey = 'access_token';
    }

    /**
     * Get the currently authenticated user.
     *
     * @return \Illuminate\Contracts\Auth\Authenticatable|null
     */
    public function user()
    {

    }

    /**
     * Validate a user's credentials.
     *
     * @param  array $credentials
     * @return bool
     */
    public function validate(array $credentials = [])
    {

    }
}

文档说了,我们的认证器需要返回一个实现 Illuminate\Contracts\Auth\Guard 的接口,因此我们需要继承下这个接口方法,GuardHelpers 帮我写好了几个判断的方法,我们引入下,这里我们看到有2个 方法,一个是user,validate,
黑白课堂
那么我们只需要做好这个2个方法的检测就可以了。

创建所需数据库

这里以数据库作为存储,当然你也可以用redis
像刚才的场景,我们需要
黑白课堂

创建好这3个表

php artisan make:model App\Models\Api -m

php artisan make:model App\Models\ApiToken -m

模型类文件

ApiToken

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class ApiToken extends Model
{
    protected $guarded = ['id'];
    protected $dates = ['expired_at'];
    /**
     * 找回会员
     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
     */
    public function user(){
        return $this->belongsTo(User::class,'user_id','id');
    }
    public function api()
    {
        return $this->belongsTo(Api::class, 'app_id', 'app_id');
    }
}

Api

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Api extends Model
{
    //主键
    protected $primaryKey = 'app_id';
    //主键类型
    protected $keyType = 'string';
    //自动增长false
    public $incrementing = false;
    //隐藏属性
    protected $hidden = ['app_secret'];

    //包含的是不允许批量赋值的数组,空表示全部可以
    protected $guarded = [];
    public function user()
    {
        return $this->belongsTo(User::class, 'user_id','id');
    }
    public function token()
    {
        return $this->hasOne(ApiToken::class, 'app_id', 'app_id');
    }
}

User

<?php

namespace App\Models;

use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable
{
    use Notifiable;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name', 'email', 'password',
    ];

    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
        'password', 'remember_token',
    ];

    /**
     * The attributes that should be cast to native types.
     *
     * @var array
     */
    protected $casts = [
        'email_verified_at' => 'datetime',
    ];
    public function token()
    {
        return $this->hasOne(ApiToken::class, 'user_id','id');
    }
    public function api()
    {
        return $this->hasOne(Api::class, 'user_id','id');
    }
}

认证器类

<?php

namespace App\Guards;

use App\Exceptions\OpenApiException;
use App\Models\ApiToken;
use Illuminate\Auth\GuardHelpers;
use Illuminate\Contracts\Auth\Guard;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;

class OpenApiGuard implements Guard
{
    use GuardHelpers;

    protected $app;
    protected $request;
    protected $inputKey;//表单值
    public static $ttl=7200;//过期时间

    public function __construct(Request $request)
    {
        $this->request = $request;
        $this->inputKey = 'access_token';
    }

    /**
     * Get the currently authenticated user.
     *
     * @return \Illuminate\Contracts\Auth\Authenticatable|null
     */
    public function user()
    {
        if(!is_null($this->user)){
            return $this->user;
        }
        $apiToken = $this->getApiToken();
        return $this->user = $apiToken->user;
    }

    public function getApiToken(){
        $token=$this->getRequestToken();
        return $this->checkToken($token);
    }

    public function checkToken($token){
        if (is_null($accessToken = ApiToken::where('access_token', $token)->first())) {
            throw new OpenApiException('不存在此AccessToken');
        }

        if (Carbon::now()->lte($accessToken->expired_at)) {

            return $accessToken;
        }

        throw new OpenApiException(
            'AccessToken 过期'
        );
    }

    public function getRequestToken(){
        $token = $this->request->query($this->inputKey);

        if (empty($token)) {
            $token = $this->request->bearerToken();
        }

        if (! empty($token)) {
            return $token;
        }
        throw new OpenApiException('缺少AccessToken值');
    }

    /**
     * Validate a user's credentials.
     *
     * @param  array $credentials
     * @return bool
     */
    public function validate(array $credentials = [])
    {
        if ($this->checkToken($credentials['access_token'])) {
            return true;
        }

        return false;
    }
}

异常处理类

<?php

namespace App\Exceptions;

use Throwable;

class OpenApiException extends \Exception
{
    public function render($request)
    {
        return response()->json([
            'code' => $this->getCode(),
            'message' => $this->getMessage(),
        ]);
    }
}

创建路由进行测试

<?php

use Illuminate\Support\Facades\Route;

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('/', function () {
    return view('welcome');
});

//创建接口账号信息
Route::get('open/api/create', 'OpenApiController@create');

//获取AccessToken
Route::post('open/api/token', 'OpenApiController@token');

//获取认证账号信息

Route::post('open/api/user', 'OpenApiController@user')->middleware('auth:openApi');

控制器创建测试

<?php

namespace App\Http\Controllers;

use App\Guards\OpenApiGuard;
use App\Models\Api;
use App\Models\ApiToken;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;

class OpenApiController extends Controller
{
    /**
     * 创建账号
     * @return \Illuminate\Http\JsonResponse
     */
    public function create(){

        //先创建个账号
        $user_data=[
            'name'=>'kongqi',
            'email'=>'kongqi@qq.com',
            'password'=>bcrypt('123456'),
        ];

        $user=User::firstOrNew(['name'=>'kongqi'],$user_data);
        if($user->app){
            return response()->json($user->app->toArray());
        }
        $create_data=[
            'user_id' => $user->id,
            'app_id' => md5(uniqid('app_id', true) . $user->id),
            'app_secret' => md5(Str::random(32)),
            'mark' => '测试账号',
        ];
        $create_data=Api::updateOrCreate(['user_id'=>$user->id],$create_data);
        //还要把app_secret显示出来

        return response()->json($create_data->makeVisible('app_secret')->toArray());

    }

    /**
     * 取得token
     * @param Request $request
     * @return \Illuminate\Http\JsonResponse
     */
    public function token(Request $request){
        $app_id=$request->input('app_id');
        $app_secret=$request->input('app_secret');
        $app=Api::where('app_id',$app_id)->where('app_secret',$app_secret)->first();
        if(empty($app)){
            return response()->json(['code'=>1,'msg'=>'找不到账号']);
        }

        $data= ApiToken::updateOrCreate([
            'user_id' => $app->user_id,
            'app_id' => $app->app_id,
        ], [
            'access_token' => md5(uniqid('access_token', true) . $app->user_id . $app->app_id . $app->app_secret),
            'expired_at' => Carbon::now()->addSeconds(OpenApiGuard::$ttl),
            'user_id'=>$app->user_id,
            'app_id'=>$app->app_id
        ]);
        return response()->json($data->toArray());
    }

    /**
     * 获取认证信息
     * @param Request $request
     * @return \Illuminate\Http\JsonResponse
     */
    public function user(Request $request){
        $user=$request->user();//取得认证后的会员
        $data['user_request']=$user;
        $auth_user=Auth::guard('openApi')->user();
        $data['auth_user']=$auth_user;
        return response()->json($data);
    }
}

过滤419令牌

app/Http/Middleware/VerifyCsrfToken.php

<?php

namespace App\Http\Middleware;

use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;

class VerifyCsrfToken extends Middleware
{
    /**
     * The URIs that should be excluded from CSRF verification.
     *
     * @var array
     */
    protected $except = [
        //
        'open/api/*'
    ];
}

配置一个认证器

<?php

return [

    'defaults' => [
        'guard' => 'web',
        'passwords' => 'users',
    ],

    'guards' => [
        'web' => [
            'driver' => 'session',
            'provider' => 'users',
        ],
        'openApi' => [
            'driver' => 'openApi',
            'provider' => 'users',
        ],

        'api' => [
            'driver' => 'token',
            'provider' => 'users',
            'hash' => false,
        ],
    ],

    'providers' => [
        'users' => [
            'driver' => 'eloquent',
            '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.获取AccessToken

黑白课堂

3.检验结果
黑白课堂

到此完成。

Laravel其他认证器使用

超级简易的

  Auth::viaRequest('customToken', function ($request) {
            return User::where('token', $request->token)->first();
   });

认证器需要添加下

<?php

return [
    ...

    'guards' => [
      ....
        'customToken' => [
            'driver' => 'customToken',
            'provider' => 'users',
            'hash' => false,
        ],
    ],

];

这种就不需要自己定义一个认证器类来实现,可以完成编写验证规则。最终你需要返回一个值,那么这个值就是作为认证器通过之后返回的值。

 $auth_user=Auth::guard('customToken')->user();//就是这个值,上面返回什么,这里就取到什么

其他认证器不再讲解,看下官方的操作。

总结

以上你还可以将它发部成一个composer 包,这里不再深入打包成包,详细包打包,看这里 https://www.heibaiketang.com/blog/show/520.html

评论区 (0)

没有记录
支持 markdown,图片截图粘贴拖拽都可以自动上传。

相关帖子

黑白课堂

如果你在.env文件下配置了变量参数,在路由缓存下会无效

| 最后更新 2020-12-11 03:05:43
1596 0
黑白课堂

laravel之Artisan命令操作,以及自己编写Artisan Console命令

| 最后更新 2021-01-13 14:24:58
1398 0
黑白课堂

JWT(JSON Web Token)是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。 一个JWT实际上就是一个字符串,它由三部分组成,头部、载荷与签名

| 最后更新 2021-01-13 14:25:29
1530 0
黑白课堂

扩展一个指令在blade模板中使用。

| 最后更新 2020-12-11 03:04:51
1144 0
黑白课堂

所有Laravel应用启动的中心,所有Laravel的核心服务都是通过服务提供者启动,服务提供者是应用配置的中心. >这里需要了解下IOC(控制反转)也叫依赖注入

| 最后更新 2021-01-11 03:14:14
1047 0
黑白课堂

控制对资源的访问权限,这个权限不同于RBAC(角色的权限访问控制),比如,只能操作自己的信息,可以说是拟补RBAC的更加细腻的权限。

| 最后更新 2021-01-13 14:24:00
1068 0
黑白课堂

黑白课堂 · 技术专家

专业PHP开发

年度VIP 站长创业者玉树凌风每天醒来0收入
查看更多

最新视频课程

钻级赞助商