分享一个 shinyApp 的用户和角色权限管理的方案
楚新元 / 2023-03-09
最近做了一个自动生成文档的 shinyApp,顺便用 {shinyauthr}
包做了一个用户登录界面,但是要实现用户修改密码的功能困扰了我好久,主要问题是 session 一旦起来后只修改配置文件里的用户密码是不行了,因为内存中的密码没有变,后来我终于想到了解决方案,把读取配置文件放在 server function 里面,每次修改配置文件密码后立马通过 session$reload()
重启会话,这样就会重新读取配置文件的用户信息,从而刷新内存中的,问题算是解决了。为了方便其他人,当然这个其他人也有可能就是未来某一天的我。长话短说,放码出来先:
先生成配置文件,这个配置文件包括用户和密码信息。用户和密码信息放在 config.sqlite 数据库的 ser_group 表里(其实一开始是放在 .csv 文件的,因为有多个配置文件,不方便导入导出),可以用如下代码生成:
library(RSQLite)
user_group = data.frame(
user = c("admin", "test"),
password = c("admin", "666666"),
permissions = c("admin", "standard"),
name = c("admin", "test")
)
config = dbConnect(SQLite(), "config.sqlite")
dbWriteTable(
conn = config,
name = "user_group",
value = user_group,
overwrite = TRUE
)
dbDisconnect(config)
下面是一个带用户和角色权限管理的 shinyApp 的示例:
##----------------------------------------------------------------------------##
# 配置环境变量
# Sys.setlocale(category = "LC_ALL", locale = "zh_CN.UTF-8")
# Sys.setenv(RSTUDIO_PANDOC = "/usr/bin")
# options(bitmapType = "cairo")
##----------------------------------------------------------------------------##
# 加载相关R包
library(shiny)
library(shinydashboard)
library(shinyauthr)
library(shinyjs)
library(RSQLite)
##----------------------------------------------------------------------------##
# 定义前端交互界面
ui = dashboardPage(
skin = "blue",
dashboardHeader(
title = "用户修改密码测试",
titleWidth = 260,
tags$li(
class = "dropdown",
style = "padding: 8px;",
logoutUI(
id = "logout",
label = "",
icon = icon("sign-out-alt"),
class = "btn-primary",
style = "color: white;"
)
)
),
dashboardSidebar(
width = 260,
collapsed = TRUE,
sidebarMenuOutput("sidebar")
),
dashboardBody(
useShinyjs(),
loginUI(
id = "login",
title = "用户登录",
user_title = "用户名",
pass_title = "用户密码",
login_title = "登录",
error_message = "无效的用户名或密码!",
additional_ui = NULL,
cookie_expiry = 7
),
tabItems(
tabItem(
tabName = "resetpassword",
uiOutput("resetpassword_ui")
)
)
)
)
##----------------------------------------------------------------------------##
# 定义后台服务逻辑
server = function(input, output, session) {
# 获取配置文件信息
config = dbConnect(SQLite(), "config.sqlite")
user_group = dbGetQuery(config, "select * from user_group")
dbDisconnect(config)
# 定义凭证信息
credentials <- loginServer(
id = "login",
data = user_group,
user_col = user,
pwd_col = password,
log_out = reactive(logout_init())
)
logout_init <- logoutServer(
id = "logout",
active = reactive(credentials()$user_auth)
)
# 捕获登录用户信息
user_info <- reactive({
credentials()$info
})
# 登录后打开、退出时关闭侧边栏
observe({
if(credentials()$user_auth) {
removeClass(
selector = "body",
class = "sidebar-collapse")
} else {
addClass(
selector = "body",
class = "sidebar-collapse")
}
})
# 验证通过后才显示侧边菜单栏
output$sidebar <- renderMenu({
req(credentials()$user_auth)
sidebarMenu(
id = "tabs",
menuItem(
"修改密码",
tabName = "resetpassword",
icon = icon("key")
)
)
})
# 验证通过后才显示密码修改界面
output$resetpassword_ui <- renderUI({
req(credentials()$user_auth)
fluidPage(
sidebarLayout(
sidebarPanel(
width = 3,
passwordInput(
inputId = "password1",
label = "请输入原密码:",
value = ""
),
passwordInput(
inputId = "password2",
label = "请输入新密码:",
value = ""
),
passwordInput(
inputId = "password3",
label = "请确认新密码:",
value = ""
),
br(),
div(
style = "display: inline-block;
width: 100%;
text-align: center;",
actionButton(
inputId = "button",
label = "提交",
# icon = icon("check"),
class = "btn-primary btn-md"
)
),
tableOutput("resetinfo")
),
mainPanel()
)
)
})
##--------------------------------------------------------------------------##
# 修改密码
observeEvent(input$button, {
pass1 = input$password1
pass2 = input$password2
pass3 = input$password3
## 验证密码后修改新密码
if (pass1 == user_info()$password & pass2 != "" & pass2 == pass3) {
user_group$password[user_group$user == user_info()$user] = pass2
config = dbConnect(SQLite(), "config.sqlite")
dbWriteTable(
conn = config,
name = "user_group",
value = user_group,
overwrite = TRUE
)
dbDisconnect(config)
### 密码修改成功后的反馈信息
output$resetinfo <- renderTable(
{
print("密码已修改成功,请用新密码重新登录!")
},
colnames = FALSE
)
### 重启会话以便读取新配置文件
session$reload()
} else {
### 密码不一致情况的反馈信息
output$resetinfo <- renderTable(
{
if (pass1 != "" & pass1 != user_info()$password) {
print("输入的原密码和登录密码不一致,请重新输入!")
} else {
if (pass2 != "" & pass2 != pass3) {
print("输入的新密码和确认密码不一致,请重新输入!")
}
}
},
colnames = FALSE
)
}
})
}
##----------------------------------------------------------------------------##
# 运行shinyApp
shinyApp(ui = ui, server = server)
##----------------------------------------------------------------------------##