我正在将R Studio Server与R Shiny结合使用,在Ubuntu 16.04上运行。一切正常。我想保护R Shiny仪表板(用户名+ pw),我正在考虑建立一个与AWS Cognito通信的小网页来验证用户。
我找不到任何关于此组合的文档(Shiny + Cognito),但确实找到了一些关于R Shiny Authentication(使用NGINX + Auth0)和使用Cognito(例如与NodeJS结合使用)的文档)。
Shiny和Cognito(例如PHP或Node JS)的组合是否合乎逻辑且安全?什么是最好的方法:一个带有一些PHP的简单网页,或一个包含Shiny的Node JS应用程序?
我意识到这个问题相当广泛,但是因为我确信我不是唯一一个在这个问题上走来走去的人,所以我仍然要求每个人都能从可能的解决方案中获益。
答案 0 :(得分:2)
这是我已实施的设置的说明。这是结合使用AWS Cognito和AWS特定功能。
上下文:我有一堆闪亮的应用程序,打包在容器中(通常使用asachet/shiny-base
或these Dockerfile
s中的一个作为基础)。我想私下托管它们,并控制谁可以访问它们。
下面的设置是闪亮代理的替代方法。实际上,它不需要任何类型的闪亮服务器。每个应用程序仅依靠shiny
。每个容器都公开一个端口(例如EXPOSE 3838
),并仅通过runApp(".", host="0.0.0.0", port=3838)
启动。伸缩策略负责根据需要启动和停止容器。身份验证逻辑与应用程序代码完全脱钩。
我的云设置是:
app1.example.com
的请求将转发到app1
,将app2.example.com
转发到app2
,依此类推。所有这些都在ALB规则。在这里我们可以轻松添加身份验证。我有一个允许用户访问应用程序的用户帐户的Cognito“用户池”。这可用于在流量级别而非应用程序级别限制对应用程序的访问。
为此,您首先需要在Cognito用户池中创建一个客户端应用程序。对于app1
,我将使用“授权代码授予”流以openid
范围和app1.example.com/oauth2/idpresponse
作为回调URL来创建Cognito客户端应用。
完成此操作后,您只需进入ALB规则并添加身份验证作为转发的先决条件:
从现在开始,必须先验证app1.example.com
上的流量,然后才能将其转发到app1
。未经身份验证的请求将被重定向到Cognito托管用户界面(类似example.auth.eu-west-2.amazoncognito.com
),以输入其凭据。您可以在Cognito设置中自定义托管UI的外观。
要将R代码包装在容器中:
使用ALB设置Cognito身份验证:
答案 1 :(得分:0)
您可以利用AWS Cognito API进行身份验证。我写了一篇有关here的帖子。
为使该答案独立,以下是详细信息。基本上,您需要做的是在应用程序的global.r
文件中使用以下代码:
base_cognito_url <- "https://YOUR_DOMAIN.YOUR_AMAZON_REGION.amazoncognito.com/"
app_client_id <- "YOUR_APP_CLIENT_ID"
app_client_secret <- "YOUR_APP_CLIENT_SECRET"
redirect_uri <- "https://YOUR_APP/redirect_uri"
library(httr)
app <- oauth_app(appname = "my_shiny_app",
key = app_client_id,
secret = app_client_secret,
redirect_uri = redirect_uri)
cognito <- oauth_endpoint(authorize = "authorize",
access = "token",
base_url = paste0(base_cognito_url, "oauth2"))
retrieve_user_data <- function(user_code){
failed_token <- FALSE
# get the token
tryCatch({token_res <- oauth2.0_access_token(endpoint = cognito,
app = app,
code = user_code,
user_params = list(client_id = app_client_id,
grant_type = "authorization_code"),
use_basic_auth = TRUE)},
error = function(e){failed_token <<- TRUE})
# check result status, make sure token is valid and that the process did not fail
if (failed_token) {
return(NULL)
}
# The token did not fail, go ahead and use the token to retrieve user information
user_information <- GET(url = paste0(base_cognito_url, "oauth2/userInfo"),
add_headers(Authorization = paste("Bearer", token_res$access_token)))
return(content(user_information))
}
在server.r
中,您可以这样使用它:
library(shiny)
library(shinyjs)
# define a tibble of allwed users (this can also be read from a local file or from a database)
allowed_users <- tibble(
user_email = c("user1@example.com",
"user2@example.com"))
function(input, output, session){
# initialize authenticated reactive values ----
# In addition to these three (auth, name, email)
# you can add additional reactive values here, if you want them to be based on the user which logged on, e.g. privileges.
user <- reactiveValues(auth = FALSE, # is the user authenticated or not
name = NULL, # user's name as stored and returned by cognito
email = NULL) # user's email as stored and returned by cognito
# get the url variables ----
observe({
query <- parseQueryString(session$clientData$url_search)
if (!("code" %in% names(query))){
# no code in the url variables means the user hasn't logged in yet
showElement("login")
} else {
current_user <- retrieve_user_data(query$code)
# if an error occurred during login
if (is.null(current_user)){
hideElement("login")
showElement("login_error_aws_flow")
showElement("submit_sign_out_div")
user$auth <- FALSE
} else {
# check if user is in allowed user list
# for more robustness, use stringr::str_to_lower to avoid case sensitivity
# i.e., (str_to_lower(current_user$email) %in% str_to_lower(allowed_users$user_email))
if (current_user$email %in% allowed_users$user_email){
hideElement("login")
showElement("login_confirmed")
showElement("submit_sign_out_div")
user$auth <- TRUE
user$email <- current_user$email
user$name <- current_user$name
# ==== User is valid, continue prep ====
# show the welcome box with user name
output$confirmed_login_name <-
renderText({
paste0("Hi there!, ",
user$name)
})
# ==== Put additional login dependent steps here (e.g. db read from source) ====
# ADD HERE YOUR REQUIRED LOGIC
# I personally like to select the first tab for the user to see, i.e.:
showTab("main_navigation", "content_tab_id", select = TRUE)
# (see the next chunk for how this tab is defined in terms of ui elements)
# ==== Finish loading and go to tab ====
} else {
# user not allowed. Only show sign-out, perhaps also show a login error message.
hideElement("login")
showElement("login_error_user")
showElement("submit_sign_out_div")
}
}
}
})
# This is where you will put your actual elements (the server side that is) ----
# For example:
output$some_plot <- renderPlot({
# *** THIS IS EXTREMELY IMPORTANT!!! ***
validate(need(user$auth, "No privileges to watch data. Please contact support."))
# since shinyjs is not safe for hiding content, make sure that any information is covered
# by the validate(...) expression as was specified.
# Rendered elements which were not preceded by a validate expression can be viewed in the html code (even if you use hideElement).
# only if user is confirmed the information will render (a plot in this case)
plot(cars)
})
}
ui.r
看起来像这样:
library(shiny)
library(shinyjs)
fluidPage(
useShinyjs(), # to enable the show/hide of elements such as login and buttons
hidden( # this is how the logout button will like:
div(
id = "submit_sign_out_div",
a(id = "submit_sign_out",
"logout",
href = aws_auth_logout,
style = "color: black;
-webkit-appearance: button;
-moz-appearance: button;
appearance: button;
text-decoration: none;
background:#ff9999;
position: absolute;
top: 0px; left: 20px;
z-index: 10000;
padding: 5px 10px 5px 10px;"
)
)
),
navbarPage(
"Cognito auth example",
id = "main_navigation",
tabPanel(
"identification",
value = "login_tab_id",
h1("Login"),
div(
id = "login",
p("To login you must identify with a username and password"),
# This defines a login button which upon click will redirect to the AWS Cognito login page
a(id = "login_link",
"Click here to login",
href = aws_auth_redirect,
style = "color: black;
-webkit-appearance: button;
-moz-appearance: button;
appearance: button;
text-decoration: none;
background:#95c5ff;
padding: 5px 10px 5px 10px;")
),
hidden(div(
id = "login_error_aws_flow",
p("An error has occurred."),
p("Please contact support")
)),
hidden(
div(
id = "login_confirmed",
h3("User confirmed"),
fluidRow(
textOutput("confirmed_login_name")),
fluidRow(
p("Use the menu bar to navigate."),
p(
"Don't forget to logout when you want to close the system."
)
)
)
),
),
tabPanel("Your actual content",
value = "content_tab_id",
fluidRow(plotOutput("some_plot")))
)
)