Laravel学习笔记(27)laravel6 认证与授权(OAuth 2.0 授权码模式实例)

参考网站

  1. 准备两个服务器,一个模拟第三方服务器,一个模拟授权服务器
  2. 授权服务器(微信)(mushishi.com)

通过 Composer 包管理器安装 Passport:
其中laravel/passport 提供 OAuth 认证服务

composer require laravel/passport

创建存储客户端和令牌的数据表:

php artisan migrate

接下来,运行 passport:install 命令来创建生成安全访问令牌时所需的加密密钥,同时,这条命令也会创建用于生成访问令牌的「个人访问」客户端(Personal Access Client)和「密码授权」客户端(Password Grant Client):

php artisan passport:install

// 或者使用
php artisan passport:keys			// 加密生成的 access_token
php artisan passport:client			// 创建自定义客户端,生成client ID 和 client secret

PS:生成的access_token位置——storage/oauth-private.key

上面命令执行后,请将 Laravel\Passport\HasApiTokens Trait 添加到 App\User 模型中,这个 Trait 会给你的模型提供一些辅助函数,用于检查已认证用户的令牌和使用范围:

use HasApiTokens, Notifiable;

接下来,在 AuthServiceProvider 的 boot 方法中调用 Passport::routes 注册令牌相关的路由

Passport::routes();

// Laravel中默认refresh_token 过期时间等于access_token 过期时间,
// refresh_token 过期时间要大于access_token 过期时间,因为只有这样,当access_token过期后,由于refresh_token 没过期,可以通过refresh_token 去刷新access_token
Passport::tokensExpireIn(now()->addDays(15)); // access_token 过期时间
Passport::refreshTokensExpireIn(now()->addDays(60)); // refresh_token 过期时间

最后,将配置文件 config/auth.php 中授权看守器 guards 的 api 的 driver 选项改为 passport。此调整会让你的应用程序在在验证传入的 API 的请求时使用 Passport 的 TokenGuard 来处理:

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

    'api' => [
        'driver' => 'passport',
        'provider' => 'users',
    ],
],
  1. 第三方服务器(B站)(mushi.com)

第一步,A 网站提供一个链接,用户点击后就会跳转到 B 网站,授权用户数据给 A 网站使用。下面就是 A 网站跳转 B 网站的一个示意链接。

https://b.com/oauth/authorize?
  response_type=code&
  client_id=CLIENT_ID&
  redirect_uri=CALLBACK_URL&
  scope=read

代码实现:

(1)路由

// 登录页面
Route::view('/login', 'login');

(2)视图resources/views/login.blade.php

// 跳转到/mushishi/login,根据示意链接,发送链接
<a href="/mushishi/login">微信登陆</a>

(3)路由

浅谈CSRF攻击方式
http_build_query

$clientId = 1;	// 授权服务器生成的client_id,要与数据库一致
$clientSecret = 'T51YiRBezu2QZzmodyTGPKdNJZ4MpeiGPv5PmjiJ';	// 授权服务器生成的client_secret,要与数据库一致

// 前面点击微信登陆后进入此处
Route::get('/mushishi/login',
    function (\Illuminate\Http\Request $request) use ($clientId) {
    	// state用于保持请求和回调的状态,授权请求后原样带回给第三方。该参数可用于防止csrf攻击,可设置为简单的随机数加session进行校验
        $request->session()->put('state', $state = Str::random(40));

        $query = http_build_query([
            'client_id' => $clientId,	// 授权服务器生成的client_id,要与数据库一致
            'redirect_uri' => 'http://www.mushi.com/auth/callback',	// 授权服务器生成的redirect,也是第三方注册在授权服务器,授权服务器生成授权码后返回授权码的地址,要与数据库一致,
            'response_type' => 'code',	//固定
            'scope' => '*',	// 应用授权作用域
            'state' => session('state'),
        ]);

		// 此路径为授权服务器默认接收授权码请求的地址
        return redirect('http://mushishi.com/oauth/authorize?'.$query);
    });

注意:此处遇到一个大坑!!!重定向的URL(redirect_uri)如果跳转不带www,而是http://mushishi.com/auth/callback,授权服务器端确认授权并重定向到客户端laravel页面后,并不会停留在http://mushishi.com/auth/callback页面,而是先进入后,发生302并重定向到http://mushishi.com页面。所以redirect_uri的值一定要带有www(因为是本地测试,没有买域名,才出现了这种情况)

第二步,用户跳转后,B 网站会要求用户登录,然后询问是否同意给予 A 网站授权。用户表示同意,(此处忽略这个步骤,默认已经同意了)这时 B 网站就会跳回redirect_uri参数指定的网址(也就是回调地址)。跳转时,会传回一个授权码code,就像下面这样。

http://www.mushi.com/auth/callback?code=def50200bb1682081717ff3dec96188294458f971642950dedaf7f7eae&state=elXKJEW6EqmPbAZDnlppmNCE98Uw23HTutPY7poZ

第三步,A 网站拿到授权码以后,就可以在后端,向 B 网站请求令牌。
路由’/auth/callback’

// 回调地址,获取 code,并随后发出获取 token 请求
Route::view('/auth/callback', 'auth_callback');

回调页面

这里使用了axios方法,参考axios中文文档,先取出url里的参数后进行判断是否error,没问题就进行异步网络请求,将参数post到后台进行数据交换

<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
    function GetRequest() {
        var url = location.search; //获取url中"?"符后的字串
        var theRequest = {};
        if (url.indexOf("?") !== -1) {
            var str = url.substr(1);
            strs = str.split("&");
            for (var i = 0; i < strs.length; i++) {
                theRequest[strs[i].split("=")[0]] = decodeURI(strs[i].split("=")[1]);
            }
        }
        return theRequest;
    }

    //调用
    var Request = GetRequest();

    if (Request['error']) {
        // 用户未授权处理
        alert(Request['error']);
    }else
    {
        var code = Request['code'];
        var state = Request['state'];

        axios.post('/get/token', {
            params: {
                code,
                state
            }
        })
            .then(function (response) {
                console.log(response.data);
            });
    }
</script>

axios.post使用案例参考:

// post 数据
let postData = { username: 'user1', password: '123' }
 
axios.post('/login', postData)
    .then(response => {
        // post 成功,response.data 为返回的数据
        // console.log() 方法用于在‘控制台’输出信息。
        console.log(response.data)
    })
    .catch(error => {
        // 请求失败
        console.log(error)
    })

路由’/get/token’

使用Guzzle与授权服务器进行后台数据交互

Route::post('/get/token', function (\Illuminate\Http\Request $request) use (
    $clientId,
    $clientSecret
) {
    // csrf 攻击处理
    // 之前存的state
    $state = $request->session()->get('state');
    // 如果判断为false,则抛错,与throw_if相反
    throw_unless(
        strlen($state) > 0 && $state === $request->params['state'],
        InvalidArgumentException::class
    );

    $response
        = (new \GuzzleHttp\Client())->post('http://mushishi.com/oauth/token', [
        'form_params' => [
            'grant_type' => 'authorization_code',
            'client_id' => $clientId,
            'client_secret' => $clientSecret,
            'redirect_uri' => 'http://www.mushi.com/auth/callback',
            'code' => $request->params['code'],
        ],
    ]);

    return json_decode((string)$response->getBody(), true);
});
  1. 刷新access_token
// 刷新 token
Route::view('/refresh/page', 'refresh_page');

Route::post('/refresh', function (\Illuminate\Http\Request $request) use (
    $clientId,
    $clientSecret
) {
    $http = new GuzzleHttp\Client;
    $response = $http->post('http://lishen.com/oauth/token', [
        'form_params' => [
            'grant_type' => 'refresh_token',
            'refresh_token' => $request->params['refresh_token'],
            'client_id' => $clientId,
            'client_secret' => $clientSecret,
        ],
    ]);

    return json_decode((string)$response->getBody(), true);
});
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
    axios.post('/refresh', {
        params: {
        	// 之前给的access_token
            refresh_token: "def502009e634dd59ac4dcd4843be50c3a7a6c76fe0c26a6a948d45b99e393cdf99d1a212a8752d0ce02f4cbc25008972b524336f23b60dfc4198e5413b7e43250126b0d1780afb85443edc1579870e823eedea4313448ffcbc" 
        }
    })
        .then(function (response) {
            console.log(response.data);
        });
</script>

你可能感兴趣的:(Laravel学习笔记)