【问题标题】:R Shiny: user authentication for a single app.RR Shiny:单个应用程序的用户身份验证。
【发布时间】:2016-12-15 16:04:47
【问题描述】:

我正在开发一个 R Shiny 应用程序并想添加用户名和登录信息。我检查了 RStudio 演示,但那只是使用 ShinyServer Pro,我正在使用 mongolite 包将 formData 备份到 Mongodb。

有没有办法在生成应用 UI 之前强制添加用户登录?

【问题讨论】:

    标签: r authentication shiny


    【解决方案1】:

    ShinyProxy 是一个开源的基于 Docker 和 Spring Java 的 Shiny 服务器,旨在解决这个问题。它允许您在应用程序配置文件中对用户进行硬编码、连接到 LDAP 服务器、使用 SSO/Keycloak 或社交网络登录。

    【讨论】:

      【解决方案2】:

      这是一个如何使用 cookie 进行身份验证的示例。更多信息可以在我的博客here找到。

      先下载cookie js到www/文件夹:

      if (!dir.exists('www/')) {
          dir.create('www')
      }
      
      download.file(
        url = 'https://cdn.jsdelivr.net/npm/js-cookie@2/src/js.cookie.min.js',
        destfile = 'www/js.cookies.js'
      )
      

      安装必要的包:

      install.packages(c('shiny', 'shinyjs', 'bcrypt'))
      

      将以下代码另存为 app.R 并点击“运行应用”按钮:

      library(shiny)
      library(shinyjs)
      library(bcrypt)
      
      
      # This would usually come from your user database.
      
      # Never store passwords as clear text
      password_hash <- hashpw('secret123') 
      
      # Our not so random sessionid
      # sessionid <- paste(
      #   collapse = '', 
      #   sample(x = c(letters, LETTERS, 0:9), size = 64, replace = TRUE)
      # )
      sessionid <- "OQGYIrpOvV3KnOpBSPgOhqGxz2dE5A9IpKhP6Dy2kd7xIQhLjwYzskn9mIhRAVHo" 
      
      
      jsCode <- '
        shinyjs.getcookie = function(params) {
          var cookie = Cookies.get("id");
          if (typeof cookie !== "undefined") {
            Shiny.onInputChange("jscookie", cookie);
          } else {
            var cookie = "";
            Shiny.onInputChange("jscookie", cookie);
          }
        }
        shinyjs.setcookie = function(params) {
          Cookies.set("id", escape(params), { expires: 0.5 });  
          Shiny.onInputChange("jscookie", params);
        }
        shinyjs.rmcookie = function(params) {
          Cookies.remove("id");
          Shiny.onInputChange("jscookie", "");
        }
      '
      
      server <- function(input, output) {
      
        status <- reactiveVal(value = NULL)
        # check if a cookie is present and matching our super random sessionid  
        observe({
          js$getcookie()
          if (!is.null(input$jscookie) && 
              input$jscookie == sessionid) {
                status(paste0('in with sessionid ', input$jscookie))
          }
          else {
            status('out')
          }
        })
      
        observeEvent(input$login, {
          if (input$username == 'admin' & 
              checkpw(input$password, hash = password_hash)) {
            # generate a sessionid and store it in your database,
            # sessionid <- paste(
            #   collapse = '', 
            #   sample(x = c(letters, LETTERS, 0:9), size = 64, replace = TRUE)
            # )
            # but we keep it simple in this example...
            js$setcookie(sessionid)
          } else {
            status('out, cause you don\'t know the password secret123 for user admin.')
          }
        })
      
        observeEvent(input$logout, {
          status('out')
          js$rmcookie()
        })
      
        output$output <- renderText({
          paste0('You are logged ', status())}
        )
      }
      
      ui <- fluidPage(
        tags$head(
          tags$script(src = "js.cookies.js")
        ),
        useShinyjs(),
        extendShinyjs(text = jsCode),
        sidebarLayout(
          sidebarPanel(
            textInput('username', 'User', placeholder = 'admin'),
            passwordInput('password', 'Password', placeholder = 'secret123'),
            actionButton('login', 'Login'),
            actionButton('logout', 'Logout')
          ),
          mainPanel(
            verbatimTextOutput('output')
          )
        )
      )
      
      shinyApp(ui = ui, server = server)
      

      【讨论】:

        【解决方案3】:

        好吧,您可以通过代码使用renderUI 并动态更改用户界面来实现。这是一个如何做的例子:

        library(shiny)
        library(ggplot2)
        
        u <- shinyUI(fluidPage(
          titlePanel("Shiny Password"),
        
          sidebarLayout(position = "left",
                        sidebarPanel( h3("sidebar panel"),
                                      uiOutput("in.pss"),
                                      uiOutput("in.clr"),
                                      uiOutput("in.titl"),
                                      uiOutput("in.cnt"),
                                      uiOutput("in.seed")
        
                        ),
                        mainPanel(h3("main panel"),
                                  textOutput('echo'),
                                  plotOutput('stdplot')
                        )
          )
        ))
        
        pok <- F
        
        s <- shinyServer(function(input, output) 
        {
          output$in.pss   <- renderUI({ input$pss; if (pok) return(NULL) else return(textInput("pss","Password:","")) })
          output$in.clr   <- renderUI({ input$pss; if (pok) return(selectInput("clr","Color:",c("red","blue"))) else return(NULL) })
          output$in.titl  <- renderUI({ input$pss; if (pok) return(textInput("titl","Title:","Data")) else return(NULL) })
          output$in.cnt   <- renderUI({ input$pss; if (pok) return(sliderInput("cnt","Count:",100,1000,500,5)) else return(NULL) })
          output$in.seed  <- renderUI({ input$pss; if (pok) return(numericInput("seed","Seed:",1234,1,10000,1)) else return(NULL) })
          histdata <- reactive(
            {
              input$pss;
              validate(need(input$cnt,"Need count"),need(input$seed,"Need seed"))
              set.seed(input$seed)
              df <- data.frame(x=rnorm(input$cnt))
            }
          )
          observe({
             if (!pok) {
               password <- input$pss
               if (!is.null(password) && password == "pass") {
                 pok <<- TRUE
               }
             }
           }
          )
          output$echo = renderText(
            {
              if (pok) {
                s <- sprintf("the %s is %s and has %d rows and uses the %d seed",
                   input$ent,input$clr,nrow(histdata()),input$seed)
              } else {
                s <- ""
              }
              return(s)
            }
          )
          output$stdplot = renderPlot(
            {
              input$pss
              if (pok) {
                return(qplot(data = histdata(),x,fill = I(input$clr),binwidth = 0.2,main=input$titl))
              } else {
                return(NULL)
              }
            }
          )
        }
        )
        shinyApp(ui=u,server=s)
        

        产量

        登录时的这个:

        一旦你输入了硬编码的密码“pass”。

        当然,以这种方式编程有点笨拙,但是您可以使用选项卡并使用类似的逻辑隐藏它们。

        或者,如果您使用的是 shinyServer,您可能会在站点前面放置一个过滤器。但这就是我在 Shiny 中的处理方式。

        【讨论】:

        【解决方案4】:

        polished R 包为任何 Shiny 应用添加了身份验证和用户管理:https://github.com/Tychobra/polished

        以下是您使用抛光后获得的默认登录页面的屏幕截图:
        您可以轻松地将登录和注册页面上的占位符徽标和颜色替换为您自己的品牌。

        Polished 还附带一个仪表板来管理您应用的用户:


        更多详情:https://polished.tech/

        【讨论】:

          【解决方案5】:

          您可以在 Shiny 应用程序之前添加一个身份验证代理,如下所示:https://www.datascienceriot.com/add-authentication-to-shiny-server-with-nginx/kris/

          这是一个骨架 Nginx 配置,它从 HTTPS 端口 443 重定向到运行在端口 8000 上的 Shiny 服务器。

          server {
              listen       443;
              server_name  shinyservername;
          
              ssl                  on;
              ssl_certificate      ...
              ssl_certificate_key  ...
              ssl_dhparam ...
          
              location / {
                  proxy_pass http://yourdestinationIP:8000;
                  proxy_set_header        X-Forwarded-Proto $scheme;
                  add_header              Front-End-Https   on;
                  proxy_set_header        Accept-Encoding   "";
                  proxy_set_header        Host            $host;
                  proxy_set_header        X-Real-IP       $remote_addr;
                  proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
              auth_basic "Restricted";
              auth_basic_user_file /etc/nginx/htpasswd;
              }
          }
          

          将主机的防火墙设置为打开端口 443,并且只允许 localhost 连接到端口 8000 上的 Shiny 服务器:

          iptables -A INPUT -p tcp --dport 443 -j ACCEPT
          iptables -A INPUT -p tcp -s localhost --dport 8000 -j ACCEPT
          iptables -A INPUT -p tcp --dport 8000 -j DROP
          

          将一个或多个用户的静态凭据添加到/etc/nginx/htpasswd

          htpasswd –c /etc/nginx/htpasswd myshinyuser
          

          一个缺点(很多)是这将进行身份验证和授权,但它不会将经过身份验证的用户信息传递给您的应用程序。为此,您需要 Shiny Server Pro 的身份验证集成,它会在会话中传递您的用户。

          【讨论】:

          • 不鼓励链接的解决方案,因为它们很容易且经常中断。如果您可以在其中添加一些信息以使其以某种方式防破,那将是一个更好的答案。也许勾勒出它是如何工作的,或者最好是一个小的工作示例。
          • 谢谢迈克。我将在一两天内更新答案中嵌入的更多信息。
          【解决方案6】:

          我最近编写了一个 R 包,它提供了可以与任何引导 UI 框架集成的登录/注销模块。

          Blogpost with example using shinydashboard

          Package repo

          包 repo 中的 inst/ 目录包含示例应用程序的代码。

          【讨论】:

          • 这个包在刷新时会注销用户,解决方法是什么?
          • @user322203 您可以设置基于 cookie 的登录,这将使用户在给定的时间段内保持登录状态。包自述文件中的详细信息。
          【解决方案7】:

          我使用 shinyAppLogin 而不是 shinApp:

          # R code
          shinyAppLogin <- function( ui, server, title="Restricted Area", accounts = list(admin="admin"), ...) {
              ui_with_login <- bootstrapPage(theme = "login_style.css",
                  div(class="login-page",
                      div(class="form",
                          h1(title), br(),
                          tags$form(class="login-form",
                              textInput(inputId = "user", label = NULL, placeholder="Username"),
                              passwordInput(inputId = "pass", label = "", placeholder = "Password" ),
                              actionButton(inputId = "login", label = "Login")
                      ) ) ) )
          
              server_with_login <- function(input, output, session) {
          
                  observeEvent(input$login, ignoreNULL = T, {
          
                  if ( input$user %in% names(accounts) && input$pass == accounts[[input$user]] ) {
          
                      removeUI(selector = "body", multiple = T, immediate = T, session = session)
                      insertUI(selector = "html", where = "beforeEnd", ui = ui, immediate = T, session = session )
                      server(session$input, session$output, session = session)
                  }
              } ) }
          
              shinyApp(ui = ui_with_login, server = server_with_login, ...)
          }
          

          然后我的代码变成: shinyAppLogin(my_ui, my_server)

          Login screen when styles

          然后我使用 enter link description here 的 css 只需将您的 CSS 保存在 www/login_style.css 中即可

          【讨论】:

            猜你喜欢
            • 2018-12-23
            • 2012-09-12
            • 2017-09-16
            • 2018-12-29
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2010-09-06
            • 2014-01-26
            相关资源
            最近更新 更多