Shiny应用中用户自定义Spotify凭证认证失败问题排查求助
问题排查与解决方案
核心问题分析
出现400错误的原因主要有三点:
- 认证逻辑触发时机不匹配:
bar_plot反应式会在用户点击「Get data」时直接执行API调用,但此时用户输入的凭证可能还未完成认证,环境变量未正确设置。 - Token缓存冲突:
get_spotify_access_token默认会缓存已获取的token,硬编码凭证时的有效token会被缓存,当用户输入新凭证时,缓存未清空,导致仍使用旧token发起请求。 - 环境变量作用域问题:在Shiny反应式上下文中设置
Sys.setenv,可能存在作用域限制,无法确保后续API调用能读取到最新的凭证。
修正方案
1. 直接传递凭证参数,避免依赖环境变量
修改认证逻辑,直接将用户输入的Client ID和Secret传入get_spotify_access_token,同时将获取到的token存储为反应式变量,后续API调用时直接传入该token。
2. 控制交互流程,确保认证完成后再允许数据请求
添加状态控制,比如在未完成认证时禁用「Get data」按钮,避免用户提前触发API调用。
3. 使用安全输入组件存储Secret
将textInput替换为passwordInput,避免Client Secret明文显示。
修正后的完整代码
library(shiny) library(spotifyr) library(plotly) library(dplyr) ui <- fluidPage(title = "Bar Plot", sidebarLayout( sidebarPanel( textInput("id","Enter Client ID"), passwordInput("secret","Enter Client Secret"), # 改为密码输入框 actionButton("auth","Authenticate"),br(),hr(), textInput("text","Enter Artist's ID"), actionButton("click","Get data", disabled = TRUE), # 默认禁用 width = 3 ), mainPanel( fluidRow(column(6,plotlyOutput("plot",width = "105%",height = "400px"))) ) )) server <- function(input,output,session){ # 存储认证后的token access_token <- reactiveVal(NULL) # 认证函数:直接传入凭证,返回token authentication <- function(id, secret) { req(id, secret) get_spotify_access_token( client_id = id, client_secret = secret, show_dialog = FALSE # 静默获取token ) } # 点击认证按钮触发 observeEvent(input$auth, { tryCatch({ token <- authentication(input$id, input$secret) access_token(token) # 认证成功后启用Get data按钮 enable("click") showNotification("认证成功!", type = "message") }, error = function(e) { showNotification(paste("认证失败:", e$message), type = "error") access_token(NULL) disable("click") }) }) artists_popularity <- function(artist_id, token){ req(artist_id, token) # 传入token调用API related_artists <- get_related_artists( id = artist_id, include_meta_info = TRUE, access_token = token ) other_related <- c() for(i in 1:nrow(related_artists$artists)){ result <- get_related_artists( id = related_artists$artists[["id"]][i], include_meta_info = TRUE, access_token = token ) other_related <- append(other_related,result) } nodes <- data.frame( id = c(1:400), node = c(other_related[[1]]$name, other_related[[2]]$name, other_related[[3]]$name, other_related[[4]]$name, other_related[[5]]$name, other_related[[6]]$name, other_related[[7]]$name, other_related[[8]]$name, other_related[[9]]$name, other_related[[10]]$name, other_related[[11]]$name, other_related[[12]]$name, other_related[[13]]$name, other_related[[14]]$name, other_related[[15]]$name, other_related[[16]]$name, other_related[[17]]$name, other_related[[18]]$name, other_related[[19]]$name, other_related[[20]]$name), label = c(other_related[[1]]$name, other_related[[2]]$name, other_related[[3]]$name, other_related[[4]]$name, other_related[[5]]$name, other_related[[6]]$name, other_related[[7]]$name, other_related[[8]]$name, other_related[[9]]$name, other_related[[10]]$name, other_related[[11]]$name, other_related[[12]]$name, other_related[[13]]$name, other_related[[14]]$name, other_related[[15]]$name, other_related[[16]]$name, other_related[[17]]$name, other_related[[18]]$name, other_related[[19]]$name, other_related[[20]]$name), popularity = c(c(other_related[[1]]$popularity, other_related[[2]]$popularity, other_related[[3]]$popularity, other_related[[4]]$popularity, other_related[[5]]$popularity, other_related[[6]]$popularity, other_related[[7]]$popularity, other_related[[8]]$popularity, other_related[[9]]$popularity, other_related[[10]]$popularity, other_related[[11]]$popularity, other_related[[12]]$popularity, other_related[[13]]$popularity, other_related[[14]]$popularity, other_related[[15]]$popularity, other_related[[16]]$popularity, other_related[[17]]$popularity, other_related[[18]]$popularity, other_related[[19]]$popularity, other_related[[20]]$popularity)), followers = c(c(other_related[[1]]$followers.total, other_related[[2]]$followers.total, other_related[[3]]$followers.total, other_related[[4]]$followers.total, other_related[[5]]$followers.total, other_related[[6]]$followers.total, other_related[[7]]$followers.total, other_related[[8]]$followers.total, other_related[[9]]$followers.total, other_related[[10]]$followers.total, other_related[[11]]$followers.total, other_related[[12]]$followers.total, other_related[[13]]$followers.total, other_related[[14]]$followers.total, other_related[[15]]$followers.total, other_related[[16]]$followers.total, other_related[[17]]$followers.total, other_related[[18]]$followers.total, other_related[[19]]$followers.total, other_related[[20]]$followers.total)) ) nodes <- distinct(nodes,node,label,popularity,followers,.keep_all = T) nodes_df <- data.frame( Artist = reorder(nodes$node,+nodes$popularity), Popularity = nodes$popularity ) nodes_df <- head(nodes_df,20) fill <- c("A","B","C","D","E","F","G","H") rand_fill <- sample(fill,size = 1) ggnodes <- nodes_df |> ggplot()+aes(x = Artist,y = Popularity,fill = Artist)+ geom_bar(stat = "identity")+coord_flip()+ ggtitle("Top 20 Most Popular Artists")+ labs(x = "Artists")+scale_fill_viridis(discrete = T,option = rand_fill)+ scale_y_continuous(expand = c(0,0))+ theme(axis.ticks = element_blank(), legend.position = "none", axis.text = element_text(size = 10,face = "bold"), plot.title = element_text(size = 12,face = "bold",hjust = 0.5), axis.title.x = element_text(size = 12,face = "bold"), axis.title.y = element_text(hjust = 0.8,size = 12,face = "bold")) g <- ggnodes |> ggplotly(tooltip = c("x","y")) return(g) } id_input <- reactive({as.character(input$text)}) # 点击Get data时触发,传入token plot_react <- eventReactive(input$click,{ artists_popularity(id_input(), access_token()) }) output$plot <- renderPlotly({ plot_react() }) } shinyApp(ui = ui,server = server)
关键修改点说明
- 使用
reactiveVal存储认证后的token,确保在反应式上下文中能正确传递。 - 认证函数直接传入凭证参数调用
get_spotify_access_token,避免环境变量依赖。 - 添加按钮状态控制:认证成功前禁用「Get data」按钮,防止无效请求。
- 所有Spotify API调用(
get_related_artists)都显式传入access_token参数,确保使用最新的有效token。 - 替换
textInput为passwordInput存储Client Secret,提升安全性。
内容的提问来源于stack exchange,提问作者Ifeanyi Idiaye
相关产品推荐
相关产品推荐

