You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何避免Plumber过滤器在返回404前拦截不存在的路由?

问题

我用Plumber开发内部API,该API由负责所有认证的公共API接入。我们通过请求头获取用户角色:100=普通用户、101=管理员、102=超级管理员。

API里有多个含敏感数据的端点,部分仅限管理员/超级管理员访问,甚至有些只对超级管理员开放。目前用过滤器和前置过滤器(preempt filter)控制端点访问权限。

当前问题:调用不存在的端点时,只有超级管理员的请求能返回404(因为其请求不会被过滤器前置拦截),普通用户和管理员的请求会被过滤器拦截,无法触发notFoundStep返回404。我们期望所有用户调用不存在的端点时都返回404。

我原本想在其他过滤器前加一个检查端点是否存在的过滤器,但过滤器里无法访问实际路由器,这个思路不可行。

请问有没有解决办法?这种多级权限控制的方式是否最优?我完全可以根据需求调整整体架构。

以下是复现问题的极简代码:

filter_authentication <- function(req, res) {
  
  if (is.na(req$HEADERS[["role"]])) {
    res$status <- 401
    return("You are unauthenticated")
  }
  
  plumber::forward()
  
}

filter_admin <- function(req, res) {
  if (req$HEADERS[["role"]] < 101) {
    res$status <- 403
    return("You lack permissions to view this")
  }
  
  plumber::forward()
}

filter_super_admin <- function(req, res) {
  if (req$HEADERS[["role"]] < 102) {
    res$status <- 403
    return("You lack permissions to view this")
  }
  
  plumber::forward()
}

#' Creates the Plumber router
#'
#' @param pr A Plumber router
make_router <- function(pr) {
  router <- pr |>
    plumber::pr_filter("filter_authentication", filter_authentication) |>
    plumber::pr_filter("filter_admin", filter_admin) |>
    plumber::pr_filter("filter_super_admin", filter_super_admin) |>
    plumber::pr_get(
      path = "/open_data",
      handler = function() {
        return("This is accessible to all users")
      },
      preempt = "filter_admin"
    ) |> 
    plumber::pr_get(
      path = "/admin_data",
      handler = function() {
        return("This is accessible to all admins")
      },
      preempt = "filter_super_admin"
    ) |> 
    plumber::pr_get(
      path = "/super_admin_data",
      handler = function() {
        return("This is accessible to all super admins")
      }
    )
  
  return(router)
}


make_req <- function(verb, path, qs = "", body = "", headers = "", args = c()) {
  req <- new.env()
  req$REQUEST_METHOD <- toupper(verb)
  req$PATH_INFO <- path
  req$QUERY_STRING <- qs
  req$rook.input <- list(
    read_lines = function() {
      body
    },
    read = function() {
      charToRaw(body)
    },
    rewind = function() {
      length(charToRaw(body))
    }
  )
  req$HEADERS <- headers
  
  req$args <- args
  
  return(req)
}

# User
pr <- make_router(plumber::pr())

req <- make_req("GET", "/admin_data", headers = c(role = 100))

res <- plumber:::PlumberResponse$new()

pr$serve(req, res)

# Admin
req <- make_req("GET", "/super_admin_data", headers = c(role = 101))

res <- plumber:::PlumberResponse$new()

pr$serve(req, res)

# Super admin
req <- make_req("GET", "/super_admin_data", headers = c(role = 102))

res <- plumber:::PlumberResponse$new()

pr$serve(req, res)

# Non-existing endpoint
req <- make_req("GET", "/does_not_exist", headers = c(role = 102))

res <- plumber:::PlumberResponse$new()

pr$serve(req, res)

req <- make_req("GET", "/does_not_exist", headers = c(role = 101))

res <- plumber:::PlumberResponse$new()
# This should also be 404
pr$serve(req, res)
解决方案

一、解决404返回问题

核心思路是先判断端点是否存在,再执行权限校验,以下是两种可行实现方式:

方法1:在全局认证过滤器中先判断路由是否存在

修改认证过滤器,先通过路由器的match方法检查请求是否匹配现有路由,不匹配直接返回404:

filter_authentication <- function(req, res) {
  # 获取当前路由器实例
  pr <- parent.frame()$pr
  # 匹配路由,判断是否存在对应端点
  match_result <- pr$match(req)
  if (is.null(match_result$route)) {
    res$status <- 404
    return("Endpoint not found")
  }
  
  # 原有认证逻辑
  if (is.na(req$HEADERS[["role"]])) {
    res$status <- 401
    return("You are unauthenticated")
  }
  
  plumber::forward()
}

这种方式下,所有请求会先经过认证过滤器,不存在的端点直接返回404,不会进入后续权限过滤器。

方法2:改用路由级过滤器替代全局权限过滤器

放弃全局绑定filter_admin和filter_super_admin,改为给需要权限的路由单独绑定对应过滤器,不存在的路由不会触发任何权限校验,直接返回404:

make_router <- function(pr) {
  router <- pr |>
    plumber::pr_filter("filter_authentication", filter_authentication) |>
    plumber::pr_get(
      path = "/open_data",
      handler = function() {
        return("This is accessible to all users")
      }
    ) |> 
    plumber::pr_get(
      path = "/admin_data",
      handler = function() {
        return("This is accessible to all admins")
      },
      # 只为该路由绑定admin权限过滤器
      filters = "filter_admin"
    ) |> 
    plumber::pr_get(
      path = "/super_admin_data",
      handler = function() {
        return("This is accessible to all super admins")
      },
      # 为该路由绑定admin+super_admin权限过滤器
      filters = c("filter_admin", "filter_super_admin")
    )
  
  return(router)
}

此方式下,不存在的路由只会经过认证过滤器,配合方法1中的路由判断逻辑,即可正常返回404。

二、优化多级权限架构

当前用前置过滤器的方式权限逻辑分散,维护成本高,推荐以下优化方案:

1. 统一权限判断逻辑

创建通用权限检查函数,集中处理角色与权限等级的校验:

check_permission <- function(role, required_level) {
  role <- as.integer(role)
  !is.na(role) && role >= required_level
}

2. 拆分专用权限过滤器

为不同权限等级创建独立过滤器,逻辑更清晰:

filter_require_admin <- function(req, res) {
  if (!check_permission(req$HEADERS[["role"]], 101)) {
    res$status <- 403
    return("You lack permissions to view this")
  }
  plumber::forward()
}

filter_require_super_admin <- function(req, res) {
  if (!check_permission(req$HEADERS[["role"]], 102)) {
    res$status <- 403
    return("You lack permissions to view this")
  }
  plumber::forward()
}

3. 路由绑定简化

路由只需声明所需权限过滤器即可,无需依赖preempt参数:

make_router <- function(pr) {
  router <- pr |>
    plumber::pr_filter("filter_authentication", filter_authentication) |>
    plumber::pr_get("/open_data", function() "This is accessible to all users") |>
    plumber::pr_get(
      "/admin_data",
      function() "This is accessible to all admins",
      filters = "filter_require_admin"
    ) |>
    plumber::pr_get(
      "/super_admin_data",
      function() "This is accessible to all super admins",
      filters = c("filter_require_admin", "filter_require_super_admin")
    )
  
  return(router)
}

4. 自定义统一404响应

通过pr_set_error自定义404返回格式,让API响应更规范:

pr |> plumber::pr_set_error(404, function(req, res) {
  res$status <- 404
  list(error = "请求的端点不存在", code = 404)
})

内容的提问来源于stack exchange,提问作者alethor

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 20:44:51