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

Android通过Graph读取SharePoint文件遇403问题及相关咨询

关于Android端访问SharePoint文件的问题解答

Q1:Android端访问SharePoint文件是否需要特殊权限?

你遇到的403错误核心在于浏览器与Android应用的请求上下文不统一:

  • 浏览器能直接访问,说明文件已配置匿名访问权限,或你的设备处于企业内网域环境,浏览器自动通过NTLM/Kerberos完成了身份验证。
  • 但Android应用的HTTP请求是独立上下文,默认不会携带浏览器的身份凭证,哪怕是同一设备也不行。
  • 若文件是企业内部资源(即便浏览器能匿名访问),Android应用仍需通过MSAL获取合法访问令牌才能绕过403。所需权限包括:
    • Files.Read 或 Files.Read.All(优先选择最小权限,根据文件访问范围决定)
    • 若访问SharePoint站点文件,还需 Sites.Read.All 或对应站点级权限

Q2:示例认证JSON文件及Android清单配置

MSAL配置JSON(auth_config.json,放置在res/raw目录)

{
  "client_id": "你的Azure AD应用客户端ID",
  "authorization_user_agent": "DEFAULT",
  "redirect_uri": "msal你的客户端ID://auth",
  "authorities": [
    {
      "type": "AAD",
      "audience": {
        "type": "AzureADMyOrg",
        "tenant_id": "你的Azure租户ID"
      }
    }
  ],
  "logging": {
    "level": "INFO",
    "pii_enabled": false
  },
  "authorization_mode": "AUTHORIZATION_CODE",
  "multiple_clouds_supported": false,
  "broker_redirect_uri_registered": true
}

AndroidManifest.xml 关键配置片段

<!-- 网络权限 -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

<!-- MSAL 必需的浏览器跳转Activity -->
<activity
    android:name="com.microsoft.identity.client.BrowserTabActivity"
    android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <!-- 替换为你的redirect_uri中的scheme部分 -->
        <data android:scheme="msal你的客户端ID" />
    </intent-filter>
</activity>

Q3:Android Kotlin读取SharePoint文件的示例

步骤1:初始化MSAL客户端并处理登录

import com.microsoft.identity.client.*
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity

class FileDownloadActivity : AppCompatActivity() {
    private lateinit var singleAccountApp: ISingleAccountPublicClientApplication

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_file_download)

        // 初始化MSAL单账户客户端
        PublicClientApplication.createSingleAccountPublicClientApplication(
            this,
            R.raw.auth_config,
            object : IPublicClientApplication.ISingleAccountApplicationCreatedListener {
                override fun onCreated(application: ISingleAccountPublicClientApplication) {
                    singleAccountApp = application
                    checkExistingAccount()
                }

                override fun onError(exception: MsalException) {
                    // 处理初始化错误,比如配置文件读取失败
                }
            }
        )
    }

    // 检查是否已有登录账户
    private fun checkExistingAccount() {
        singleAccountApp.getCurrentAccountAsync(object : ISingleAccountPublicClientApplication.CurrentAccountCallback {
            override fun onAccountLoaded(activeAccount: IAccount?) {
                if (activeAccount == null) {
                    signIn() // 无账户则触发登录
                } else {
                    downloadSharePointFile(activeAccount) // 有账户直接下载
                }
            }

            override fun onAccountChanged(priorAccount: IAccount?, currentAccount: IAccount?) {}
            override fun onError(exception: MsalException) {}
        })
    }

    // 触发登录流程
    private fun signIn() {
        val scopes = arrayOf("Files.Read", "Sites.Read.All")
        singleAccountApp.signIn(
            this,
            null,
            scopes,
            object : AuthenticationCallback {
                override fun onSuccess(result: IAuthenticationResult) {
                    downloadSharePointFile(result.account)
                }

                override fun onError(exception: MsalException) {}
                override fun onCancel() {}
            }
        )
    }
}

步骤2:携带令牌下载文件

import okhttp3.Call
import okhttp3.Callback
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import java.io.IOException
import java.io.OutputStream

private fun downloadSharePointFile(account: IAccount) {
    // 静默获取访问令牌
    singleAccountApp.acquireTokenSilentAsync(
        arrayOf("Files.Read", "Sites.Read.All"),
        account.authority,
        object : SilentAuthenticationCallback {
            override fun onSuccess(result: IAuthenticationResult) {
                val accessToken = result.accessToken
                downloadFileWithToken("你的SharePoint文件链接", accessToken)
            }

            override fun onError(exception: MsalException) {
                if (exception is MsalUiRequiredException) {
                    signIn() // 需要交互登录,重新触发登录流程
                }
            }
        }
    )
}

// 发起带令牌的下载请求
private fun downloadFileWithToken(fileUrl: String, accessToken: String) {
    val client = OkHttpClient()
    val request = Request.Builder()
        .url(fileUrl)
        .addHeader("Authorization", "Bearer $accessToken")
        .build()

    client.newCall(request).enqueue(object : Callback {
        override fun onFailure(call: Call, e: IOException) {
            runOnUiThread {
                // 主线程更新UI,提示下载失败
            }
        }

        override fun onResponse(call: Call, response: Response) {
            if (response.isSuccessful) {
                response.body?.let { body ->
                    // 将文件保存到应用私有目录
                    val outputStream: OutputStream = openFileOutput("downloaded_file.docx", MODE_PRIVATE)
                    outputStream.write(body.bytes())
                    outputStream.close()

                    runOnUiThread {
                        // 主线程提示下载成功
                    }
                }
            } else {
                runOnUiThread {
                    // 处理403等错误,提示权限问题
                }
            }
        }
    })
}

注意事项

  • 确保Azure AD应用已在Azure门户中配置好API权限,并完成管理员同意(若权限需要)。
  • 如果文件确实是完全公开的匿名资源,也可以尝试移除Authorization头,直接发起普通HTTP请求下载,但企业场景下这种情况极少。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 17:25:34