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

使用shinymanager结合PostgreSQL哈希密码存储实现R Shiny应用登录验证时失败求助

shinymanager结合PostgreSQL哈希密码存储实现R Shiny应用登录验证时失败求助

看起来你遇到的问题主要是自定义验证函数的参数传递逻辑搞混了,还有密码验证的流程不对,另外数据库连接的管理也可以优化。我帮你一步步梳理并修正代码:

核心问题分析

  1. 参数混淆:你的my_custom_check_creds函数把登录用户输入的user和password当成了PostgreSQL的连接参数,这完全搞错了逻辑——数据库的连接参数(dbname、host、db_user、db_pwd)应该是固定的,而登录的用户名和密码是用户输入的内容。
  2. 密码验证逻辑错误:sodium::password_store每次生成的哈希值是唯一的(带随机盐值),所以你不能直接把用户输入的明文密码哈希后去数据库里匹配,必须先根据用户名查到存储的哈希值,再用password_verify验证明文密码是否和哈希匹配。
  3. 数据库连接管理不当:每次验证都新建数据库连接且不关闭,容易导致连接耗尽,应该复用你之前创建的连接池pool。

修正后的完整代码

library(RPostgreSQL)
library(shiny)
library(shinymanager)
library(DBI)
library(pool)
library(sodium)

# 数据库连接参数(注意变量名区分开登录用户和数据库用户)
dbname <- "***********"
host <- "***********"
db_user <- "***********"  # 改个名字避免和登录用户混淆
db_pwd <- "***********"

# 创建连接池(复用连接,避免频繁创建销毁)
con_pool <- dbPool(
  drv = dbDriver("PostgreSQL"),
  dbname = dbname,
  host = host,
  user = db_user,
  password = db_pwd
)

# 初始化用户表(只需要执行一次,之后可以注释掉)
if (!DBI::dbExistsTable(con_pool, c("test", "test2"))) {
  DBI::dbWriteTable(
    con_pool, 
    c("test", "test2"), 
    data.frame(
      user = c("David"),
      password = sodium::password_store("123"),
      stringsAsFactors = FALSE
    ),
    row.names = FALSE
  )
}

# 自定义验证函数:接收连接池,返回验证逻辑函数
my_custom_check_creds <- function(pool) {
  function(user_input, pwd_input) {
    # 先根据用户名查询存储的哈希密码
    req <- sqlInterpolate(
      pool, 
      sql = "SELECT password FROM test.test2 WHERE user = ({user})",
      user = user_input
    )
    res <- dbGetQuery(pool, statement = req)
    
    if (nrow(res) == 1) {
      # 取出存储的哈希密码,验证输入的明文密码是否匹配
      stored_hash <- res$password
      if (sodium::password_verify(stored_hash, pwd_input)) {
        # 验证成功,返回用户信息和result=TRUE
        list(user = user_input, result = TRUE)
      } else {
        # 密码不匹配
        list(result = FALSE)
      }
    } else {
      # 用户不存在
      list(result = FALSE)
    }
  }
}

ui <- fluidPage(
  tags$h2("My secure application"),
  verbatimTextOutput("auth_output")
)

ui <- secure_app(ui)

server <- function(input, output, session) {
  # 传入连接池到验证函数
  res_auth <- secure_server(
    check_credentials = my_custom_check_creds(con_pool)
  )
  
  output$auth_output <- renderPrint({
    reactiveValuesToList(res_auth)
  })
  
  # 关闭连接池(当应用停止时)
  onStop(function() {
    poolClose(con_pool)
  })
}

shinyApp(ui, server)

关键修正点说明

  1. 变量名区分:把数据库的连接用户名改成db_user,避免和用户输入的登录用户名混淆。
  2. 复用连接池:直接使用之前创建的con_pool进行查询,不需要每次新建连接,并且在应用停止时关闭连接池。
  3. 修正验证逻辑:先通过用户名查到存储的哈希密码,再用password_verify验证明文密码,这是sodium包正确的密码验证方式(因为哈希带随机盐,每次生成的哈希不同,不能直接对比哈希值)。
  4. 参数传递正确:my_custom_check_creds接收连接池作为参数,返回的内部函数才是处理用户输入的验证逻辑,这样secure_server调用时就能正确拿到用户输入的用户名和密码。

试试这个修正后的代码,应该就能正常完成登录验证了。

备注:内容来源于stack exchange,提问作者zakros

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.23 10:45:30