You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Streamlit应用中Azure AD认证网络访问异常问题排查

问题分析:Streamlit Azure AD认证跨局域网访问异常

问题现象

  • 本地访问http://localhost:8501时,点击登录按钮可正常跳转至Microsoft认证页面,完成登录后返回令牌字典。
  • 局域网内其他用户通过网络URL访问应用时,点击登录按钮后,Microsoft认证弹窗会在服务器端机器打开,而非用户本地机器。

原始代码

import streamlit as st
import msal
import webbrowser
import requests
from selenium.webdriver.support.ui import WebDriverWait
from selenium import webdriver
from selenium.webdriver.support import expected_conditions as EC
import urllib


client_id = "xxxx"
tenant_id = "xxxx"
client_secret = "xxxx"
redirect_uri = "http://localhost:8501/"
scopes = ["https://graph.microsoft.com/.default"]
authority = f"https://login.microsoftonline.com/{tenant_id}"
endpoint = "https://graph.microsoft.com/v1.0/me"

app = msal.ConfidentialClientApplication(
    client_id, client_credential=client_secret, authority=authority, verify=False
)

def get_token_from_cache():
    accounts = app.get_accounts()
    if not accounts:
        return None
    
    result = app.acquire_token_silent(scopes, account=accounts[0])
    if "access_token" in result:
        return result["access_token"]
    else:
        return None

def login():
    flow = app.initiate_auth_code_flow(
        scopes=scopes)

    if "auth_uri" not in flow:
        return st.write("Failed with token")

    auth_uri = flow["auth_uri"]

    browser = webdriver.Chrome()
    browser.get(auth_uri)

    WebDriverWait(browser, 200).until(
        EC.url_contains(redirect_uri))
    
    redirected_url = browser.current_url
    url = urllib.parse.urlparse(redirected_url)
    # parse the query string to get a dictionary of {key: value}

    query_params = dict(urllib.parse.parse_qsl(url.query))
    

    #code = query_params.get('code')[0]
    
    result = app.acquire_token_by_auth_code_flow(flow,query_params, scopes=scopes)
    
    browser.quit()
    return result


if st.button("Login"):
    token = get_token_from_cache()
    if not token:
        token = login()

    st.write(st.experimental_get_query_params())
    if token:
        st.write("Logged in successfully!")
        st.write(token)
    else:
        st.write("Failed to login")

问题根源确认

你的猜测完全正确:核心问题出在webdriver.Chrome()这段代码。Streamlit的后端逻辑运行在服务器机器上,当远程用户点击登录按钮时,这段代码会在服务器端启动Chrome浏览器,而非用户本地的浏览器,导致整个认证流程在服务器端执行,出现弹窗错位的问题。

解决方案:重构OAuth2授权码流程

要让用户在本地浏览器完成认证,需改用标准的OAuth2授权码流程,去掉服务器端启动浏览器的逻辑,改为引导用户在本地完成认证,再回传授权码到服务器获取令牌。

修改步骤及代码

  1. 更新重定向URI:将redirect_uri改为服务器的网络地址(如http://你的服务器IP:8501/),同时在Azure AD应用注册后台同步更新该重定向URI。
  2. 移除Selenium依赖:删掉所有与webdriver相关的代码,改用Streamlit会话状态管理授权流程上下文。
  3. 引导用户本地认证:通过页面链接让用户在本地浏览器打开认证页面,完成后回调到应用获取令牌。

修改后的代码:

import streamlit as st
import msal
import urllib.parse

client_id = "xxxx"
tenant_id = "xxxx"
client_secret = "xxxx"
# 替换为服务器的网络地址,需和Azure AD应用注册中的重定向URI一致
redirect_uri = "http://你的服务器IP:8501/"
scopes = ["https://graph.microsoft.com/.default"]
authority = f"https://login.microsoftonline.com/{tenant_id}"

app = msal.ConfidentialClientApplication(
    client_id, client_credential=client_secret, authority=authority, verify=False
)

def get_token_from_cache():
    accounts = app.get_accounts()
    if not accounts:
        return None
    
    result = app.acquire_token_silent(scopes, account=accounts[0])
    return result.get("access_token")

# 处理认证回调:检查URL中的授权码
query_params = st.experimental_get_query_params()
if "code" in query_params and "auth_flow" in st.session_state:
    # 使用授权码交换令牌
    result = app.acquire_token_by_auth_code_flow(
        st.session_state["auth_flow"], query_params, scopes=scopes
    )
    if "access_token" in result:
        st.session_state["access_token"] = result["access_token"]
        # 清除URL中的查询参数,避免重复处理
        st.experimental_set_query_params()
    else:
        st.error(f"登录失败: {result.get('error_description', '未知错误')}")

# 主页面逻辑
if "access_token" not in st.session_state:
    if st.button("登录"):
        # 初始化授权码流程
        flow = app.initiate_auth_code_flow(scopes=scopes, redirect_uri=redirect_uri)
        st.session_state["auth_flow"] = flow
        # 引导用户在本地浏览器打开认证链接
        st.markdown(f"请点击[这里]({flow['auth_uri']})进行登录", unsafe_allow_html=True)
else:
    st.success("登录成功!")
    st.write("令牌信息:", st.session_state["access_token"])
    if st.button("退出登录"):
        # 清理会话状态
        del st.session_state["access_token"]
        if "auth_flow" in st.session_state:
            del st.session_state["auth_flow"]
        st.experimental_rerun()

核心优化点

  • 去掉服务器端启动浏览器的逻辑,改为用户本地浏览器完成认证。
  • 用Streamlit的session_state保存授权流程上下文,避免认证过程中丢失状态。
  • 处理认证后的回调参数,在服务器端安全完成令牌交换。
  • 增加退出登录功能,完善会话管理。

内容的提问来源于stack exchange,提问作者Vic

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 16:45:35