身份验证与 API 安全
添加 API 密钥验证、CORS 标头和速率限制过滤器
身份验证与 API 安全 是 CoddyKit 上的免费 R Academy 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 R Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 R Academy 课程共包含 4 节课。
API 安全为何重要
Plumber API 是一个公开的 HTTP 服务器。如果没有身份验证,任何能够访问该端口的人都可以调用您的端点。安全层包括身份验证(您是谁?)、授权(您能做什么?)、输入验证和传输安全。
将 Plumber 过滤器用作中间件
Plumber 中的过滤器会在路由处理函数之前运行。使用 pr_filter(name, function(req, res){...}) 添加中间件,以检查每个请求。调用 plumber::forward() 将请求传递给下一个过滤器或路由;如需拒绝请求,则提前返回。
# library(plumber)
# pr <- plumb('api.R')
# pr |>
# pr_filter('logger', function(req, res) {
# cat(req$REQUEST_METHOD, req$PATH_INFO, '
')
# plumber::forward() # must call to continue
# }) |>
# pr_run(port = 8000)API 密钥身份验证过滤器
服务器到服务器 API 最常见的简单身份验证模式,是在请求标头中传递静态 API 密钥。过滤器会检查每个请求的标头,如果缺少密钥或密钥错误,则返回 401。
# In api.R:
# VALID_KEY <- Sys.getenv('API_SECRET_KEY')
#
# #* @filter auth
# function(req, res) {
# key <- req$HTTP_X_API_KEY
# if (is.null(key) || key != VALID_KEY) {
# res$status <- 401L
# return(list(error = 'Unauthorized'))
# }
# plumber::forward()
# }检查 Authorization 标头
Bearer 令牌会通过 Authorization: Bearer <token> 标头传递。您可以通过 req$HTTP_AUTHORIZATION 访问它。使用 strsplit() 解析标头以提取令牌部分,然后根据您的存储进行验证。
# #* @filter bearer_auth
# function(req, res) {
# auth_header <- req$HTTP_AUTHORIZATION
# if (is.null(auth_header) || !startsWith(auth_header, 'Bearer ')) {
# res$status <- 401L
# return(list(error = 'Bearer token required'))
# }
# token <- substring(auth_header, 8) # strip 'Bearer '
# if (!token_is_valid(token)) {
# res$status <- 401L
# return(list(error = 'Invalid token'))
# }
# plumber::forward()
# }使用 #* @preempt 跳过身份验证
某些端点(例如运行状况检查和公开文档)应跳过身份验证。请使用 #* @preempt auth 为这些端点添加注解,其中 auth 与过滤器名称相匹配。Plumber 会将请求直接路由到处理函数,绕过该过滤器。
# #* Health check — no auth required
# #* @preempt auth
# #* @get /ping
# function() {
# list(status = 'ok', time = as.character(Sys.time()))
# }
#
# #* Protected endpoint — goes through auth filter
# #* @get /data
# function() {
# list(secret = 'sensitive data')
# }使用 pr_cors() 配置 CORS
如果您的 API 会被其他域中的浏览器调用,就必须启用 CORS(跨源资源共享)。使用 pr_cors() 配置允许的源、方法和标头,无需手动编写原始标头。
# library(plumber)
# pr <- plumb('api.R')
# pr |>
# pr_cors(
# origin = 'https://myapp.example.com',
# methods = c('GET', 'POST'),
# headers = c('Content-Type', 'X-API-Key'),
# credentials = TRUE
# ) |>
# pr_run(port = 8000)输入清理——绝不要信任用户输入
在查询或文件操作中使用输入之前,始终对其进行验证和清理:
- 检查类型:
is.numeric()、is.character() - 检查范围:
id >= 1 && id <= 1e9 - 拒绝意外字符:
grepl('[^a-zA-Z0-9_]', name) - 绝不要将用户字符串直接插入 SQL——请使用参数化查询
# #* @post /search
# function(req, res) {
# body <- jsonlite::fromJSON(req$postBody)
# query <- body$query
# if (!is.character(query) || nchar(query) > 200) {
# res$status <- 400L
# return(list(error = 'query must be a string <= 200 chars'))
# }
# if (grepl('[;\'"]', query)) {
# res$status <- 400L
# return(list(error = 'Invalid characters in query'))
# }
# list(results = search_db(query))
# }速率限制概念
Plumber 没有内置的速率限制器,但您可以在过滤器中使用共享环境,按 IP 跟踪请求数量,从而自行实现速率限制:
- 在 R 环境中按 IP 记录每个请求的时间戳
- 如果请求数量超过时间窗口内的限制,则返回 429 并拒绝请求
- 在生产环境中,请使用 nginx 等反向代理来执行速率限制
# request_log <- new.env()
#
# #* @filter rate_limit
# function(req, res) {
# ip <- req$REMOTE_ADDR
# now <- as.numeric(Sys.time())
# if (!exists(ip, envir = request_log)) assign(ip, c(), envir = request_log)
# times <- get(ip, envir = request_log)
# times <- times[times > now - 60] # last 60 seconds
# if (length(times) >= 60) { res$status <- 429L; return(list(error='Too Many Requests')) }
# assign(ip, c(times, now), envir = request_log)
# plumber::forward()
# }安全存储 API 密钥
绝不要将机密信息硬编码在源文件中。请将其存储在环境变量中,并在启动时使用 Sys.getenv() 读取。在本地使用 .env 文件(将其从 git 中排除),在生产环境中则通过部署环境注入机密信息。
# In .env (never commit this file):
# API_SECRET_KEY=my_super_secret_key_here
#
# In api.R:
# VALID_KEY <- Sys.getenv('API_SECRET_KEY', unset = '')
# if (nchar(VALID_KEY) == 0) stop('API_SECRET_KEY not set')
#
# Load .env locally (devtools::load_dot_env or Sys.setenv):
# readRenviron('.env')
cat('Sys.getenv reads API keys without exposing them in source
')将用户上下文附加到请求
在身份验证过滤器中验证令牌后,请将解码后的用户信息附加到 req 对象,这样下游处理函数无需重新验证即可访问这些信息。req 上的自定义字段会在整个过滤器链中保留。
# #* @filter auth
# function(req, res) {
# token <- req$HTTP_AUTHORIZATION
# user <- validate_token(token) # returns list(id=1, role='admin')
# if (is.null(user)) { res$status <- 401L; return(list(error='Unauthorized')) }
# req$user <- user # attach to request
# plumber::forward()
# }
#
# #* @get /profile
# function(req) {
# list(user_id = req$user$id, role = req$user$role)
# }使用 tryCatch 处理错误
将端点逻辑包装在 tryCatch() 中,以捕获意外错误,并返回整洁的 500 响应,而不是导致工作进程崩溃或向调用者泄露堆栈跟踪。
# #* @get /risky/<id:int>
# function(id, res) {
# tryCatch({
# result <- risky_db_call(id)
# list(data = result)
# }, error = function(e) {
# message('Error in /risky: ', conditionMessage(e))
# res$status <- 500L
# list(error = 'Internal server error')
# })
# }快速检查:@preempt 注解
#* @preempt auth 注解会对 Plumber 端点执行什么操作?
API 安全回顾
保护 Plumber API 需要采用分层防御:
pr_filter('auth', ...)——在中间件中检查每个请求req$HTTP_AUTHORIZATION/req$HTTP_X_API_KEY——读取身份验证标头#* @preempt auth——为公开端点跳过身份验证pr_cors()——配置浏览器跨源访问- 在任何数据库或文件操作之前验证输入
- 使用
Sys.getenv()获取机密信息——绝不要将密钥硬编码
常见问题解答
「身份验证与 API 安全」课时是免费的吗?
是的 — 「身份验证与 API 安全」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 R Academy 课程的其余内容,请升级到 CoddyKit PRO。 R Academy 课程共包含 4 节课。
「身份验证与 API 安全」这节课中我会学到什么?
添加 API 密钥验证、CORS 标头和速率限制过滤器 你通过在浏览器中直接运行的动手代码来练习 R Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 R Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 R Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。
「身份验证与 API 安全」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 R Academy 课中编写并运行代码吗?
能。每节 R Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。