
使用这些基本 REST API 最佳实践构建出色的 API
Sinatra是开发Web应用程序和API的热门Ruby框架之一,被广泛应用于超过200,000个应用程序中。Sinatra作为一种领域特定语言(DSL),允许开发者在Ruby中直接创建Web应用程序和API。与典型的Model-View-Controller框架不同,Sinatra将特定的URL直接映射到相关的Ruby代码,并将代码的输出作为响应返回。
在这篇博文中,您将学习如何构建Sinatra API,并使用Auth0来保护其端点。我们将一起构建Sinatra Songs API,这是一个歌曲CRUD API。值得一提的是,这个API的命名灵感来源于Frank Sinatra,因为如果没有Frank Sinatra,也就没有 Sinatra API的命名由来!😉
对于此项目,您将使用以下版本:
您将从零开始构建Sinatra Songs API。如果遇到困难,您可以查阅项目存储库。该存储库包含两个分支:main分支提供了歌曲CRUD API的代码,而add-authorization分支则包含了连接Auth0并保护终端节点的代码。
首先,在终端中创建一个新项目。新建一个名为“sinatra-auth0-songs-api”的文件夹,并将其设置为当前工作目录。
接下来,安装 Sinatra。为此,您需要在项目的根目录中创建一个名为“Gemfile”的文件,并在其中列出所有依赖项。
# Gemfile
# frozen_string_literal: true
source 'https://rubygems.org'
ruby File.read('.ruby-version').strip
gem 'sinatra', '~> 3.0', '>= 3.0.2'
gem 'puma'
您可以在.ruby-version
文件中指定Ruby版本,这是我个人偏好的常见做法。正如Bundler文档所阐释的,如果您依赖Ruby虚拟机(VM)中的特定功能,这样做能让您的应用程序在遇到不兼容时更快地失败。如此一来,部署服务器上的Ruby VM将与本地VM保持一致。为此,您需要在.ruby-version
文件中填入您将使用的Ruby版本号。
3.1.2
最后,通过运行以下命令来安装 Gem:
bundle install
就这样,Sinatra 🎩 被安装了!您还安装了 puma 作为 Web 服务器。
让我们创建一个类来表示一首歌。在该目录中新建一个名为“models”的文件夹,并在其中创建一个新文件“song.rb”。
使用以下代码填充“song.rb”文件。
# models/song.rb
# frozen_string_literal: true
# Class to represent a Song
class Song
attr_accessor :id, :name, :url
def initialize(id, name, url)
@id = id
@name = name
@url = url
end
def to_json(*a)
{
'id' => id,
'name' => name,
'url' => url
}.to_json(*a)
end
end
您正在定义一个名为Song
的类,该类具有三个属性:id
、name
和url
。此外,您还将为Song
类实现一个更专业的to_json
方法,以便在控制器中将歌曲渲染为JSON时,该方法可以作为序列化程序使用。
到目前为止,您主要使用了Ruby语言;现在,是时候亲身体验一下Sinatra框架了。
请在终端中创建一个新文件,命名为api.rb
,并将以下内容添加到该文件中,作为API的基本框架。
# api.rb
# frozen_string_literal: true
require 'sinatra'
require 'json'
before do
content_type 'application/json'
end
get '/songs' do
return {todo: :implementation}.to_json
end
get '/songs/:id' do
return {todo: :implementation}.to_json
end
post '/songs' do
return {todo: :implementation}.to_json
end
put '/songs/:id' do
return {todo: :implementation}.to_json
end
delete '/songs/:id' do
return {todo: :implementation}.to_json
end
让我们分解一下“api.rb”文件中的内容。
首先,您需要加载“sinatra”和“json”这两个gem。
require 'sinatra'
require 'json'
与 Rails 不同,在 Sinatra 中,您必须自行加载所有内容。这可能很棒,因为它通过迫使你明确正在使用的东西,从而消除了 Rails 的所有魔力🔮。
接下来,您将定义一个过滤器:before。
before do
content_type 'application/json'
end
正如Sinatra文档所阐述的,before过滤器会在每次请求处理之前进行评估。
在此情境下,您需要将Content-Type
头部设置为application/json
,以此告知客户端,来自此服务器的所有响应均采用JSON格式。
接下来,我们将着手定义路由。
get '/songs' do
# ...
end
get '/songs/:id' do
# ...
end
post '/songs' do
# ...
end
put '/songs/:id' do
# ...
end
delete '/songs/:id' do
# ...
end
这些路由代表了您将要实现的CRUD(创建、读取、更新、删除)操作。
嗯,虽然严格来说它更像是CRRUD(因为读取操作有两个),但您已经明白了主要意思。🫠
有了这个API框架,您就可以启动服务器并测试各个端点了。
要从终端运行服务器:
ruby api.rb
服务器运行后,您的终端将如下所示:
➜ sinatra-auth0-songs-api git:(main) ✗ ruby api.rb
== Sinatra (v3.0.2) has taken the stage on 4567 for development with backup from Puma
Puma starting in single mode...
* Puma version: 6.0.0 (ruby 3.1.2-p20) ("Sunflower")
* Min threads: 0
* Max threads: 5
* Environment: development
* PID: 98050
* Listening on http://127.0.0.1:4567
* Listening on http://[::1]:4567
Use Ctrl-C to stop
现在,您可以访问终端节点。我创建了一个 POSTMAN Collection,因此您可以自行测试这些终端节点。您也可以像这样使用:http://localhost:4567(通过 curl 命令)。
➜ curl -v http://localhost:4567/songs
{"todo":"implementation"}%
为了向API中填充一些数据,您可以使用songs.json
文件。这个文件是通过LastFM API获取的数据填充的,包含了Frank Sinatra的Top 10 Tracks,并且格式是LastFM的一个简化版本。您可以下载这个文件来使用。
{
"id": 1,
"name": "My Way",
"url": "https://www.last.fm/music/Frank+Sinatra/_/My+Way"
}
让我们实现一个帮助程序,它将在 Sinatra API 启动后负责从 songs.json
文件中读取数据。
为此,请创建一个名为 helpers
的新文件夹,并在其中添加一个名为 songs_helper.rb
的文件。
# helpers/songs_helper.rb
# frozen_string_literal: true
require_relative '../models/song'
require 'json'
# Class to read songs from a JSON file
class SongsHelper
def self.songs
filepath = File.join(File.dirname(__FILE__), '../songs.json')
file = File.read(filepath)
data = JSON.parse(file)['songs']
data.map do |song|
Song.new(song['id'], song['name'], song['url'])
end
end
end
该类实现了一个方法,用于读取文件并将其内容映射到对象数组中。SongsHelper
类读取 songs.json
文件中的歌曲。
接下来,在你的 api.rb
文件中,你可以调用 SongsHelper
类的 songs
方法来加载歌曲。
# api.rb
# frozen_string_literal: true
require 'sinatra'
require 'json'
# 👇 new code
require_relative 'helpers/songs_helper'
songs ||= SongsHelper.songs
# 👆 new code
# existing code ...
您正在导入文件,并调用其中的方法将数据存储在名为songs
的变量中。这个变量是通过helpers/songs_helper
模块中的songssongs
方法获取的。
请注意,在实际的应用程序中,您会使用一个合适的数据库来存储数据,并且无需执行从文件导入数据的这一步骤。但为了简化本教程,我们没有使用数据库,而是直接处理来自songs.json
文件的数据。
现在,您可以使用这个songs
变量来管理GET请求,例如获取所有歌曲的请求可以命名为GET /songs
。
# api.rb
# frozen_string_literal: true
require 'sinatra'
require 'json'
require_relative 'helpers/songs_helper'
songs ||= SongsHelper.songs
before do
content_type 'application/json'
end
# 👇 new code
get '/songs' do
return songs.to_json
end
# 👆 new code
# existing code ...
该请求现在将使用 curl
命令通过 GET 方法检索一组歌曲,访问的 URL 是 /songs
。
➜ curl http://localhost:4567/songs
[
{"id":1,"name":"My Way","url":"https://www.last.fm/music/Frank+Sinatra/_/My+Way"},
{"id":2,"name":"Strangers in the Night","url":"https://www.last.fm/music/Frank+Sinatra/_/Strangers+in+the+Night"},
{"id":3,"name":"Fly Me to the Moon","url":"https://www.last.fm/music/Frank+Sinatra/_/Fly+Me+to+the+Moon"},
{"id":4,"name":"That's Life","url":"https://www.last.fm/music/Frank+Sinatra/_/That%27s+Life"},
{"id":5,"name":"I've Got You Under My Skin","url":"https://www.last.fm/music/Frank+Sinatra/_/I%27ve+Got+You+Under+My+Skin"},
{"id":6,"name":"Come Fly With Me","url":"https://www.last.fm/music/Frank+Sinatra/_/Come+Fly+With+Me"},
{"id":7,"name":"The Way You Look Tonight","url":"https://www.last.fm/music/Frank+Sinatra/_/The+Way+You+Look+Tonight"},
{"id":8,"name":"Fly Me to the Moon (In Other Words)","url":"https://www.last.fm/music/Frank+Sinatra/_/Fly+Me+to+the+Moon+(In+Other+Words)"},
{"id":9,"name":"Theme from New York, New York","url":"https://www.last.fm/music/Frank+Sinatra/_/Theme+from+New+York,+New+York"},
{"id":10,"name":"Jingle Bells","url":"https://www.last.fm/music/Frank+Sinatra/_/Jingle+Bells"}
]%
现在,让我们来实现歌曲详细信息的路由。为此,我们需要引入 helpers 的概念,并创建一个新的 helper。路由将是 /songs/:id
。
接下来,在您的 api.rb
文件中,添加相应的内容。
# frozen_string_literal: true
require 'sinatra'
require 'json'
require_relative 'helpers/songs_helper'
songs ||= SongsHelper.songs
# 👇 new code
helpers do
def id_param
halt 400, { message: 'Bad Request' }.to_json if params['id'].to_i < 1
params['id'].to_i
end
end
# 👆 new code
# existing code ...
在 Sinatra 中,帮助程序(helpers)指的是那些在路由处理程序和模板中均可使用的顶级方法。
在本例中,您将定义一个帮助程序方法。这个方法首先会检查一个哈希值是否存在,这个哈希值是Sinatra在路由块中自动为您提供的,它包含了来自请求的相关数据。这个特定的哈希键我们称之为id_param
。
在id_param
方法中,如果对应的值不是正数,则会返回一个错误提示。如果它是一个有效的正数值,则方法会返回这个值并将其转换为整数类型。您将在所有需要用到id
参数的路由中使用这个方法,例如:在GET、PUT和DELETE请求处理/songs/:id
这个路由时。
现在,回到api.rb
文件,您可以通过使用这个helper
方法来实现获取、更新和删除歌曲详细信息的路由,如下所示(注意,这里我们假设id_param
方法已经在文件中被正确定义):
/songs/:id
,使用id_param
来获取并验证歌曲ID。/songs/:id
(原文中写的是“放”,这里应该是PUT的误写),同样使用id_param
。/songs/:id
,也是使用id_param
。在这些路由处理程序中,如果id_param
方法检测到无效的ID,可以相应地返回“Bad Request”错误。
# existing code ...
get '/songs' do
return songs.to_json
end
get '/songs/:id' do
# 👇 new code
song = songs.find { |s| s.id == id_param }
halt 404, { message: 'Song Not Found' }.to_json unless song
return song.to_json
# 👆 new code
end
# existing code ...
您正在使用 Ruby 的 Enumerable#find
方法在数组中查找具有通过 params
发送的 ID 的歌曲。如果未找到对应的歌曲,则返回 404 NOT FOUND 错误。否则,您将以 JSON 格式返回该歌曲的信息。
让我们现在使用:curl
➜ curl http://localhost:4567/songs/1
{"id":1,"name":"My Way","url":"https://www.last.fm/music/Frank+Sinatra/_/My+Way"}%
此时,您已经实现了 Songs API 中的两个读取路由。接下来,是时候实现创建、更新和删除路由了。
让我们从创建(create)路由开始。您可以通过提供一个包含歌曲名称的 JSON 对象来发起 POST 请求。该 POST 请求的 URL 可以通过 curl 命令来发起,如下所示:
curl -X POST 'http://localhost:4567/songs' \
-H 'Content-Type: application/json' \
-d '{
"name": "A new song",
"url": "http://example.com"
}'
在更新歌曲信息时,您需要在请求正文中传递name
和url
参数,并确保它们具有正确的JSON格式。这是实现相关帮助程序的一个关键提示。
接下来,我们将实现一个新的帮助程序,命名为json_params
,它的作用是检查请求的正文是否符合JSON格式要求。
现在,请在api.rb
文件中添加实现json_params
帮助程序的代码。
# api.rb
# frozen_string_literal: true
require 'sinatra'
require 'json'
require_relative 'helpers/songs_helper'
songs ||= SongsHelper.songs
helpers do
# existing code ...
# 👇 new code
def json_params
request.body.rewind
@json_params ||= JSON.parse(request.body.read).transform_keys(&:to_sym)
rescue JSON::ParserError
halt 400, { message: 'Invalid JSON body' }.to_json
end
# 👆 new code
# existing code ...
end
# existing code ...
该方法从 request.body
中读取 JSON 数据。如果在使用 JSON.parse
解析时遇到 JSON::ParseError
,则表示请求体不是有效的 JSON 格式,此时该方法将返回 400 Bad Request 错误。
此外,您还应该验证请求体中的参数是否仅包含必需的参数:name
和 url
。为了实现这一验证逻辑,我们可以创建一个新的 helper 方法。
# api.rb
# existing code ...
helpers do
# existing code ...
def json_params
request.body.rewind
@json_params ||= JSON.parse(request.body.read).transform_keys(&:to_sym)
rescue JSON::ParserError
halt 400, { message: 'Invalid JSON body' }.to_json
end
# 👇 new code
def require_params!
json_params
attrs = %i[name url]
halt(400, { message: 'Missing parameters' }.to_json) if (attrs & @json_params.keys).empty?
end
# 👆 new code
# existing code ...
end
# existing code ...
该方法将是您在路由中使用的主要方法。首先,它调用 require_params!
以初始化实例变量 @json_params
,并在上下文中使其可用。然后,该方法验证 @json_params
是否包含 name
键且不包含其他未预期的参数。如果验证失败,它将返回 400 Bad Request 状态码。
require_params!
方法会检查 json_params
(通过参数传递或从请求体中解析得到)是否满足要求。
只有在创建和更新歌曲时,才需要 name
参数。为此,您可以创建一个 before
过滤器来完成此验证操作。
接下来,在 api.rb
中,添加以下内容来定义这个 before
过滤器:
# frozen_string_literal: true
require 'sinatra'
require 'json'
require_relative 'helpers/songs_helper'
songs ||= SongsHelper.songs
# 👇 new code
set :method do |*methods|
methods = methods.map { |m| m.to_s.upcase }
condition { methods.include?(request.request_method) }
end
# 👆 new code
helpers do
# ... existing code
end
before do
content_type 'application/json'
end
# 👇 new code
before method: %i[post put] do
require_params!
end
# 👆 new code
让我们进一步分析并明确您的需求。您提到了两个新增的元素:一个是变量(虽然您写的是“a”,但根据上下文,我假设您可能想指的是某个具体的变量或设置项,但此处我们将其忽略,因为重点在于后续内容),另一个是您已经了解的before
过滤器。
set
方法是Sinatra框架提供的一个功能,它允许您为应用程序设置属性。这个方法接受两个参数:设置名称和对应的值。
在您的情况中,您打算使用set
方法来标识HTTP方法。具体做法是,您会设定一个名称(,并将一个包含HTTP方法名称的数组作为参数传递给它。随后,您会利用条件语句来确保before
过滤器仅在特定条件下执行,也就是当HTTP方法是POST
或PUT
时。
在before
过滤器内部,您会传递一个HTTP方法的符号列表,以指明这段代码应在哪些HTTP请求中执行。紧接着,您可能会调用一个自定义的方法,用于确保请求中包含了必要的参数。
现在,我们来关注api.rb
文件中创建和更新歌曲的代码实现。
首先,为了创建新歌曲,您需要处理POST /songs
请求。
# existing code ...
before method: %i[post put] do
require_params!
end
# existing code ...
# 👇 new code
post '/songs' do
create_params = @json_params.slice(:name, :url)
if create_params.keys.sort == %i[name url]
new_song = { id: songs.size + 1, name: @json_params[:name], url: @json_params[:url] }
else
halt(400, { message: 'Missing parameters' }.to_json)
end
songs.push(new_song)
return new_song.to_json
end
# 👆new code
# existing code ...
end
# existing code ...
这条路由旨在简化歌曲创建过程。首先,它会验证 params
哈希中是否存在 name
和 url
这两个键;请注意,之前的过滤器已经确保了这两个参数是传递的唯一参数。如果这两个参数都存在,那么可以创建一首新歌曲。这里需要说明的是,您只是简单地将一个标识符递增1,并将新歌曲对象推送到一个数组中;在实际应用中,您通常会在数据库中创建新记录。如果缺少 name
或 url
参数中的任何一个,则路由会返回 400 Bad Request 错误。
接下来,我们继续为 update
路由添加代码。
# api.rb
# existing code ...
# 👇 new code
put '/songs/:id' do
song = songs.find { |s| s.id == id_param }
halt 404, { message: 'Song Not Found' }.to_json unless song
song.name = @json_params[:name] if @json_params.keys.include? :name
song.url = @json_params[:url] if @json_params.keys.include? :url
return song.to_json
end
# 👆new code
# existing code ...
end
当请求更新歌曲时,您的代码会尝试使用 id_param
(从请求中提取的歌曲ID)在歌曲数组中查找该歌曲。如果未找到对应的歌曲,则返回 404 Not Found 错误。如果找到了歌曲,则仅更新请求正文中发送的字段,并以 JSON 格式返回更新后的歌曲信息。
最后,还有删除歌曲的路由需要实现。让我们将其添加到 api.rb
文件中:实现一个 DELETE 请求处理,路径为 /songs/:id
。
# api.rb
# existing code ...
# 👇 new code
delete '/songs/:id' do
song = songs.find { |s| s.id == id_param }
halt 404, { message: 'Song Not Found' }.to_json unless song
song = songs.delete(song)
return song.to_json
end
# 👆new code
# existing code ...
end
delete song 方法与 update song 方法非常相似,但它调用 Array#delete 函数并以 JSON 格式呈现歌曲。
您的 Songs API 已完成!但未得到保障😩。此时,您的代码必须与存储库的 main 分支上的代码非常相似。
到目前为止,您已经成功构建了一个CRUD Songs API,但目前该API的终端节点对所有人都是开放的。为了确保只有经过授权的用户才能执行创建、更新和删除歌曲的操作,您计划使用Auth0作为身份访问管理(IAM)提供商。
接下来,您将实现已在add-authorization
分支中完成的代码,该代码将作为您操作的指南。
要将您的Sinatra API与Auth0进行连接,您需要先在Auth0控制面板的API部分创建一个新的Auth0 API。具体操作步骤如下:
https://sinatra-auth0-songs-api
在设置 Sinatra API 时,您需要复制一个值(此处未具体说明是哪个值,可能是指某个配置参数或令牌)。此外,您还需要获取您的 Auth0 域。除非您已配置自定义域,否则此值通常为您的租户名称和区域组成的 URL,形如 https://[TENANT_NAME].[REGION].auth0.com
。如果您不确定此值的具体内容,可以打开 API 设置中的“Test”选项卡,并查看“Asking Auth0 for tokens from my application”部分下的代码示例,其中会包含这个参数的示例值:identifier
创建 API 后,您可以前往命令行并开始安装依赖项。
您将需要一些 gem,因此让我们继续将它们添加到Gemfile
:
gem 'dotenv'
gem 'jwt'
接下来,在终端中运行:
bundle install
您正在安装 dotenv
gem,以便从本地文件中读取环境变量。您可以使用 .env.example
文件作为模板,并将其内容复制到项目根目录中的一个名为 .env
的文件中。
请记住上一步,您必须保存您的 Auth0 域和标识符。嗯,这就是您可以在 .env
文件中使用它们的地方。
将 AUTH0_DOMAIN
和 AUTH0_IDENTIFIER
粘贴到您的 .env
文件中。
您还安装了 JWT gem,它是 JWT 标准的 Ruby 实现,稍后将帮助您验证 JWT 令牌,稍后您将了解有关这些令牌的更多信息。
为了保护 API 的终端节点,您将使用所谓的基于令牌的授权。基本上,您的 Sinatra Songs API 将接收一个访问令牌;传递的访问令牌会告知 API,令牌的持有者已被授权访问 API 并执行范围指定的特定操作。
最后,您的 API 将通过确保访问令牌具有正确的结构,并且它是由正确的授权服务器(在本例中为 Auth0)颁发的,来验证访问令牌。
为了验证访问令牌,您需要首先创建一个新的类来专门处理这一过程。
接下来,请在您的文件夹中新建一个文件,命名为helpers/auth0_client_helper.rb
,并向其中添加相应的代码。
# helpers/auth0_client_helper.rb
# frozen_string_literal: true
require 'jwt'
require 'net/http'
# AuthoClient helper class to validate JWT access token
class Auth0ClientHelper
# Auth0 Client Objects
Error = Struct.new(:message, :status)
Response = Struct.new(:decoded_token, :error)
# Helper Functions
def self.domain_url
"https://#{ENV['AUTH0_DOMAIN']}/"
end
def self.decode_token(token, jwks_hash)
JWT.decode(token, nil, true, {
algorithm: 'RS256',
iss: domain_url,
verify_iss: true,
aud: (ENV['AUTH0_AUDIENCE']).to_s,
verify_aud: true,
jwks: { keys: jwks_hash[:keys] }
})
end
def self.get_jwks
jwks_uri = URI("#{domain_url}.well-known/jwks.json")
Net::HTTP.get_response jwks_uri
end
# Token Validation
def self.validate_token(token)
jwks_response = get_jwks
unless jwks_response.is_a? Net::HTTPSuccess
error = Error.new(message: 'Unable to verify credentials', status: :internal_server_error)
return Response.new(nil, error)
end
jwks_hash = JSON.parse(jwks_response.body).transform_keys(&:to_sym)
decoded_token = decode_token(token, jwks_hash)
Response.new(decoded_token, nil)
rescue JWT::VerificationError, JWT::DecodeError
error = Error.new('Bad credentials', 401)
Response.new(nil, error)
end
end
这个类中包含了一些内容,这些内容在《Rails API 授权示例指南》中得到了详尽的解释,特别是在“Auth0Client 类在后台的运作机制”这一章节下的“验证 JSON Web Token(JWT)”部分。当然,我已经做了一些调整,将原本适用于 Rails 的代码修改为适用于 Sinatra,但核心思想依然保持不变。
为了深入了解这些安全相关的概念,你可以参考《Rails 认证示例指南》和《Rails 授权示例指南》(后者还介绍了基于角色的访问控制(RBAC)的概念)。
不过,现在让我们聚焦于这个类中的一个核心方法:validate_token
方法。
def self.validate_token(token)
jwks_response = get_jwks
unless jwks_response.is_a? Net::HTTPSuccess
error = Error.new(message: 'Unable to verify credentials', status: :internal_server_error)
return Response.new(nil, error)
end
jwks_hash = JSON.parse(jwks_response.body).transform_keys(&:to_sym)
decoded_token = decode_token(token, jwks_hash)
Response.new(decoded_token, nil)
rescue JWT::VerificationError, JWT::DecodeError
error = Error.new('Bad credentials', 401)
Response.new(nil, error)
end
让我们分解一下validate_token
方法的作用:
validate_token
方法,该方法使用 JWT Gem 对访问令牌进行解码,如下所示:JWT.decode(token, nil, true, {
algorithm: 'RS256',
iss: domain_url,
verify_iss: true,
aud: (ENV['AUTH0_AUDIENCE']).to_s,
verify_aud: true,
jwks: { keys: jwks_hash[:keys] }
})
这会从环境变量中获取 u
,并在值中设置 u
。最后,在参数中传递您之前创建的 domain_url
、AUTH0_DOMAIN
、AUTH0_AUDIENCE
和 jwks_hash
、jwks
。
要了解有关参数的更多信息,可以参考 Rails API Authorization By Example Developer Guide 中的“Auth0Client 类在后台做什么?”以及 JWT.decode
。
创建帮助程序 authorize!
,该类已经在执行大部分工作来验证访问令牌。现在,您需要在要保护的终端节点中实际调用它。使用 Auth0ClientHelper
,类似于您之前使用它的方式。
转到您的 api.rb
文件并添加相关代码。
# api.rb
# existing code ...
helpers do
# existing code ...
# 👇 new code
def authorize!
token = token_from_request
validation_response = Auth0ClientHelper.validate_token(token)
return unless (error = validation_response.error)
halt error.status, { message: error.message }.to_json
end
def token_from_request
authorization_header_elements = request.env['HTTP_AUTHORIZATION']&.split
halt 401, { message: 'Requires authentication' }.to_json unless authorization_header_elements
unless authorization_header_elements.length == 2
halt 401, { message: 'Authorization header value must follow this format: Bearer access-token' }.to_json
end
scheme, token = authorization_header_elements
halt 402, { message: 'Bad credentials' }.to_json unless scheme.downcase == 'bearer'
token
end
# 👆 new code
end
# existing code ...
好吧,您实际上定义了两个帮助程序,但这两个帮助程序是互为辅助的。token_from_request
帮助程序通过调用另一个方法从请求中提取令牌。该方法会检查 HTTP 标头,并对其进行解析以验证其格式是否正确。而 authorize!
帮助程序则依赖于 token_from_request
来获取令牌。
使用 Bearer 模式时,格式正确的 Authorization
标头如下所示:
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.NHVaYe26MbtOYhSKkoKYdFVomg4i8ZJd8_-RU8VNbftc4TSMb4bXP3l3YlNWACwyXPGffz5aXHc6lty1Y2t4SWRqGteragsVdZufDn5BlnJl9pdR_kdVFUsra2rWKEofkZeIC4yWytE58sMIihvo9H1ScmmVwBcQP6XETqYd0aSHp1gOa9RdUPDvoXQ5oqygTqVtxaDr6wUFKrKItgBMzWIdNZ6y7O9E0DhEPTbE9rfBo6KTFsHAZnMg4k68CDp2woYIaXbmYTWcvbzIuHO7_37GT79XdIwkm95QJ7hYC9RiwrV7mesbY4PAahERJawntho0my942XheVLmGwLMBkQ
然后,该方法会验证请求标头中是否存在 Authorization
,令牌是否存在,以及它是否具有正确的格式(例如,以 “Bearer ” 开头)。如果任何一项检查失败,它将返回 401 Unauthorized 状态码。
从请求标头中检索到令牌后,帮助程序(我们假设它是一个辅助方法或类)将调用 Auth0ClientHelper
类中的 authorize!
方法来验证令牌。如果令牌经过验证且没有错误,则 authorize!
方法将正常完成其执行流程。如果在验证过程中出现任何错误,authorize!
方法会返回一个包含正确状态码和错误消息的 Error
对象。
要使用这个帮助程序保护您的 API 端点,您需要在任何客户端尝试调用这些端点之前调用 authorize!
方法。
保护终端节点的最后一步是实现一个过滤器,该过滤器会在请求到达端点之前被触发。在 Sinatra 中,这通常是通过 before
过滤器来实现的。
在您的 api.rb
文件中,您可能已经有一个可以重复使用的过滤器,因此让我们修改它,以便在调用任何需要保护的端点之前都执行 authorize!
方法。
# api.rb
# existing code ...
# old code
# before method: %i[post put] do
# require_params!
# end
# old code
# 👇 new code
before method: %i[post put delete] do
require_params!
authorize!
end
# 👆 new code
# existing code ...
首先,您将该方法添加到过滤器中,因为您希望只有授权用户能够创建、更新和删除歌曲。delete
before
接着,在过滤器中调用将执行授权验证的帮助程序 authorize!
。
就是这样!现在,您已经设置好了授权验证,可以使用 curl
命令来测试相关的终端节点。
curl -X POST 'http://localhost:4567/songs' \
-H 'Authorization: Bearer YOUR_TOKEN' \
-d '{
"name": "A new song"
"url": "http://example.com"
}'
将占位符替换为有效的访问令牌后,此请求的结果将如下所示:
{"id":11,"name":"A new song","url":"http://example.com"}
要获取 API 的有效访问令牌,请按照将 Sinatra API 与 Auth0 连接部分中显示的步骤进行操作。
在这篇博文中,您了解了 Ruby 框架 Sinatra,并学习了如何创建一个基本的 CRUD API 来管理 Frank Sinatra 的歌曲。
您首先通过控制面板创建了一个新的 Auth0 账户和一个新的 API。接着,您使用了 JWT gem 来验证由 Auth0 颁发的访问令牌。最后,您通过实施基于令牌的授权(采用 Bearer 方案)保护了用于创建、更新和删除歌曲的 API 终端节点。
我希望您喜欢这篇文章。您是否使用过其他的 Ruby 框架呢?请在评论中告诉我!
感谢阅读!
原文链接:https://auth0.com/blog/add-authorization-to-sinatra-api-using-auth0/