stratoss/phalcon2rest

具有OAuth2、JWT和速率限制的Phalcon2项目

安装: 68

依赖项: 0

建议者: 0

安全: 0

星标: 14

关注者: 8

分支: 5

开放问题: 0

类型:项目

1.0.0 2016-05-08 17:11 UTC

This package is auto-updated.

Last update: 2024-08-26 19:22:38 UTC


README

一个使用Phalcon2框架的API的基础项目

本项目是基于cmoore4的PhalconRest分支,但已修改以正确与Phalcon2协同工作。目前包含最新的phpleague的OAuth2 Server(5.x)和JSON Web Tokens (JWT)。同时实现了速率限制。

Phalcon框架是一个优秀的PHP框架,它作为语言的一个C扩展存在,这使得它非常快。除了它的速度之外,它还是一个功能强大的框架,具有优秀的文档,遵循了许多现代软件开发的最佳实践。这包括使用直接注入模式来处理类之间的服务解析、遵循PSR-0的自动加载器、MVC架构(或不是)、数据库、flatfile、redis等的缓存处理器以及许多其他附加功能。

本项目旨在建立一个使用Phalcon的基础项目,该项目使用Phalcon框架的最佳实践来实施API设计的最佳实践。

在任何主要框架中编写返回JSON的路由都很简单。我在这里所做的不仅仅是这一点,而是扩展了框架,使得使用此项目编写的API实际上是REST-like的,并实现了比简单的'echo json_encode($array)'更方便的方法和模式。

提供了健壮的错误消息、解析搜索字符串和部分响应的控制器、根据请求发送多种MIME类型的响应类,以及如何以几种方式实现身份验证的示例,以及一些用于实现常见REST-like任务的模板。

强烈建议阅读index.phpExceptions\HttpException.phpModules\V1\Controllers\RestController.php文件。

有关使用OAuth2服务器的一般信息和完整文档,请访问此处

API假设

URL结构

/v1/path1/path2?q=(search1:value1,search2:value2)&fields=(field1,field2,field3)&limit=10&offest=20&type=csv&suppress_error_codes=true

请求体

请求体将以有效的JSON提交。

字段

搜索

搜索由'q'参数确定。之后是一个括号包围的键:值对列表,由逗号分隔。

示例:q=(name:Jonhson,city:Oklahoma)

部分响应

部分响应用于仅返回记录的某些特定字段。它们由'fields'参数确定,该参数是一个字段名称列表,由逗号分隔,并括在括号中。

示例:fields=(id,name,location)

限制和偏移量

常用于分页大型结果集。偏移量是要开始的记录,限制是要返回的记录数。

示例:limit=20&offset=20将返回第21至40条记录

返回类型

覆盖任何接受头。默认为JSON。必须实现返回类型处理程序。

示例:type=csv

抑制的错误代码

某些客户端要求所有响应均为200状态码(例如Flash),即使存在应用错误。包含此参数后,应用将始终返回200状态码,客户端将负责检查响应体以确保响应有效。

例如:suppress_error_codes=true

安装

获取composer

curl -sS getcomposer.org/installer | php

安装项目和依赖项(预期phalcon2作为模块加载!)

php composer.phar create-project stratoss/phalcon2rest MyAPI --stability dev --no-interaction

用于JWT签名的公钥/私钥 示例密钥在ssl文件夹中生成,在进入生产之前必须重新生成您自己的密钥集!

openssl genrsa -out private.key 2048
openssl rsa -in private.key -pubout -out public.key

响应

所有路由控制器必须返回一个数组。此数组用于创建响应对象。

使用密码授权检索访问令牌

curl https://domain/v1/access_token --data "grant_type=password&client_id=1&client_secret=pass2&username=stan&password=pass&scope=basic"

{
    "tokenType": "Bearer",
    "expiresIn": 3600,
    "accessToken": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6ImUyY2ExYjhiM2ZlODRhM2Q2ZGFmZjZhNmFiZmQwMjJiNzQxMGYxOWNmODAzMWVkOThlNjc4YzVjN2Q3Mjc4MTk0NDFhYzMyNTc5OTM1OTIxIn0.eyJhdWQiOiIxIiwianRpIjoiZTJjYTFiOGIzZmU4NGEzZDZkYWZmNmE2YWJmZDAyMmI3NDEwZjE5Y2Y4MDMxZWQ5OGU2NzhjNWM3ZDcyNzgxOTQ0MWFjMzI1Nzk5MzU5MjEiLCJpYXQiOjE0NjI3MjkxMTgsIm5iZiI6MTQ2MjcyOTExOCwiZXhwIjoxNDYyNzMyNzE4LCJzdWIiOiIxIiwic2NvcGVzIjpbImJhc2ljIl19.VtKS7k1WiebSHiYwSrYJ9D8G90BQE81UkCNr8RI3-Ul3XaUw8kF1mMR-Q7YeO4pISUT48Gu5Sj6fTSsqF1n0Qz3axR-qcJstSwy_T0VZDNQYYdGGoXSRWabiIkLA50lbaPd8YGPLZO3IijgvyZxC3Miz7iUxxz1XOHvzHiDZP5s",
    "refreshToken": "U8PSWA\/xs\/Qw7\/\/Do76KM61+R6tUQwoex4YmnH0HjUCjzDKR22mRvSSPzkgbuR6EqjaaAp\/QWdBcYjgZj5XOyXAH9LM7C\/uBr4YeLUeTvzf7SylqIlLOuKe6aOZzD+L5ztSNdMCzjCQ1PZSWtk+hm7Ik1lUfXe1A\/09qI+hfG+p5cXHvow26ZSWz42uYaoEV5MAh+VBvR2vWx4WrAa6JJ\/6QISJRu+KTUXJmiGBbzhZiHZJ7+pyTeLiFk\/euT8aTa2tIz5WMogQDhMfzzPadOZY88jrc9mPuwxOv8DPKnysAlYHklnEQzESoP4MmKP0D9yrbLx80eE3PdEnj1hPYcpLIb8Dvc1IK+rIzg9+71HSW5XH54npY7MlLbNPDAB02bdX5SDWJhT8XwN8zqZQiLOUwBgP\/ESjW5dI7ckLzmmguqRPbd3TtNoYGultyFUCIxKH1FVNqJrxgVqJk083KXqFAzcsZw6cxgvies3djGxcPGCzyNRlxuEU8+ZoMJ0u0"
}

使用客户端凭证授权检索访问令牌

curl https://domain/v1/access_token --data "grant_type=client_credentials&client_id=1&client_secret=pass2&scope=basic"

{
    "tokenType": "Bearer",
    "expiresIn": 3600,
    "accessToken": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6ImQwYjU1YjJiMjhhYmIxOTMwODk3OTg2ZTIxN2RkMTkwZGY1YTlkYzk2NGJmZjljY2ZjMWQ0NGI4Y2RiNjU0OTAyYjMwM2M1ZDliMzY5ZWQxIn0.eyJhdWQiOiIxIiwianRpIjoiZDBiNTViMmIyOGFiYjE5MzA4OTc5ODZlMjE3ZGQxOTBkZjVhOWRjOTY0YmZmOWNjZmMxZDQ0YjhjZGI2NTQ5MDJiMzAzYzVkOWIzNjllZDEiLCJpYXQiOjE0NjI3MjE4NzcsIm5iZiI6MTQ2MjcyMTg3NywiZXhwIjoxNDYyNzI1NDc3LCJzdWIiOiIiLCJzY29wZXMiOlsiYmFzaWMiXX0.KDkaVBMBX4UelYJ4UoknjgrssEaqpQPj2MPe4ArIppIc0BYNA-5xxVWCSu8rSGKO7QAVM2XSyiux3yq8NoClgtaPlPtpZN6pcSfwGo9MSM6IwQanpd978pwPCi-tXXl4mlViph9sgxPioJ3CzCBoJTpeEtRnEm6nxMUgLnncXps"
}

交换刷新令牌以获取新的刷新令牌和访问令牌集

curl https://domain/v1/access_token --data "client_id=1&client_secret=pass2&grant_type=refresh_token&scope=basic&refresh_token=YOUR_REFRESH_TOKEN"

{
    "tokenType": "Bearer",
    "expiresIn": 3600,
    "accessToken": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6ImY0NGRiMzQ1MzE0MDdlMWY2MWU0N2NkODQ2ZjIxOTRiMjNiZWZhNzZmOWVjYWY5ZDIyMWFhYTg5MTVhMDhjOGFhMzkzYTdmMGI4NGEwNjQ1In0.eyJhdWQiOiIxIiwianRpIjoiZjQ0ZGIzNDUzMTQwN2UxZjYxZTQ3Y2Q4NDZmMjE5NGIyM2JlZmE3NmY5ZWNhZjlkMjIxYWFhODkxNWEwOGM4YWEzOTNhN2YwYjg0YTA2NDUiLCJpYXQiOjE0NjI3MjIzMjIsIm5iZiI6MTQ2MjcyMjMyMiwiZXhwIjoxNDYyNzI1OTIyLCJzdWIiOiIxIiwic2NvcGVzIjpbImJhc2ljIl19.COJ5kAWEEjZyKN_k1N0sgiLJzpEtlgT9H3oJpeicQ-bZteuABZ3sYWCgBY2FrRm6Q8ouMra9WXj38NnwYRgOusRq2H1JL-3redvTu8LitPljNLYritSAuPivOVY4e6FVjQHeuXfIl37rmKIHUXmJcUSJRh1XOqW9mXJGggiXhlI",
    "refreshToken": "muYBWN8fSzSL2UCQU0FCq7EZrJ7bPBmJxLsHOTzBSoHJn0gT+ilWyeJzvOqrlVJel4V8K7HIOQfExbKB5l0UrwzFo5UDCz5qj72wWgUn8aJWY09LfGZAs6Qsx\/INLmg6y7petQdtWspAPWlaid8OBk2w5IsqQ7kLFATHCA9fWIg3HWRrc8RPkWeBgOZ5ekRO1dGnmzDm+HLmt8hvIq7uiNDRINYYDwmYh50Ifkv8iJhxL7Pj351KPg43G9pB6L8mNfVizx71c3cofuHlTYMc4S5pt9PFBg7kbtR+qYAD5Wpm3jK204HTpx\/lYyVtEZuFou8O+7ssWlWCSXf7wogxPy9fMuRgXzONnqUn8XHDJEBOxZIIVeu7AAgsWKGJvNrLVY+81oa8BQL1MdCqxQs8vVnHgzq9+bnrjZPlhcvm\/jhWzeCx6X\/fjdneTsZXOZXLK0OpCYNkyOaT2xC5H3RI2+jRGU0HCXGJTmuBlz4Kx48fdUuy2DwF\/DL+LS2mWE6o"
}

使用隐式授权检索访问令牌

查看Modules/V1/Controllers/AuthorizeController.php。必须采取额外步骤以验证用户。

为了简单起见,假设过程从对https://domain/v1/authorize的POST请求开始,发送POST数据“response_type=token&client_id=1&scope=basic”,并成功进行客户端认证,则响应将重定向到

http://example.com/super-app#access_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6IjBjZDUxMDRkZDg2YTg0OThhZWUyZGQzOGNlYzgzYzRkMTU4MmE4YjM4ZmZjYWY3ZDQ2MjZiZTY0NWUxN2Q0MjJjZWJmMDRlNWY2YjBjNWUxIn0.eyJhdWQiOiIxIiwianRpIjoiMGNkNTEwNGRkODZhODQ5OGFlZTJkZDM4Y2VjODNjNGQxNTgyYThiMzhmZmNhZjdkNDYyNmJlNjQ1ZTE3ZDQyMmNlYmYwNGU1ZjZiMGM1ZTEiLCJpYXQiOjE0NjI3ODg0NzcsIm5iZiI6MTQ2Mjc4ODQ3NywiZXhwIjoxNDYyNzkyMDc3LCJzdWIiOiIxIiwic2NvcGVzIjpbImJhc2ljIl19.mI8E7KVG6NGxBqbZ6nVojtOXbvRCQzjnNcBSRHAbF2SyoKQlQTGAfDmGNxozfKoNh7G60Il84NKYVvwhC3S3-jLhsEgVA0UePnVnGq4V84M0yMBtLJY3puLSIOAoAGuvUjMjSlxNJnqXZ68R3oD1vi3dmA7MVeSELbii2apAyo4&token_type=bearer&expires_in=3600

使用授权码授权检索访问代码

查看Modules/V1/Controllers/AuthorizeController.php。必须采取额外步骤以验证用户。

为了简单起见,假设过程从对https://domain/v1/authorize的POST请求开始,发送POST数据“response_type=code&client_id=1&scope=basic”,并成功进行客户端认证,则响应将重定向到

http://example.com/super-app?code=ReoVHgGRnMj6IVhAUDUvunKKCi2BvGxfsJ8nGMj%2FIO2ITr6u7%2FJ7epKAIEG%2F0KZMk5Cc5GhWouG8zYHgGwzAHSztOS%2FKKp8krH5rm6e4pIkmhYvy9TCDUF1fdSo0axfZTQm1V9Ja8Ww3GN%2BeMvpmoKCXPNB8VEOs7smkTI9EGJGjVC2bS26ZJKWGuIV1UqyUKEeSiNfvhAqzeZWF2fXhGDDxmawtIPo7C3Vhs9ZW035P%2FKcugRxdT5t5MTkB%2BgRllqNGLo1DCnXvSB9E9H6KOEraMMYdqzcX4YNX8TseBrJINBJM7JUZkjFqQ176DXfnI7ULN7R%2FUJrRwWNdPMuHwQ%3D%3D

JSON

JSON是默认的响应类型。响应将类似于以下内容

curl "https://domain/v1/example?q=(id:3)&fields=(author,title,year)" \
    -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

[
    {
        "author": "Stanimir Stoyanov",
        "title": "OAuth2 with Phalcon",
        "year": "2016"
    }
]

curl "https://domain/v1/example?q=(year:2010)&fields=(author,title)" \
    -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

[
    {
        "author": "John Doe",
        "title": "Greatest book"
    },
    {
        "author": "John Doe",
        "title": "Book of books"
    }
]

可以通过包含查询参数' envelope=true '将信封包含在响应中。这将返回记录集和元信息作为正文。

curl "https://domain/v1/example?q=(year:2010)&fields=(author,title)" \
    -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

{
    "_meta": {
        "status": "SUCCESS",
        "count": 2
    },
    "records": [
        {
            "author": "John Doe",
            "title": "Greatest book"
        },
        {
            "author": "John Doe",
            "title": "Book of books"
        }
    ]
}

通常情况下,数据库字段名是snake_cased。然而,当与API一起工作时,开发人员通常更喜欢返回camelCase格式的JSON字段(许多API请求来自浏览器,在JS中)。此项目将默认将记录响应中的所有键从snake_case转换为camelCase。

可以通过设置JSONResponse的函数“convertSnakeCase(false)”来为API关闭此功能。

CSV

CSV是另一个实现的处理器。它使用第一条记录的键作为标题行,然后从数组中的每一行创建csv。可以关闭标题行以用于响应。

curl "https://domain/v1/example?q=(year:2010)&fields=(id,author,title)&type=csv" \
    -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

id,author,title
1,"John Doe","Greatest book"
2,"John Doe","Book of books"

错误

Phalcon2Rest\Exceptions\HttpException扩展PHP的本地异常。抛出此类异常将向客户端返回格式良好的JSON响应。

throw new \Phalcon2Rest\Exceptions\HttpException(
	'Could not return results in specified format',
	403,
	null
	array(
		'dev' => 'Could not understand type specified by type paramter in query string.',
		'internalCode' => 'NF1000',
		'more' => 'Type may not be implemented. Choose either "csv" or "json"'
	)
);

返回此内容

{
     "devMessage": "Could not understand type specified by type paramter in query string.",
     "error": 403,
     "errorCode": "NF1000",
     "more": "Type may not be implemented. Choose either \"csv\" or \"json\"",
     "userMessage": "Could not return results in specified format"
}

示例控制器

示例控制器在/example设置了一个路由并实现了上述所有查询参数。您可以混合和匹配这些查询中的任何一项

api.example.local/v1/example?q=(author:Stanimir Stoyanov)

api.example.local/v1/example?fields=(id,title)

api.example.local/v1/example/1?fields=(author)&envelope=false

api.example.local/v1/example?type=csv

api.example.local/v1/example?q=(year:2010)&offset=1&limit=2&type=csv&fields=(id,author)

速率限制

已实现3个速率限制器,在config/config.ini中配置

允许的访问令牌请求数量

[access_token_limits]

r1 = 5

此行设置每个IP对/access_token/authorize端点的请求限制为每5秒1个。

有多少未授权请求?

未授权请求限制

r10 = 60

此行设置每个IP对其他请求的每1分钟10个请求限制,当缺少/无效的授权头时。OPTIONS请求也在此计数。

其他所有内容

通用请求限制

r600 = 3600

所有授权消费者每小时600个请求。用户和客户端在此单独计数。

跟踪速率限制器

每个请求都返回X-Rate-Limit-*头,例如。

HTTP/1.1 200 OK

X-Rate-Limit-Limit: 600
X-Rate-Limit-Remaining: 599
X-Rate-Limit-Reset: 3600
X-Record-Count: 2
X-Status: SUCCESS
E-Tag: 6385b20e0a8a3fb0edd588d630573f00

当达到限制时

< HTTP/1.1 429 Too Many Requests
< X-Rate-Limit-Limit: 600
< X-Rate-Limit-Remaining: 0
< X-Rate-Limit-Reset: 3355
< X-Status: ERROR
< E-Tag: f22e9815ad32e143287944e727627e9c
<

{
    "errorCode": 429,
    "userMessage": "Too Many Requests",
    "devMessage": "You have reached your limit. Please try again after 3355 seconds.",
    "more": "",
    "applicationCode": "P1010"
}

CORS怎么办?

通过扩展RestController控制器,我们提供了基本的CORS策略。

将检查控制器,并将Access-Control-Allow-Methods填充为所有找到的有效方法。

 curl -I -X OPTIONS https://domain/v1/authorize

 Access-Control-Allow-Methods: POST, OPTIONS
 Access-Control-Allow-Origin: *
 Access-Control-Allow-Credentials: true
 Access-Control-Allow-Headers: origin, x-requested-with, content-type
curl -I -X OPTIONS https://domain/v1/example

Access-Control-Allow-Methods: GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: origin, x-requested-with, content-type

请注意,没有安全的方法使用OPTIONS方法授权用户,因此这些请求被速率限制器计为未授权请求。

性能优化

默认使用FileCache,这非常慢。考虑使用memcached或redis。在项目中我们使用sqlite3作为数据库。考虑使用MySQL/PostgreSQL/MongoDB或其他具有预先缓存的数据库。

还有其他什么吗?

由于这是非常个人化的,因此尚未实现访问/刷新令牌的撤销。

查看Components\Oauth2\Repositories\AccessTokenRepository.phpComponents\Oauth2\Repositories\RefreshTokenRepository.php

每个访问都有一个唯一的标识符(jti),可用于撤销。

可以使用类似JWT.io的工具轻松调试令牌。