Laravel Passport授予第一方应用程序的流程

时间:2018-10-26 14:04:32

标签: php laravel security oauth-2.0 laravel-passport

我正在使用Laravel Passport来将API的某些部分授予第三方应用访问权限。

但是,我也通过自己的第一方本机Android应用程序使用自己的API。 因此,在这种情况下,我在整个互联网上寻找了最佳实践,但是却无法得出结论。

这是我发现的可能性:

可能性#01

我可以关注User Credential Password Grant Flow
在这种情况下,我需要将client_secretclient_id传递到授权服务器。为了确保它们的安全性,我无法在移动应用程序的源代码中编写它们(APK可反编译...)。

所以,我有2个选择。

可能性#01-选择A

通过我自己的服务器代理并在调用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);

可能性#01-选择B

使用中间件调用oauth端点时注入机密:

class InjectPasswordGrantSecret
{
    public function handle($request, Closure $next)
    {
        $request->request->add([
            'client_id' => 1,
            'client_secret' => 'myownclientsecretishere'
        ]);
        return $next($request);
    }
}

这些是有效的示例,但在资源方面也很贪婪。 我尝试在本地计算机上使用 Apache基准测试,并且每秒收到9个请求。

可能性#02

我可以关注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将是一个很好的解决方案...

我希望有人能帮助我解决这个问题,并解释说什么是使它在生产服务器上安全且[不缓慢地]工作的好解决方案。

3 个答案:

答案 0 :(得分:2)

OAuth2是一种标准,但是人们完成的实现有时可能会有所不同,但是在大多数情况下,最终它不会改变任何内容。
例如,使用oauth/tokenmyserver/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/ enter image description here

它说

<块引用>

我们强烈建议您使用授权码流程 密码授予有几个原因。 我们取消了密码授予选项。

然后,它在图中清楚地说明您应该使用

<块引用>

使用 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中间件)。