siddhantfriends/laravel-proxify

一个用于SPA JavaScript API调用的Laravel代理包

v1.0 2017-03-25 23:18 UTC

This package is auto-updated.

Last update: 2024-09-20 20:41:42 UTC


README

Software License Build Status Total Downloads

摘要

简介

该包是从Cellcote/laravel-proxify分叉而来,归功于Rik Schreurs,最初由Michele Andreoli创建,但由于包已过时且不支持Laravel版本5,我对其进行了修改以使其与Laravel 5兼容。

此包将是解决Alex Bilbie提出的问题的解决方案。他说:

假设你刚刚开发了一个闪亮的Angular/Ember/Backbone之类的单页网页应用,它通过Ajax调用从你编写的API中获取所有数据。你还选择使用OAuth来保护API,并且你也使用SSL保护API端点(因为OAuth规范要求)。

POST /auth HTTP/1.1
Host: api.example.com

grant_type=password
&client_id=webapp
&client_secret=abc123
&username=admin
&password=mypassword

服务器将做出响应

{
    "access_token": "DDSHs55zpG51Mtxnt6H8vwn5fVJ230dF",
    "refresh_token": "24QmIt2aV1ubaenB2D6G0se5pFRk4W05",
    "token_type": "Bearer",
    "expires": 1415741799
}

这个问题已经存在了。

首先,在我们的应用请求中,我们发送了客户端ID和密钥,API使用这些信息来确保请求来自已知的来源。由于没有后端代码,这些信息将不得不存储在前端代码中,并且无法加密在代码中,因为在JavaScript中无法安全地进行加密。因此,我们已经有了这样一个问题,即通过使用凭证来识别网页应用——这些凭证已经泄露在公开代码中,并允许攻击者尝试进行未经授权的请求。你也不能使用引用头锁定请求,因为它们很容易被伪造。你也不能将凭证以加密的形式存储在cookie中,因为攻击者可以像获取嵌入源代码中的客户端凭证一样轻松地获取该cookie。

继续,在请求的响应中,服务器给我们提供了一个访问令牌,用于验证对API的请求,以及一个刷新令牌,用于在访问令牌过期时获取新的访问令牌。

首先,我们有这样一个问题,即访问令牌现在对攻击者可用。他现在不需要其他任何东西就可以向您的API发出请求并疯狂地抓取所有用户的私人数据并执行API允许的任何操作。服务器无法知道这不是应用发出的请求。

因此,由于这是你编写并与之通信的后端应用,你已经决定使用资源所有者密码凭证授权(也称为密码授权)来获取访问令牌。访问令牌然后可以用来验证API请求。

网页应用将向API发出Ajax请求以在捕获其凭证后对用户进行登录(以下行断行是为了提高可读性)。这是有效的OAuth 2密码授权访问令牌请求的外观

来自网页应用的合法请求

GET /resource/123 HTTP/1.1
Authorization: Bearer DDSHs55zpG51Mtxnt6H8vwn5fVJ230dF
Host: api.example.com

来自攻击者的合法请求

GET /resource/123 HTTP/1.1
Authorization: Bearer DDSHs55zpG51Mtxnt6H8vwn5fVJ230dF
Host: api.example.com

即使您的API具有短暂生命周期的访问令牌,刷新令牌也会包含在浏览器响应中,因此攻击者可以利用它在新旧令牌过期时获取新的访问令牌。

简单来说,前端无法保证安全。因此,不要这么做。

那么,如何在单页Web应用中安全地使用OAuth呢?

很简单;通过一个薄的服务器端组件来代理所有API调用。这个组件(从现在起我们称它为代理)将验证来自用户会话的Ajax请求。访问令牌和刷新令牌可以以加密形式存储在cookie中,只有代理可以解密。应用程序的客户端凭证也将硬编码到代理中,因此它们也不公开可访问。

首先,为了验证用户,Web应用将只携带用户凭证和客户端ID,向代理发送请求,而不是客户端密钥!

POST /ajax/auth HTTP/1.1
Host: example.com

grant_type=password
&username=admin
&password=mypassword
&client_id=webapp

然后,代理将添加它唯一知道的客户端密钥,并将请求转发到API。

POST /auth HTTP/1.1
Host: api.example.com

grant_type=password
&username=admin
&password=mypassword
&client_id=webapp
&client_secret=abc123

服务器将做出响应

{
    "access_token": "DDSHs55zpG51Mtxnt6H8vwn5fVJ230dF",
    "refresh_token": "24QmIt2aV1ubaenB2D6G0se5pFRk4W05",
    "token_type": "Bearer",
    "expires": 1415741799
}

代理将令牌加密存储在cookie中,并向用户返回成功消息。

当Web应用请求API端点时,它将调用代理而不是API。

GET /ajax/resource/123 HTTP/1.1
Cookie: <encrypted cookie with tokens>
Host: example.com

代理将解密cookie,向请求添加Authorization头,并将其转发到API。

GET /resource/123 HTTP/1.1
Authorization: Bearer DDSHs55zpG51Mtxnt6H8vwn5fVJ230dF
Host: api.example.com

代理将直接将响应传回浏览器。

在这种设置下,没有公开可见或纯文本的客户端凭证或令牌,这意味着攻击者不会向API发送伪造的请求。此外,由于浏览器不再直接与API通信,您可以将其从公共互联网中移除,并锁定防火墙规则,以便只允许来自Web服务器的请求。

为了保护攻击者仅窃取cookie,您可以使用CSRF保护措施。

感谢Alex Bilbie提出的问题:http://alexbilbie.com/2014/11/oauth-and-javascript

安装

运行以下composer命令

composer require siddhantfriends/laravel-proxify

设置

  1. Siddhantfriends\LaravelProxify\ApiProxyServiceProvider::class,添加到config/app.php中的服务提供者列表。
  2. 'Proxify' => Siddhantfriends\LaravelProxify\Facades\ApiProxyFacade::class,添加到config/app.php中别名列表。

配置

为了使用Api Proxy,首先发布其配置

php artisan vendor:publish --provider="Siddhantfriends\LaravelProxify\ApiProxyServiceProvider"

之后,编辑文件app/config/proxy.php以满足您的需求。

用法

routes/web.php中添加一个新的端点,例如

Route::any('proxify/{url?}', function($url) {
	return Proxify::makeRequest(Request::method(), Input::all(), $url);
})->where('url', '(.*)');

这是您的代理端点,然后您可以调用代理以获取访问令牌(client_id参数是可选的)

POST proxify/example.com/oauth/access_token HTTP/1.1
Host: example.com

&grant_type=password
[&client_id=webapp]
&username=admin
&password=mypassword

然后您可以调用受保护的资源

POST proxify/example.com/protected_resource HTTP/1.1
Host: example.com

如果access_token过期并且您有refresh_tokenApiProxy将为您调用OAuth服务器并使用新的令牌刷新access_token。之后,它将向受保护的资源发出新的调用。

这个ApiProxy包与由Luca Degasperi编写的oauth2-server-laravel配合得很好。我已经在这个包上进行了测试。

外观

ApiProxy可以通过 Facade Proxify 或通过IOC容器中的代理服务使用。可用方法如下:

/**
 * Use this method in the laravel route file
 * @param $method 
 * @param array $inputs
 * @return Response
 * @throws ProxyMissingParamException
 */
Proxify::makeRequest(Request::method(), Input::all());

许可

本软件包在MIT许可证下发布。