我正在使用Laravel Passport来将API的某些部分授予第三方应用访问权限。
但是,我也通过自己的第一方本机Android应用程序使用自己的API。 因此,在这种情况下,我在整个互联网上寻找了最佳实践,但是却无法得出结论。
这是我发现的可能性:
我可以关注User Credential Password Grant Flow。
在这种情况下,我需要将client_secret
和client_id
传递到授权服务器。为了确保它们的安全性,我无法在移动应用程序的源代码中编写它们(APK可反编译...)。
所以,我有2个选择。
通过我自己的服务器代理并在调用oauth端点之前注入秘密:
$proxy = Request::create('/oauth/token', 'post', [
'grant_type' => 'password',
'client_id' => 1,
'client_secret' => 'myownclientsecretishere',
'username' => $username,
'password' => $password
]);
$proxy->headers->set('Accept', 'application/json');
$response = app()->handle($proxy);
使用中间件调用oauth端点时注入机密:
class InjectPasswordGrantSecret
{
public function handle($request, Closure $next)
{
$request->request->add([
'client_id' => 1,
'client_secret' => 'myownclientsecretishere'
]);
return $next($request);
}
}
这些是有效的示例,但在资源方面也很贪婪。 我尝试在本地计算机上使用 Apache基准测试,并且每秒收到9个请求。
我可以关注Personal Access Grant。
这个看起来不像OAuth2中的标准,它使我们可以通过任何自定义路线创建令牌,如下所示:
if (! auth()->attempt(compact('username', 'password'))) {
return error_response(__('auth.failed'));
}
$user = auth()->user();
$token = $user->createToken(null)->accessToken;
使用 Apache基准测试,我得到了更好的结果(大约30个请求/秒)。
但是,令牌的生存期默认情况下不可配置,而是设置为1年(请注意,存在一些变通办法,可以使用自定义提供程序来配置此生存期)。
我真的很想知道这种解决方案是否打算在生产环境中使用。
最初,我使用JWT tymon库是因为我只有自己的应用程序。但是,现在我需要使其能够与第一方和第三方应用程序一起使用,我认为(通过Laravel Passport)OAuth2将是一个很好的解决方案...
我希望有人能帮助我解决这个问题,并解释说什么是使它在生产服务器上安全且[不缓慢地]工作的好解决方案。
答案 0 :(得分:2)
OAuth2是一种标准,但是人们完成的实现有时可能会有所不同,但是在大多数情况下,最终它不会改变任何内容。
例如,使用oauth/token
或myserver/mytokenroute
作为路由路径,只要令牌生成仍然以相同的方式进行,就不会改变该路由应该做的任何事情。再举一个例子,路由是否需要client_secret
并不重要,只要它被安全地提供(并且不存储在客户端)即可。
因此,如果您需要对OAuth2流进行很好的控制,则可以自己实现Laravel Passport
路由。
首先,您可以从Passport::route();
方法中删除AuthServiceProvider@boot
,以删除对Passport路由的访问,然后仅创建所需的路由(例如,oauth / token)。
然后,创建扩展Laravel\Passport\Http\Controllers\AccessTokenController
的自己的控制器,以便能够使用Laravel Passport
提供的某些功能。在此控制器中,您可以根据需要创建任意数量的方法。
这是token
路线的示例:
use Laravel\Passport\Http\Controllers\AccessTokenController;
use Laravel\Passport\TokenRepository;
use Lcobucci\JWT\Parser as JwtParser;
use League\OAuth2\Server\AuthorizationServer;
use Psr\Http\Message\ServerRequestInterface;
class MyOAuthController extends AccessTokenController
{
public function __construct(AuthorizationServer $server, TokenRepository $tokens, JwtParser $jwt)
{
parent::__construct($server, $tokens, $jwt);
}
public function token(ServerRequestInterface $request)
{
$data = $request->getParsedBody();
// You can inject your secrets here
$data['grant_type'] = 'password';
$data['client_secret'] = 'SECRET-HERE';
$data['client_id'] = 'ID HERE';
return parent::issueToken($request->withParsedBody($data));
}
}
如果需要,还可以将parent::issueToken
的结果存储在中间变量中以使用它,而不用将其返回给客户端。
然后,在您的AuthServiceProvider@boot
方法中声明路由:
Route::post('/myserver/mytokenroute', '\App\Http\Controllers\MyOAuthController@token');
别忘了您可以使用中间件,例如,我将使用它来限制单个客户端的请求:
Route::middleware('throttle:10,1')->post('/myserver/mytokenroute', '\App\Http\Controllers\MyOAuthController@token');
使用这种方法,您将不会使用代理,而且我发现它比使用Personal Access Token
更好,因为您可以更好地控制所有其他流(授权代码,刷新流等)。如果您需要特定的行为,只需创建一个新方法并将其链接到路由,即可将源代码保持整洁并集中在单个控制器中。
答案 1 :(得分:2)
这是我经常提到的页面:https://oauth2.thephpleague.com/authorization-server/which-grant/
它说
<块引用>我们强烈建议您使用授权码流程 密码授予有几个原因。 我们取消了密码授予选项。
然后,它在图中清楚地说明您应该使用
<块引用>使用 PKCE 授予授权码
并且还表明
<块引用>如果客户端是一个完全在前端运行的web应用 结束(例如单页 Web 应用程序)或本机应用程序,例如 作为移动应用程序,您应该使用授权代码授予 PKCE 扩展。 您可以在文档中进一步阅读。
此外,这里有一个很好的教程,用一个例子解释了流程的每一个细节: https://auth0.com/docs/architecture-scenarios/mobile-api
我希望这些有帮助。
PS:当我需要在我的第一方应用程序中授权我的用户时,我通过引用这个图表来使用密码授权。但是,它似乎已更改,并且密码授予现在不再是最佳做法,因此不推荐使用。
答案 2 :(得分:1)
我之前完成的工作是通过扩展Laravel\Passport\Http\Controllers\AccessTokenController
的我自己的控制器实现的。您不仅可以设置此控制器来处理创建令牌,还可以添加路由来刷新和吊销令牌。
例如,我有一个create
方法用于显示登录表单,一种store
方法用于创建访问令牌,一种refresh
方法用于使用刷新令牌来刷新过期的访问权限令牌,以及用于撤销提供的访问令牌和相关刷新令牌的revoke
方法。
由于该项目不是开放源代码,所以我无法提供确切的代码,但这是一个仅使用store
方法的快速示例控制器:
use Illuminate\Http\Request;
use Psr\Http\Message\ServerRequestInterface;
use Laravel\Passport\Http\Controllers\AccessTokenController;
class MyAccessTokenController extends AccessTokenController
{
public function store(Request $request, ServerRequestInterface $tokenRequest)
{
// update the request to force your grant type, id, secret, and scopes
$request->request->add([
'grant_type' => 'password',
'client_id' => 'your-client-id',
'client_secret' => 'your-client-secret',
'scope' => 'your-desired-scopes',
]);
// generate the token
$token = $this->issueToken($tokenRequest->withParsedBody($request->request->all()));
// make sure it was successful
if ($token->status() != 200) {
// handle error
}
// return the token
return $token;
}
}
refresh
方法基本上是相同的,但是grant_type
将是refresh_token
。
revoke
方法将获取经过身份验证的用户令牌($token = $request->user()->token()
,将其撤消($token->revoke()
),然后手动浏览oauth_refresh_tokens
表以获取相关令牌( access_token_id
= $token->id
),并将revoked
字段更新为true
。
创建一些路由以使用您的自定义控制器,您就很好了(注意:revoke
路由将需要auth:api
中间件)。