参考网站
通过 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',
],
],
第一步,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);
});
// 刷新 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>