使用shinymanager结合PostgreSQL哈希密码存储实现R Shiny应用登录验证时失败求助
shinymanager结合PostgreSQL哈希密码存储实现R Shiny应用登录验证时失败求助
看起来你遇到的问题主要是自定义验证函数的参数传递逻辑搞混了,还有密码验证的流程不对,另外数据库连接的管理也可以优化。我帮你一步步梳理并修正代码:
核心问题分析
- 参数混淆:你的
my_custom_check_creds函数把登录用户输入的user和password当成了PostgreSQL的连接参数,这完全搞错了逻辑——数据库的连接参数(dbname、host、db_user、db_pwd)应该是固定的,而登录的用户名和密码是用户输入的内容。 - 密码验证逻辑错误:
sodium::password_store每次生成的哈希值是唯一的(带随机盐值),所以你不能直接把用户输入的明文密码哈希后去数据库里匹配,必须先根据用户名查到存储的哈希值,再用password_verify验证明文密码是否和哈希匹配。 - 数据库连接管理不当:每次验证都新建数据库连接且不关闭,容易导致连接耗尽,应该复用你之前创建的连接池
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)
关键修正点说明
- 变量名区分:把数据库的连接用户名改成
db_user,避免和用户输入的登录用户名混淆。 - 复用连接池:直接使用之前创建的
con_pool进行查询,不需要每次新建连接,并且在应用停止时关闭连接池。 - 修正验证逻辑:先通过用户名查到存储的哈希密码,再用
password_verify验证明文密码,这是sodium包正确的密码验证方式(因为哈希带随机盐,每次生成的哈希不同,不能直接对比哈希值)。 - 参数传递正确:
my_custom_check_creds接收连接池作为参数,返回的内部函数才是处理用户输入的验证逻辑,这样secure_server调用时就能正确拿到用户输入的用户名和密码。
试试这个修正后的代码,应该就能正常完成登录验证了。
备注:内容来源于stack exchange,提问作者zakros
相关产品推荐
相关产品推荐

