Android oneDrive 集成(一)– SDK申请

微软的MSAL平台有多种租户模式,本篇文章将介绍单租户的接入方式。

AzureADMyOrg:仅限应用注册的组织目录中的帐户(单租户)
AzureADMultipleOrgs:任何组织目录中的帐户(多租户)
AzureADandPersonalMicrosoftAccount:任何组织目录中的帐户 (多租户) 和个人 Microsoft 帐户 (例如,Skype、Xbox 和 Outlook.com)
PersonalMicrosoftAccount:仅个人 Microsoft 帐户

注意⚠️:本篇文章仅适用于单租户,因为MSAL对不同的租户有不同的接入方式。

一、配置权限

默认情况下,aure平台只会给你分配用户读取权限,如果你需要读写onedrive的文件,还需要配置文件读写权限。

image-20210206115946455

找到Files权限组,根据你的需要,配置你需要的权限。

image-20210206120109792

二、 导入android MSAL库

https://docs.microsoft.com/zh-cn/azure/active-directory/develop/quickstart-v2-android#step-2-run-the-sample-app

App中

依赖中添加:

// api 30以上版本使用,否则无法编译成功,没到api30不要使用这个
implementation 'com.microsoft.identity.client:msal:2.+'
implementation 'com.microsoft.graph:microsoft-graph:1.5.+'

// 低于api 30使用
implementation 'com.microsoft.identity.client:msal:1.6.+'

如果无法成功添加依赖,可以尝试添加maven仓库

allprojects {
repositories {
mavenCentral()
google()
maven {
url 'https://pkgs.dev.azure.com/MicrosoftDeviceSDK/DuoSDK-Public/_packaging/Duo-SDK-Feed/maven/v1'
}
jcenter()
}
}

2.1 在raw中增加配置

创建名为auth_config_single_account.json的文件,这是单用户的,里面输入

{
"client_id" : "你的客户端id",
"authorization_user_agent" : "DEFAULT",
"redirect_uri" : "你的回调地址",
"account_mode" : "SINGLE",
"broker_redirect_uri_registered": true,
"authorities" : [
{
"type": "AAD",
"audience": {
"type": "你的应用类型:AzureADMyOrg、AzureADandPersonalMicrosoftAccount、PersonalMicrosoftAccount、AzureADMultipleOrgs",
"tenant_id": "你的tenantId"
}
}
]
}

这个便是MSAL配置中的生成的东西

image-20210205100654995

2.2 在清单中配置以下代码

<!--Intent filter to capture System Browser or Authenticator calling back to our app after sign-in-->
<activity
android:name="com.microsoft.identity.client.BrowserTabActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="msauth"
android:host="包名"
android:path="/签名hash" />
</intent-filter>
</activity>

三、代码集成

如下图所示,如果已经登陆过了,sdk会自动保存cookies信息到本地,如果本地没有cookie,则需要调用登陆接口跳转微软登陆界面进行登陆。

如果本地有cookie信息,需要通过本地的cookie信息获取到请求的token。

注意⚠️:如果是跳转登陆并且登陆成功后,是不需要重新获取token的。

ActivityDiagram1

3.1 初始化

private fun initOneDrive() {
PublicClientApplication.createSingleAccountPublicClientApplication(this,
R.raw.auth_config_single_account,
object : ISingleAccountApplicationCreatedListener {
override fun onCreated(application: ISingleAccountPublicClientApplication) {
/**
* This test app assumes that the app is only going to support one account.
* This requires "account_mode" : "SINGLE" in the config json file.
*/
oneDriveApp = application
loadAccount()
}
override fun onError(exception: MsalException) {
exception.printStackTrace()
}
})

3.2 账户登陆

  1. 检查本地是否有cookie信息

    /**
    * 加载用户,没有登陆过,则需要重新登陆
    */
    private fun loadAccount() {
    if (!this::oneDriveApp.isInitialized) {
    Log.e(TAG, "还没有初始化sdk")
    return
    }
    oneDriveApp.getCurrentAccountAsync(object : CurrentAccountCallback {
    override fun onAccountLoaded(activeAccount: IAccount?) {
    if (activeAccount == null) {
    Log.w(TAG, "用户还没有登陆")
    login()
    } else {
    Log.w(TAG, "已经登陆过,自动登陆,开始获取token")
    getTokenByAccountInfo(activeAccount)
    }
    }
    override fun onAccountChanged(
    priorAccount: IAccount?,
    currentAccount: IAccount?
    ) {
    Log.w(TAG, "账号已却换,重新获取token")
    if (currentAccount == null){
    Log.e(TAG, "当前账户为空")
    return
    }
    getTokenByAccountInfo(currentAccount)
    }
    override fun onError(exception: MsalException) {
    exception.printStackTrace()
    }
    })
    }
  2. 登陆(如果登陆成功,token会同步返回,不需要再调用接口获取token)

    private fun login() {
    oneDriveApp.signIn(this, "", getScopes(), object : AuthenticationCallback {
    override fun onSuccess(authenticationResult: IAuthenticationResult?) {
    authInfo = authenticationResult
    MSAL.updateAuthInfo(authInfo)
    Toast.makeText(this@OneDriveActivity, "登陆成功", Toast.LENGTH_SHORT)
    .show()
    Log.d(TAG, "登陆成功")
    }
    override fun onError(exception: MsalException?) {
    Toast.makeText(this@OneDriveActivity, "登陆失败", Toast.LENGTH_SHORT)
    .show()
    Log.d(TAG, "登陆失败")
    exception?.printStackTrace()
    }
    override fun onCancel() {
    Log.d(TAG, "取消登陆")
    Toast.makeText(this@OneDriveActivity, "登陆取消", Toast.LENGTH_SHORT)
    .show()
    }
    })
    }
  3. 通过cookie获取token

    /**
    * 根据用户信息获取token
    */
    private fun getTokenByAccountInfo(account: IAccount) {
    oneDriveApp.acquireTokenSilentAsync(getScopes(), account.authority, object : SilentAuthenticationCallback {
    override fun onSuccess(authenticationResult: IAuthenticationResult?) {
    Log.d(TAG, "获取token成功")
    authInfo = authenticationResult
    MSAL.updateAuthInfo(authInfo)
    }
    override fun onError(exception: MsalException) {
    exception.printStackTrace()
    Toast.makeText(this@OneDriveActivity, "获取token失败", Toast.LENGTH_SHORT).show()
    }
    })
    }

注意⚠️:getScopes() 为你的接口访问权限范围,根据在后台配置的权限进行填写便可。

image-20210206120237279

3.3 调用api

oneDrive 的sdk并没有直接调用接口的方法,需要用户自己请求MSAL的webApi接口实现自己的功能。像普通的http请求接口便可。

/**
* 获取应用的app文件夹列表
*/
@GET("users/{userId}/drive/special/{special-folder-name}/children")
suspend fun getAppFolder(
@Header(MSAL.TOKEN_KEY) authorization: String,
@Path("userId") userId: String,
@Path("special-folder-name") folderName: String
): MsalResponse<List<MsalSourceItem>>

API 地址

注意:没个接口都需要在header中设置token

headers.put["Authorization"] = "${authResult.getAccessToken()}"

X 常见问题

x.1 Manifest merger failed with multiple errors, see logs

错误信息:

[com.microsoft.identity:common:3.0.9] /Users/aria/.gradle/caches/transforms-2/files-2.1/9bbd0210b395b52a878222653b1cfa37/jetified-common-3.0.9/AndroidManifest.xml:20:9-59 Error:
Missing 'package' key attribute on element package at [com.microsoft.identity:common:3.0.9] AndroidManifest.xml:20:9-59
[com.microsoft.identity:common:3.0.9] /Users/aria/.gradle/caches/transforms-2/files-2.1/9bbd0210b395b52a878222653b1cfa37/jetified-common-3.0.9/AndroidManifest.xml:21:9-70 Error:
Missing 'package' key attribute on element package at [com.microsoft.identity:common:3.0.9] AndroidManifest.xml:21:9-70
[com.microsoft.identity:common:3.0.9] /Users/aria/.gradle/caches/transforms-2/files-2.1/9bbd0210b395b52a878222653b1cfa37/jetified-common-3.0.9/AndroidManifest.xml:22:9-77 Error:
Missing 'package' key attribute on element package at [com.microsoft.identity:common:3.0.9] AndroidManifest.xml:22:9-77
[com.microsoft.identity:common:3.0.9] /Users/aria/.gradle/caches/transforms-2/files-2.1/9bbd0210b395b52a878222653b1cfa37/jetified-common-3.0.9/AndroidManifest.xml Error:
Validation failed, exiting

原因:

你的api版本没有到30,你却使用了implementation 'com.microsoft.identity.client:msal:2.+' 版本的库

解决:

1、升级到api30

2、使用低版本的库

implementation 'com.microsoft.identity.client:msal:1.6.+'

x.2 AccountMode in configuration is not set to single.

错误信息:

com.microsoft.identity.client.exception.MsalClientException: AccountMode in configuration is not set to single. Cannot initialize single account PublicClientApplication.
at com.microsoft.identity.client.PublicClientApplication$11.onCreated(PublicClientApplication.java:981)
at com.microsoft.identity.client.PublicClientApplication$9.onTaskCompleted(PublicClientApplication.java:908)
at com.microsoft.identity.client.PublicClientApplication$9.onTaskCompleted(PublicClientApplication.java:894)
at com.microsoft.identity.common.internal.controllers.CommandDispatcher$3.run(CommandDispatcher.java:216)
at android.os.Handler.handleCallback(Handler.java:883)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7356)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)

原因:

你的auth_config_single_account.json是单账号模式的,因此需要在json文件中指定用户模式。

解决:

auth_config_single_account.json文件中添加"account_mode" : "SINGLE"

image-20210205150041586

x.2 只能使用手机号登陆,不能使用邮箱登陆

原因:

指定了Tenant_id租户id。

image-20210206114834177

解决:

auth_config_single_account.json移除"tenant_id": "consumers"

image-20210206114735746

参考地址

官方demo

API 地址