如何通过自定义HTML登录页访问Shiny应用
自定义登录页访问Shiny APP的配置方案
你可以通过纯Shiny+JavaScript或结合PHP后端验证两种方式实现自定义登录页访问Shiny APP,以下是具体步骤:
方案一:纯Shiny+JavaScript前端验证
这种方案无需额外后端服务,直接在Shiny内完成登录验证,适合简单场景。
1. 修改Shiny代码
调整服务器逻辑,维护登录状态,动态切换登录页与主UI:
library(shiny) ui <- fluidPage( uiOutput("main_content") ) server <- function(input, output, session) { # 登录状态标记 is_logged_in <- reactiveVal(FALSE) # 合法用户列表(实际项目建议替换为数据库查询) valid_users <- list( "user1@example.com" = "password123", "user2@example.com" = "456password" ) # 动态渲染内容:未登录显示登录页,已登录显示原Shiny界面 output$main_content <- renderUI({ if (!is_logged_in()) { htmlTemplate("www/index.html") } else { fluidPage( sidebarLayout( sidebarPanel( sliderInput("bins", "Number of bins:", min = 1, max = 50, value = 30) ), mainPanel( plotOutput("distPlot") ) ) ) } }) # 处理登录请求 observeEvent(input$login_attempt, { username <- input$login_username password <- input$login_password if (!is.null(username) && !is.null(password) && exists(username, where = valid_users) && valid_users[[username]] == password) { is_logged_in(TRUE) } else { showModal(modalDialog( title = "登录失败", "用户名或密码错误,请重试", easyClose = TRUE )) } }) # 原有绘图逻辑 output$distPlot <- renderPlot({ x <- faithful[, 2] bins <- seq(min(x), max(x), length.out = input$bins + 1) hist(x, breaks = bins, col = 'darkgray', border = 'white') }) } shinyApp(ui = ui, server = server)
2. 修改自定义登录页(index.html)
添加JavaScript监听表单提交,将用户名密码发送给Shiny服务器:
<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>用户登录</title> <link href="assets/dist/css/bootstrap.min.css" rel="stylesheet"> <link href="signin.css" rel="stylesheet"> <!-- 加载Shiny通信所需JS --> {{ headContent() }} </head> <body class="text-center"> <main class="form-signin w-100 m-auto"> <form id="loginForm"> <img class="mb-4" src="assets/brand/bootstrap-logo.svg" alt="" width="72" height="57"> <h1 class="h3 mb-3 fw-normal">请输入用户名和密码</h1> <div class="form-floating"> <input type="email" class="form-control" id="floatingInput" placeholder="name@example.com"> <label for="floatingInput">邮箱</label> </div> <div class="form-floating"> <input type="password" class="form-control" id="floatingPassword" placeholder="密码"> <label for="floatingPassword">密码</label> </div> <div class="checkbox mb-3"> <label> <input type="checkbox" value="remember-me"> 记住我 </label> </div> <button class="w-100 btn btn-lg btn-primary" type="submit">登录</button> <p class="mt-5 mb-3 text-muted">© 2017–2022</p> </form> </main> <script> // 监听表单提交,阻止默认跳转 document.getElementById('loginForm').addEventListener('submit', function(e) { e.preventDefault(); // 获取输入值 const username = document.getElementById('floatingInput').value; const password = document.getElementById('floatingPassword').value; // 将数据发送给Shiny服务器 Shiny.setInputValue('login_username', username); Shiny.setInputValue('login_password', password); Shiny.setInputValue('login_attempt', Math.random()); // 触发登录验证逻辑 }); </script>
方案二:结合PHP后端验证
这种方案将登录验证逻辑放在PHP后端,安全性更高,适合生产环境。
1. 编写PHP登录验证脚本(login.php)
放在独立的Web服务器目录(如Apache/Nginx下),负责验证用户并创建会话:
<?php session_start(); // 合法用户(实际项目建议从数据库读取加密后的密码) $valid_users = [ 'user1@example.com' => password_hash('password123', PASSWORD_DEFAULT), 'user2@example.com' => password_hash('456password', PASSWORD_DEFAULT) ]; $username = $_POST['username'] ?? ''; $password = $_POST['password'] ?? ''; if (isset($valid_users[$username]) && password_verify($password, $valid_users[$username])) { $_SESSION['logged_in'] = true; $_SESSION['username'] = $username; echo json_encode(['success' => true]); } else { echo json_encode(['success' => false, 'message' => '用户名或密码错误']); } ?>
2. 编写PHP会话验证脚本(check_session.php)
用于Shiny验证用户是否已登录:
<?php session_id($_POST['session_id']); session_start(); if (isset($_SESSION['logged_in']) && $_SESSION['logged_in'] === true) { echo json_encode(['success' => true]); } else { echo json_encode(['success' => false]); } ?>
3. 修改登录页(index.html)的JS逻辑
将登录请求发送到PHP脚本:
<!-- 其他部分同前,替换script内容 --> <script> document.getElementById('loginForm').addEventListener('submit', function(e) { e.preventDefault(); const username = document.getElementById('floatingInput').value; const password = document.getElementById('floatingPassword').value; // 发送请求到PHP登录脚本 fetch('http://你的域名/login.php', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: `username=${encodeURIComponent(username)}&password=${encodeURIComponent(password)}` }) .then(response => response.json()) .then(data => { if (data.success) { window.location.href = 'http://你的ShinyAPP地址'; } else { alert(data.message); } }) .catch(error => { console.error('登录请求失败:', error); alert('登录请求失败,请稍后重试'); }); }); </script>
4. 修改Shiny代码验证会话
使用shinyjs读取PHP会话Cookie,验证用户登录状态:
library(shiny) library(shinyjs) library(httr) ui <- fluidPage( useShinyjs(), uiOutput("main_content") ) server <- function(input, output, session) { is_logged_in <- reactiveVal(FALSE) # 检查PHP会话有效性 observe({ session_id <- runjs("return document.cookie.split('; ').find(row => row.startsWith('PHPSESSID='))?.split('=')[1];") if (!is.null(session_id)) { response <- POST("http://你的域名/check_session.php", body = list(session_id = session_id)) result <- content(response, "parsed") is_logged_in(result$success) } else { is_logged_in(FALSE) } }) # 动态渲染内容 output$main_content <- renderUI({ if (!is_logged_in()) { htmlTemplate("www/index.html") } else { fluidPage( sidebarLayout( sidebarPanel( sliderInput("bins", "Number of bins:", min = 1, max = 50, value = 30) ), mainPanel( plotOutput("distPlot") ) ) ) } }) # 原有绘图逻辑 output$distPlot <- renderPlot({ x <- faithful[, 2] bins <- seq(min(x), max(x), length.out = input$bins + 1) hist(x, breaks = bins, col = 'darkgray', border = 'white') }) } shinyApp(ui = ui, server = server)
注意事项
- 安全性:生产环境中禁止明文存储密码,建议使用bcrypt等加密算法存储哈希值。
- 跨域问题:Shiny APP与PHP脚本需部署在同一域名下,或配置CORS允许跨域请求。
- 会话管理:PHP方案需确保会话Cookie的安全性(设置
HttpOnly、Secure属性)。
内容的提问来源于stack exchange,提问作者Antonio
相关产品推荐
相关产品推荐

