为你的 Android (Kotlin/Java) 应用添加认证 (Authentication)
本指南将向你展示如何将 Logto 集成到你的 Android 应用中。
- 示例基于 View system 和 View Model,但在使用 Jetpack Compose 时,概念是相同的。
- 示例是用 Kotlin 编写的,但对于 Java 来说,概念是相同的。
- 我们的 SDK repository 上提供了 Kotlin 和 Java 示例项目。
- 教程视频可以在我们的 YouTube channel 上观看。
前置条件
- 一个 Logto Cloud 账户或 自托管 Logto。
- 已创建的 Logto 原生应用。
- 一个 Kotlin Android 应用项目。
安装
Logto Android SDK 支持的最低 Android API 级别为 24。
Logto Android SDK 有两个主要版本:
- v3:在 Chrome Custom Tabs(系统浏览器)中打开登录体验,这样可以实现密钥登录(passkey sign-in)并共享浏览器会话。请注意,v3 移除了对 微信(原生) 和 支付宝(原生) 连接器的支持;你可以改用 微信(Web) 和 支付宝(Web),它们通过浏览器工作。如果你依赖原生连接器,请继续使用 v2。
- v2:在嵌入式 WebView 中打开登录体验,这是原生社交连接器所需的,但不支持 密钥登录(passkey sign-in)(WebView 不支持 WebAuthn,即密钥的底层标准)。
本指南涵盖了这两个版本。请在下方标签中选择你的版本,选择将在本指南中保持同步。
在安装 Logto Android SDK 之前,请确保在 Gradle 项目构建文件的仓库配置中添加了 mavenCentral():
dependencyResolutionManagement {
repositories {
mavenCentral()
}
}
将 Logto Android SDK 添加到你的依赖项中:
- v3
- v2
使用最新的 v3 版本作为依赖版本:
- Kotlin
- Groovy
dependencies {
implementation("io.logto.sdk:android:3.0.0")
}
dependencies {
implementation 'io.logto.sdk:android:3.0.0'
}
- Kotlin
- Groovy
dependencies {
implementation("io.logto.sdk:android:2.0.3")
}
dependencies {
implementation 'io.logto.sdk:android:2.0.3'
}
由于 SDK 需要访问互联网,你需要在 AndroidManifest.xml 文件中添加以下权限:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<!-- 添加网络权限 -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- 其他配置... -->
</manifest>
集成
初始化 LogtoClient
创建一个 LogtoViewModel.kt 并在此视图模型中初始化 LogtoClient:
//...with other imports
import io.logto.sdk.android.LogtoClient
import io.logto.sdk.android.type.LogtoConfig
class LogtoViewModel(application: Application) : AndroidViewModel(application) {
private val logtoConfig = LogtoConfig(
endpoint = "<your-logto-endpoint>",
appId = "<your-app-id>",
scopes = null,
resources = null,
usingPersistStorage = true,
)
private val logtoClient = LogtoClient(logtoConfig, application)
companion object {
val Factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(
modelClass: Class<T>,
extras: CreationExtras
): T {
// 从 extras 中获取 Application 对象
val application = checkNotNull(extras[APPLICATION_KEY])
return LogtoViewModel(application) as T
}
}
}
}
然后,为你的 MainActivity.kt 创建一个 LogtoViewModel:
//...with other imports
class MainActivity : AppCompatActivity() {
private val logtoViewModel: LogtoViewModel by viewModels { LogtoViewModel.Factory }
//...other codes
}
配置重定向 URI
在我们深入细节之前,下面是终端用户体验的快速概览。登录流程可以简化为如下:
- 你的应用调用登录方法。
- 用户被重定向到 Logto 登录页面。对于原生应用,会打开系统浏览器。
- 用户完成登录后被重定向回你的应用(配置为重定向 URI)。
关于基于重定向的登录
- 此认证 (Authentication) 过程遵循 OpenID Connect (OIDC) 协议,Logto 强制执行严格的安全措施以保护用户登录。
- 如果你有多个应用程序,可以使用相同的身份提供商 (IdP)(日志 (Logto))。一旦用户登录到一个应用程序,当用户访问另一个应用程序时,Logto 将自动完成登录过程。
要了解有关基于重定向的登录的原理和好处的更多信息,请参阅 Logto 登录体验解释。
让我们切换到 Logto Console 的应用详情页面。添加一个重定向 URI io.logto.android://io.logto.sample/callback 并点击“保存更改”。
在 Android 中,重定向 URI 遵循如下模式:$(LOGTO_REDIRECT_SCHEME)://$(YOUR_APP_PACKAGE)/callback:
LOGTO_REDIRECT_SCHEME应为反向域名格式的自定义 scheme。YOUR_APP_PACKAGE是你的应用包名。
假设你将 io.logto.android 作为自定义 LOGTO_REDIRECT_SCHEME,io.logto.sample 作为你的应用包名,则重定向 URI 应为 io.logto.android://io.logto.sample/callback。
- v3
- v2
在 v3 中,登录体验会在 Custom Tab(系统浏览器)中打开,重定向通过操作系统级别的 intent filter 路由回你的应用。你需要在应用的构建文件中用 logtoRedirectScheme manifest 占位符声明你的重定向 URI 的 scheme:
- Kotlin
- Groovy
android {
defaultConfig {
manifestPlaceholders["logtoRedirectScheme"] = "io.logto.android"
}
}
android {
defaultConfig {
manifestPlaceholders.logtoRedirectScheme = 'io.logto.android'
}
}
此外,v3 通过 Android 的 intent filter 匹配强制执行重定向 URI 模式,因此偏离该模式的重定向 URI 永远不会传递到你的应用:
- scheme 必须等于
logtoRedirectSchememanifest 占位符。 - host 必须为你的
applicationId。 - path 必须为
/callback。
请保持 scheme 和 host 为小写,因为 intent filter 匹配区分大小写,且浏览器会将 scheme 转为小写。
使用 App Links 替代自定义 scheme?
如需使用 Android App Links(你拥有域名下的 https 重定向 URI)替代自定义 scheme:
-
在
https://your.domain/.well-known/assetlinks.json上托管 Digital Asset Links 文件,声明你的 application ID 和签名证书的 SHA-256 指纹。如果使用 Play App Signing 发布,你可以在 Play Console 的 设置 > 应用签名 下找到发布指纹。该文件必须以Content-Type: application/json、HTTP 200 且无重定向的方式提供。 -
在你的
AndroidManifest.xml文件中,给 SDK 的重定向接收 Activityio.logto.sdk.android.auth.logto.LogtoRedirectReceiverActivity声明 App Links intent filter。如果你完全不使用自定义 scheme,可以用tools:node="removeAll"移除 SDK 内置的 filter,此时logtoRedirectSchememanifest 占位符也不再需要:AndroidManifest.xml<manifest xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"><application><activity android:name="io.logto.sdk.android.auth.logto.LogtoRedirectReceiverActivity"><!-- 若要保留自定义 scheme 重定向,请不要移除此行。 --><intent-filter tools:node="removeAll" /><intent-filter android:autoVerify="true"><action android:name="android.intent.action.VIEW" /><category android:name="android.intent.category.DEFAULT" /><category android:name="android.intent.category.BROWSABLE" /><data android:scheme="https" android:host="your.domain" android:path="/callback" /></intent-filter></activity></application></manifest> -
在 Logto 控制台的应用详情页添加
https://your.domain/callback作为重定向 URI(如用于登出,也作为登出后重定向 URI),并传递给signIn/signOut。
请注意,回调现在是你域名下的真实 URL,因此需要在该地址提供一个备用页面(例如“返回应用”按钮),以便在浏览器未能通过服务器重定向启动 App Links 时使用。该按钮只需链接到当前 URL(如将 href 设为 window.location.href):授权参数已在查询字符串中,用户点击可再次尝试路由到你的应用。在 Android 12+ 上,未验证的域名不会打开应用,因此损坏的 assetlinks.json 会静默失败。你可以用 adb shell pm get-app-links <applicationId> 检查验证状态。
无需额外设置。登录体验会在内嵌 WebView 中打开,SDK 会在 WebView 内部拦截重定向。
实现登录与登出
在调用 logtoClient.signIn 之前,请确保你已在管理控制台中正确配置了重定向 URI。
你可以使用 logtoClient.signIn 让用户登录,使用 logtoClient.signOut 让用户登出。
- v3
- v2
在 v3 中,logtoClient.signOut 会执行完整的登出操作:它会清除本地凭据、撤销刷新令牌 (Refresh token),并通过在浏览器中打开结束会话端点来结束 Logto 会话。随后,浏览器会通过登出后重定向 URI 跳转回你的应用。在使用前,请前往 Logto Console 的应用详情页,添加登出后重定向 URI io.logto.android://io.logto.sample/callback 并点击“保存更改”。登出后重定向 URI 的格式与重定向 URI 相同,其 scheme 也必须与 logtoRedirectScheme manifest 占位符一致。
例如,在 Android 应用中:
//...with other imports
class LogtoViewModel(application: Application) : AndroidViewModel(application) {
// ...other codes
// 添加一个 live data 用于监听认证 (Authentication) 状态
private val _authenticated = MutableLiveData(logtoClient.isAuthenticated)
val authenticated: LiveData<Boolean>
get() = _authenticated
fun signIn(context: Activity) {
logtoClient.signIn(context, "io.logto.android://io.logto.sample/callback") { logtoException ->
logtoException?.let { println(it) }
// 更新 live data
_authenticated.postValue(logtoClient.isAuthenticated)
}
}
fun signOut(context: Activity) {
logtoClient.signOut(context, "io.logto.android://io.logto.sample/callback") { logtoException ->
logtoException?.let { println(it) }
// 更新 live data
_authenticated.postValue(logtoClient.isAuthenticated)
}
}
}
然后在你的 activity 中调用 signIn 和 signOut 方法:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
//...other codes
// 假设你的布局中有一个 id 为 "sign_in_button" 的按钮
val signInButton = findViewById<Button>(R.id.sign_in_button)
signInButton.setOnClickListener {
logtoViewModel.signIn(this)
}
// 假设你的布局中有一个 id 为 "sign_out_button" 的按钮
val signOutButton = findViewById<Button>(R.id.sign_out_button)
signOutButton.setOnClickListener {
if (logtoViewModel.authenticated) { // 检查用户是否已认证 (Authentication)
logtoViewModel.signOut(this)
}
}
// 监听认证 (Authentication) 状态以更新 UI
logtoViewModel.authenticated.observe(this) { authenticated ->
if (authenticated) {
// 用户已认证 (Authentication)
signInButton.visibility = View.GONE
signOutButton.visibility = View.VISIBLE
} else {
// 用户未认证 (Authentication)
signInButton.visibility = View.VISIBLE
signOutButton.visibility = View.GONE
}
}
}
}
- 你也可以直接调用
logtoClient.signOut(context),不带登出后重定向 URI。这种情况下无需在 Console 配置:浏览器会显示 Logto 登出页面,用户手动关闭后返回应用。 - 如果没有 UI 上下文,你可以调用
logtoClient.clearCredentials来清除本地凭据并撤销刷新令牌 (Refresh token)。注意,这会保留浏览器中的 Logto 会话,因此下次signIn可能会通过该会话自动登录用户。
例如,在 Android 应用中:
//...with other imports
class LogtoViewModel(application: Application) : AndroidViewModel(application) {
// ...other codes
// 添加一个 live data 用于监听认证 (Authentication) 状态
private val _authenticated = MutableLiveData(logtoClient.isAuthenticated)
val authenticated: LiveData<Boolean>
get() = _authenticated
fun signIn(context: Activity) {
logtoClient.signIn(context, "io.logto.android://io.logto.sample/callback") { logtoException ->
logtoException?.let { println(it) }
// 更新 live data
_authenticated.postValue(logtoClient.isAuthenticated)
}
}
fun signOut() {
logtoClient.signOut { logtoException ->
logtoException?.let { println(it) }
// 更新 live data
_authenticated.postValue(logtoClient.isAuthenticated)
}
}
}
然后在你的 activity 中调用 signIn 和 signOut 方法:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
//...other codes
// 假设你的布局中有一个 id 为 "sign_in_button" 的按钮
val signInButton = findViewById<Button>(R.id.sign_in_button)
signInButton.setOnClickListener {
logtoViewModel.signIn(this)
}
// 假设你的布局中有一个 id 为 "sign_out_button" 的按钮
val signOutButton = findViewById<Button>(R.id.sign_out_button)
signOutButton.setOnClickListener {
if (logtoViewModel.authenticated) { // 检查用户是否已认证 (Authentication)
logtoViewModel.signOut()
}
}
// 监听认证 (Authentication) 状态以更新 UI
logtoViewModel.authenticated.observe(this) { authenticated ->
if (authenticated) {
// 用户已认证 (Authentication)
signInButton.visibility = View.GONE
signOutButton.visibility = View.VISIBLE
} else {
// 用户未认证 (Authentication)
signInButton.visibility = View.VISIBLE
signOutButton.visibility = View.GONE
}
}
}
}
检查点:测试你的应用程序
现在,你可以测试你的应用程序:
- 运行你的应用程序,你将看到登录按钮。
- 点击登录按钮,SDK 将初始化登录过程并将你重定向到 Logto 登录页面。
- 登录后,你将被重定向回你的应用程序,并看到登出按钮。
- 点击登出按钮以清除令牌存储并登出。
获取用户信息
展示用户信息
要展示用户信息,你可以使用 logtoClient.getIdTokenClaims() 方法。例如,你可以在 ViewModel 中获取用户信息,然后在 Activity 中展示:
class LogtoViewModel(application: Application) : AndroidViewModel(application) {
// ...其他代码
// 添加 LiveData 以监听 id token claims
private val _idTokenClaims = MutableLiveData<IdTokenClaims>()
val idTokenClaims: LiveData<IdTokenClaims>
get() = _idTokenClaims
fun getIdTokenClaims() {
logtoClient.getIdTokenClaims { logtoException, idTokenClaims ->
logtoException?.let { _logtoException.postValue(it) } ?: _idTokenClaims.postValue(idTokenClaims)
}
}
}
//...with other imports
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
//...其他代码
// 假设你的布局中有一个 id 为 `user_info_text_view` 的 TextView
val userInfoResponseTextView: TextView = findViewById(R.id.user_info_text_view)
logtoViewModel.userInfoResponse.observe(this) { userInfoResponse ->
userInfoResponseTextView.text = if (userInfoResponse !== null) {
val json = Gson().toJson(userInfoResponse, UserInfoResponse::class.java)
JSONObject(json).toString(2)
} else {
""
}
}
}
}
请求额外声明 (Claims)
你可能会发现从 logtoClient.getIdTokenClaims() 返回的对象中缺少一些用户信息。这是因为 OAuth 2.0 和 OpenID Connect (OIDC) 的设计遵循最小权限原则 (PoLP),而 Logto 是基于这些标准构建的。
默认情况下,返回的声明(Claim)是有限的。如果你需要更多信息,可以请求额外的权限(Scope)以访问更多的声明(Claim)。
“声明(Claim)”是关于主体的断言;“权限(Scope)”是一组声明。在当前情况下,声明是关于用户的一条信息。
以下是权限(Scope)与声明(Claim)关系的非规范性示例:
“sub” 声明(Claim)表示“主体(Subject)”,即用户的唯一标识符(例如用户 ID)。
Logto SDK 将始终请求三个权限(Scope):openid、profile 和 offline_access。
如需请求额外的权限 (Scopes),你可以将权限传递给 LogtoConfig 对象。例如:
private val logtoConfig = LogtoConfig(
// ...其他配置
scopes = listOf("email", "phone"), // 或 `listOf(UserScope.EMAIL, UserScope.PHONE)`
)
然后你可以在 logtoClient.getIdTokenClaims() 的返回值中访问这些额外的声明 (Claims):
logtoClient.getIdTokenClaims { logtoException, idTokenClaims ->
println("IdTokenClaims:$idTokenClaims")
}
// 现在你可以访问额外的声明 claims.email、claims.phone 等
需要网络请求的声明
为了防止 ID 令牌 (ID token) 过大,一些声明需要通过网络请求来获取。例如,即使在权限中请求了 custom_data 声明,它也不会包含在用户对象中。要访问这些声明,你可以使用 logtoClient.fetchUserInfo() 方法:
logtoClient.fetchUserInfo {_, userInfoResponse ->
println("UserInfoResponse:$userInfoResponse")
}
// 现在你可以访问声明 `userInfo.custom_data`
权限 (Scopes) 与声明 (Claims)
Logto 使用 OIDC 权限 (Scopes) 和声明 (Claims) 约定 来定义用于从 ID 令牌 (ID token) 和 OIDC userinfo 端点 获取用户信息的权限 (Scopes) 和声明 (Claims)。"scope" 和 "claim" 都是 OAuth 2.0 和 OpenID Connect (OIDC) 规范中的术语。
对于标准 OIDC 声明 (Claims),其在 ID 令牌 (ID token) 中的包含严格由所请求的权限 (Scopes) 决定。扩展声明 (Claims)(如 custom_data 和 organizations)可以通过 自定义 ID 令牌 (Custom ID token) 设置额外配置到 ID 令牌 (ID token) 中。
以下是支持的权限 (Scopes) 及其对应的声明 (Claims) 列表:
标准 OIDC 权限 (Scopes)
openid(默认)
| Claim name | Type | Description |
|---|---|---|
| sub | string | 用户的唯一标识符 |
profile(默认)
| Claim name | Type | Description |
|---|---|---|
| name | string | 用户的全名 |
| username | string | 用户名 |
| picture | string | 终端用户头像的 URL。该 URL 必须指向一个图片文件(例如 PNG、JPEG 或 GIF 图片文件),而不是包含图片的网页。请注意,该 URL 应专门指向适合在描述终端用户时显示的头像,而不是终端用户拍摄的任意照片。 |
| created_at | number | 终端用户创建的时间。该时间以自 Unix 纪元(1970-01-01T00:00:00Z)以来的毫秒数表示。 |
| updated_at | number | 终端用户信息最后更新时间。该时间以自 Unix 纪元(1970-01-01T00:00:00Z)以来的毫秒数表示。 |
其他 标准声明 (Claims) 包括 family_name、given_name、middle_name、nickname、preferred_username、profile、website、gender、birthdate、zoneinfo 和 locale 也会包含在 profile 权限 (Scope) 中,无需请求 userinfo 端点。与上表声明 (Claims) 不同的是,这些声明 (Claims) 仅在其值不为空时返回,而上表声明 (Claims) 的值为空时会返回 null。
与标准声明 (Claims) 不同,created_at 和 updated_at 声明 (Claims) 使用的是毫秒而不是秒。
email
| Claim name | Type | Description |
|---|---|---|
string | 用户的电子邮件地址 | |
| email_verified | boolean | 电子邮件地址是否已被验证 |
phone
| Claim name | Type | Description |
|---|---|---|
| phone_number | string | 用户的电话号码 |
| phone_number_verified | boolean | 电话号码是否已被验证 |
address
关于 address 声明 (Claim) 的详细信息,请参阅 OpenID Connect Core 1.0。
带有 (默认) 标记的权限 (Scopes) 总是由 Logto SDK 请求。当请求相应权限 (Scope) 时,标准 OIDC 权限 (Scopes) 下的声明 (Claims) 总是包含在 ID 令牌 (ID token) 中——无法关闭。
扩展权限 (Scopes)
以下权限 (Scopes) 由 Logto 扩展,并将通过 userinfo 端点 返回声明 (Claims)。这些声明 (Claims) 也可以通过 控制台 > 自定义 JWT 配置为直接包含在 ID 令牌 (ID token) 中。详见 自定义 ID 令牌 (ID token)。
custom_data
| Claim name | Type | Description | Included in ID token by default |
|---|---|---|---|
| custom_data | object | 用户的自定义数据 |
identities
| Claim name | Type | Description | Included in ID token by default |
|---|---|---|---|
| identities | object | 用户关联的身份 | |
| sso_identities | array | 用户关联的 SSO 身份 |
roles
| Claim name | Type | Description | Included in ID token by default |
|---|---|---|---|
| roles | string[] | 用户的角色 (Roles) | ✅ |
urn:logto:scope:organizations
| Claim name | Type | Description | Included in ID token by default |
|---|---|---|---|
| organizations | string[] | 用户所属的组织 (Organizations) ID | ✅ |
| organization_data | object[] | 用户所属的组织 (Organizations) 数据 |
这些组织 (Organizations) 声明 (Claims) 也可以在使用 不透明令牌 (Opaque token) 时通过 userinfo 端点获取。但不透明令牌 (Opaque tokens) 不能作为组织令牌 (Organization tokens) 用于访问组织专属资源。详见 不透明令牌 (Opaque token) 与组织 (Organizations)。
urn:logto:scope:organization_roles
| Claim name | Type | Description | Included in ID token by default |
|---|---|---|---|
| organization_roles | string[] | 用户所属组织 (Organizations) 的角色 (Roles),格式为 <organization_id>:<role_name> | ✅ |
API 资源与组织 (Organizations)
我们建议首先阅读 🔐 基于角色的访问控制 (RBAC),以了解 Logto RBAC 的基本概念以及如何正确设置 API 资源。
配置 Logto 客户端
一旦你设置了 API 资源,就可以在应用中配置 Logto 时添加它们:
val logtoConfig = LogtoConfig(
//...other configs
resources = listOf("https://shopping.your-app.com/api", "https://store.your-app.com/api"), // 添加 API 资源
)
每个 API 资源都有其自己的权限(权限)。
例如,https://shopping.your-app.com/api 资源具有 shopping:read 和 shopping:write 权限,而 https://store.your-app.com/api 资源具有 store:read 和 store:write 权限。
要请求这些权限,你可以在应用中配置 Logto 时添加它们:
val logtoConfig = LogtoConfig(
// ..other configs
scopes = listOf("shopping:read", "shopping:write", "store:read", "store:write"),
resources = listOf("https://shopping.your-app.com/api", "https://store.your-app.com/api"),
)
你可能会注意到权限是与 API 资源分开定义的。这是因为 OAuth 2.0 的资源指示器 指定请求的最终权限将是所有目标服务中所有权限的笛卡尔积。
因此,在上述情况下,权限可以从 Logto 中的定义简化,两个 API 资源都可以拥有 read 和 write 权限,而无需前缀。然后,在 Logto 配置中:
val logtoConfig = LogtoConfig(
// ...other configs
scopes = listOf("read", "write"),
resources = listOf("https://shopping.your-app.com/api", "https://store.your-app.com/api"),
)
对于每个 API 资源,它将请求 read 和 write 权限。
请求 API 资源中未定义的权限是可以的。例如,即使 API 资源没有可用的 email 权限,你也可以请求 email 权限。不可用的权限将被安全地忽略。
成功登录后,Logto 将根据用户的角色向 API 资源发布适当的权限。
获取 API 资源的访问令牌 (Access Token)
要获取特定 API 资源的访问令牌 (access token),你可以使用 getAccessToken 方法:
logtoClient.getAccessToken("https://shopping.your-app.com/api") { logtoException, 访问令牌 (Access token) ->
logtoException?.let { println(it) }
访问令牌 (Access token)?.let { println(it) }
}
此方法将返回一个 JWT 访问令牌 (access token),当用户具有相关权限时,可以用来访问 API 资源。如果当前缓存的访问令牌 (access token) 已过期,此方法将自动尝试使用刷新令牌 (refresh token) 获取新的访问令牌 (access token)。
获取组织令牌 (Organization Tokens)
如果你对组织不熟悉,请阅读 🏢 组织(多租户) 以开始了解。
在配置 Logto 客户端时,你需要添加 UserScope.Organizations 权限:
val logtoConfig = LogtoConfig(
// ...other configs
scopes = listOf(UserScope.Organizations),
)
用户登录后,你可以获取用户的组织令牌:
// 将参数替换为有效的组织 ID。
// 用户的有效组织 ID 可以在 ID 令牌声明 `organizations` 中找到。
logtoClient.getOrganizationToken("organization-id") { logtoException, organizationToken ->
logtoException?.let { println(it) }
organizationToken?.let { println(it) }
}
// 或者
logtoClient.getOrganizationTokenClaims("organization-id") { logtoException, claims ->
logtoException?.let { println(it) }
claims?.let { println(it) }
}
组织 API 资源
要获取组织中 API 资源的访问令牌 (Access token),你可以使用 getAccessToken 方法,并将 API 资源和组织 ID 作为参数:
logtoClient.getAccessToken(
'https://shopping.your-app.com/api',
organizationId
) { logtoException, accessToken ->
println("AccessToken:$accessToken")
}