内部共享Shiny应用的用户访问追踪与IP权限管控问询
好问题!针对你的两个需求,我来一步步给你解答:
一、追踪访问Shiny应用的用户
Shiny的session对象里包含了客户端的请求信息,我们可以利用它来获取访问者的IP地址,并记录访问日志。
1. 获取客户端IP并记录日志
在你的server函数中,添加一个observe事件,当用户建立连接时自动记录IP和访问时间:
server <- function(input, output, session) { # 追踪用户访问信息 observe({ # 确保能获取到IP地址 req(session$request$REMOTE_ADDR) user_ip <- session$request$REMOTE_ADDR access_time <- format(Sys.time(), "%Y-%m-%d %H:%M:%S") # 将日志追加到文件中 log_entry <- sprintf("IP: %s, 访问时间: %s\n", user_ip, access_time) cat(log_entry, file = "shiny_access_log.txt", append = TRUE) }, once = TRUE) # 确保每个session只执行一次 # 你的原有server逻辑代码... }
2. 获取更多用户信息(可选)
如果需要更详细的信息,比如用户的浏览器类型,可以读取session$request$HTTP_USER_AGENT:
user_agent <- session$request$HTTP_USER_AGENT log_entry <- sprintf("IP: %s, 浏览器: %s, 访问时间: %s\n", user_ip, user_agent, access_time)
注意事项
如果你的Shiny应用部署在反向代理(如Nginx)后面,session$request$REMOTE_ADDR可能会返回代理服务器的IP。这时需要配置代理转发真实客户端IP,并通过session$request$HTTP_X_FORWARDED_FOR来获取:
user_ip <- session$request$HTTP_X_FORWARDED_FOR %||% session$request$REMOTE_ADDR
(注:%||%是rlang包中的函数,用于处理空值,你也可以自己定义:%||% <- function(a, b) if (is.null(a)) b else a)
二、用信号量限制特定IP的访问权限
这里分两种场景:限制特定IP的并发连接数,或者只允许指定IP访问(白名单),我都给你提供实现方案:
1. IP白名单:只允许指定IP访问
如果想让特定IP才能访问应用,可以在用户建立连接时检查IP是否在白名单内,不在则直接关闭session:
# 定义允许访问的IP列表 allowed_ips <- c("192.168.1.100", "192.168.1.101", "10.0.0.5") server <- function(input, output, session) { observe({ req(session$request$REMOTE_ADDR) user_ip <- session$request$REMOTE_ADDR if (!user_ip %in% allowed_ips) { # 提示访问被拒绝 showModal(modalDialog( title = "访问受限", "您的IP地址没有权限访问该应用", easyClose = FALSE )) # 关闭当前session session$close() } }, once = TRUE) # 你的原有server逻辑代码... }
2. 信号量限制同一IP的并发连接数
如果想限制同一个IP同时连接应用的数量(比如最多2个并发),可以用全局环境存储IP的连接计数,并配合锁来保证线程安全:
# 全局环境存储每个IP的连接数 ip_connection_counts <- new.env() # 创建锁,避免多session同时修改计数导致的冲突 ip_lock <- lock() # 自定义空值处理函数 `%||%` <- function(a, b) if (is.null(a)) b else a server <- function(input, output, session) { user_ip <- session$request$REMOTE_ADDR %||% "unknown" # 连接建立时检查并发数 observe({ lock(ip_lock) current_count <- ip_connection_counts[[user_ip]] %||% 0 if (current_count >= 2) { # 限制每个IP最多2个并发连接 showModal(modalDialog( title = "访问受限", "当前IP的并发连接数已达上限,请稍后再试", easyClose = FALSE )) session$close() } else { # 增加计数 ip_connection_counts[[user_ip]] <- current_count + 1 } unlock(ip_lock) }, once = TRUE) # 连接关闭时释放计数 onSessionEnded(function() { lock(ip_lock) current_count <- ip_connection_counts[[user_ip]] %||% 1 if (current_count > 1) { ip_connection_counts[[user_ip]] <- current_count - 1 } else { # 计数为0时移除该IP记录 rm(list = user_ip, envir = ip_connection_counts) } unlock(ip_lock) }) # 你的原有server逻辑代码... }
注意事项
- 如果你的Shiny应用是多进程部署(比如用
shiny-server的worker配置),全局环境的计数不会在进程间共享,这时需要用外部存储(比如Redis、数据库)来维护连接计数。 - 同样要注意反向代理场景下的真实IP获取问题。
内容的提问来源于stack exchange,提问作者Antarqui
相关产品推荐
相关产品推荐

