R使用AWS Cognito进行Shiny身份验证
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了R使用AWS Cognito进行Shiny身份验证相关的知识,希望对你有一定的参考价值。
我正在将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,或一个Node JS应用程序,其中包含Shiny?
我意识到这个问题相当广泛,但是因为我确信我不是唯一一个带着这些问题走来走去的人,所以我仍然要求所有人都能从可能的解决方案中获益。
这是我已经实现的设置的描述。这是使用AWS Cognito以及AWS特定功能。
上下文:我有一堆闪亮的应用程序,打包在容器中(通常使用asachet/shiny-base
或these Dockerfile
s作为基础)。我想私下托管他们并控制谁可以访问它们。
下面的设置是闪亮代理的替代方案。实际上,它不需要任何类型的闪亮服务器。每个应用程序只依赖于shiny
。每个容器暴露一个端口(例如EXPOSE 3838
),并简单地与runApp(".", host="0.0.0.0", port=3838)
一起发射。扩展策略负责根据需要启动和停止容器。身份验证逻辑与应用程序代码完全分离。
我的云设置是:
- 应用程序负载均衡器(ALB)用作用户入口点。您必须使用HTTPS侦听器来设置身份验证。我只是将HTTP流量重定向到HTTPS。
- 每个应用程序的弹性容器服务(ECS)任务+服务。这可确保我的应用程序得到充分配置并完全独立运行。每个应用都可以有一个独立的扩展策略,因此每个应用都有适当的流量资源。您甚至可以将应用程序配置为自动启动/停止以节省大量资源。显然,应用程序需要是私有的,即只能从ALB访问。
- 每个ECS都有一个不同的ALB目标组,因此对
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 Hosted UI(类似example.auth.eu-west-2.amazoncognito.com
)以输入其凭据。您可以在Cognito设置中自定义托管UI的外观。
Helpful links
用于在容器中打包R代码:
使用ALB设置Cognito身份验证:
- Amazon documentation
- 走过:https://www.thorntech.com/2018/09/user-authentication-alb-cognito/(其中包含this video)
您可以使用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 su以上是关于R使用AWS Cognito进行Shiny身份验证的主要内容,如果未能解决你的问题,请参考以下文章
我可以使用 AWS Cognito 进行 Ejabberd 服务器身份验证吗?
尝试通过 AWS 应用程序负载均衡器和 Cognito 进行身份验证时出现 500 错误