开始使用Kotlin进行安卓认证

9,250 阅读27分钟

如果你为Android制作应用程序,你最终将不得不建立一个需要用户登录的应用程序。本文将向你展示如何使用Auth0为Android应用添加功能齐全的登录和注销,这样你就可以把时间和精力集中在实际功能上。

添加认证--"登录/注销 "的更正式术语--只是看起来简单。它往往变成了自己的项目。仅仅是处理用户想要登录的多种方式就会很快成为一项耗费精力的任务。你还得处理用户管理、扩展和安全问题,每个问题都有几十个考虑因素、风险、问题和边缘案例。

Auth0解决了这个问题。有了Auth0和几行代码,你的应用程序就可以拥有一个全功能的系统,支持用用户名/密码组合登录和单点登录。你还可以添加社交账户、多因素认证、无密码登录、生物识别等功能。你也不必自己更新或处理 "幕后 "问题!相反,你可以把精力集中在工作上。相反,你可以把精力集中在你的应用程序的工作上。

如果你想在关注构建和执行步骤的同时略过这些内容,请寻找🛠表情符号。

你要构建的内容

你将使用Auth0构建一个单屏幕的Android应用,允许用户登录和退出。我特意让它尽可能的简单,以保持对认证的关注。

在登录时,用户将能够从他们的用户资料中看到以下信息。

  • 全名
  • 电子邮件地址
  • 图片

当你启动完成的应用程序时,你会看到一个问候语、一个登录按钮和一个禁用的注销按钮。

The app’s “Welcome” screen. The “Log In” button is enabled, and the “Log Out” button is disabled.

按下登录按钮,用户会进入Auth0通用登录界面。它出现在一个嵌入你的应用程序的网络浏览器视图中。下面是它在模拟器中的样子...

The default Auth0 Universal Login web page, as viewed in an emulator, with Auth0 logo and “email address” and “password” fields.

...这是它在设备上的样子。

The default Auth0 Universal Login web page, as viewed in an emulator, with Auth0 logo and “email address” and “password” fields.

当你使用Auth0为你的应用程序添加登录/注销功能时,你将认证委托给一个由Auth0托管的登录页面。你可能已经在谷歌的网络应用中看到了这一点,比如Gmail和YouTube。这些服务重定向你使用accounts.google.com来登录。登录后,谷歌将你作为一个已登录的用户返回到网络应用中。

如果你担心使用Auth0的 "通用登录 "意味着你的应用程序的登录屏幕将停留在默认的Auth0 "外观和感觉 "上,我有个好消息要告诉你:你可以定制它以符合你的应用程序或组织的品牌形象。

通用登录页面使你不必为认证系统编码。它为你的应用程序提供了一个自成一体的登录框,具有若干功能,以提供良好的用户体验。

如果用户输入了一个无效的电子邮件地址/密码组合,它会显示一个错误信息,并给他们另一个登录的机会。

Universal Login displaying the “wrong email or password” message.

有两种方法可以退出 "通用登录 "屏幕。有一种是 "不开心的路径",即用户按下屏幕左上角的 "取消"按钮,这样就可以退出通用登录屏幕,并将其返回到打开的屏幕。当应用程序检测到用户已经取消了登录,它会显示一个Snackbar,通知用户他们需要登录才能使用该应用程序。

The App’s “Welcome” screen, with Snackbar that reads “You need to log in to use the app.”

当用户输入有效的电子邮件地址/密码组合时,通用登录的 "快乐路径 "出现了。当这种情况发生时,Auth0认证了用户,嵌入式网页视图和通用登录将消失,控制权将返回到应用程序,现在看起来像这样。

The app in its “logged in” state. The “Log In” button is disabled, the “Log Out” button is enabled, and the app’s other controls are visible.

下面是用户登录后的变化。

  • 屏幕顶部的标题文字现在说,"你已经登录了"。
  • 登录按钮被禁用,而注销按钮被启用。
  • 与用户账户相关的姓名、电子邮件地址和照片出现在屏幕上。
  • 窗口显示 "成功:"和用户的名字。

正如你所期望的那样,用户通过按下注销按钮来注销,这时他们会进入这个屏幕。

The app is in its “logged out” state. The “Log In” button is enabled, the “Log Out” button is disabled, and the app’s other controls are hidden.

请注意。

  • 屏幕上方的文字说,"你已经注销了"。
  • 登录按钮被启用,而注销按钮被禁用。
  • 登录时可见的其他控件不再可见。

你将需要什么

你将需要以下东西来建立这个应用程序。

1.一个Auth0账户

该应用程序使用Auth0来验证用户,这意味着你需要一个Auth0账户。你可以注册一个免费账户,它可以让你在10个应用程序中添加登录/注销,支持7000个用户和无限制的登录--足够满足你的原型设计、开发和测试需求。

2.一个安卓开发设置

要为安卓开发应用程序,请确保你有以下条件,按以下顺序:

  • 任何运行Linux、macOS或2013年或以后的Windows的电脑,至少有8GB内存。当涉及到内存时,一般来说越多越好。
  • Java SE开发工具包(JDK),版本11或更高。你可以通过打开命令行界面并输入以下内容来了解你电脑上的版本 java --version.
  • Android Studio。版本3.6(2020年2月)或更高版本。在写这篇文章时,我使用了Android Studio当前的稳定版本:2021.2.1版,也被称为 "Chipmunk"。
  • 至少有一个Android SDK(软件开发工具包)平台。你可以在Android Studio中确认你是否有一个(如果没有就安装一个)。打开工具SDK管理器。你会看到一个Android SDK平台的列表。选择当前的SDK在撰写本文时为Android 11.0 (R)),点击应用按钮,并在出现的确认对话框中点击确定按钮。等待SDK平台的安装,安装完成后点击完成按钮。
  • 一个安卓设备,真实的或虚拟的。
    • **使用一个真实的设备。**用USB线将设备连接到你的电脑。确保你的设备有开发选项和USB调试功能。
    • **使用一个虚拟设备。**使用Android Studio,你可以建立一个虚拟设备(模拟器),在你的电脑上运行。下面是我制作的虚拟设备的配方,它可以模拟当前型号的廉价Android手机。
      1. 打开工具AVD管理器(AVD是 "Android虚拟设备 "的简称)。你的虚拟设备"窗口将出现。点击创建虚拟设备...按钮。
      2. 选择硬件窗口将出现。在手机类别中,选择Pixel 3a并点击下一步按钮。
      3. 系统图像窗口将出现,你会看到一个Android版本的列表。选择R(API 30,也被称为Android 11.0)。如果你在R旁边看到一个下载链接,点击它,等待操作系统下载,然后点击完成按钮。然后点击 "下一步"按钮。
      4. Android虚拟设备(AVD)窗口将出现。AVD名称字段应该包含Pixel 3a API 30;下面的两行应该有Pixel 3a(合理的 "代表 "手机,在撰写本文时已发布三年)和R的标题,在启动方向部分,应该选择纵向。点击 "完成"按钮。
      5. 你将回到 "你的虚拟设备"窗口。列表中现在将包含Pixel 3a API 30,当你运行该应用程序时,该设备将可供你使用。

3.对安卓开发有一定的熟悉程度。

如果你是Android开发或Kotlin编程语言的新手,你会发现 Kotlin中的安卓基础知识是一个很好的介绍。

第一个步骤

下载并运行启动器项目

为了使本教程专注于在Android应用中实现Auth0认证,我创建了一个启动项目,你可以下载。它包含一个已经布置好控件的活动,并包括所有必要的文件。从这个项目开始,你就可以专注于在应用程序中添加登录和注销功能,而不会受到诸如构建用户界面的干扰。

🛠 下载包含应用程序的启动和完成项目的.zip文件(257 KB)并解压。这将在你的本地驱动器上创建一个 get-started-android-authentication-kotlin-main在你的本地驱动器上创建一个文件夹。

🛠 启动Android Studio并打开启动项目。通过选择文件打开,导航到 Android Login (starter)文件夹,然后点击 "打开"按钮。

该项目应该只需要几秒钟就能加载到Android Studio。

🛠 确认启动项目可以运行。从设备菜单中选择一个模拟器或设备,并运行该应用程序。它应该看起来像这样。

The starter app’s initial screen.

现在,该应用程序只是显示 "欢迎 "屏幕。你可以按下按钮,但它们还不会做任何事情。

获取应用程序的软件包名称

在我们设置Auth0方面的东西之前,还有一项任务要执行:你需要获得应用程序的包名,即唯一标识Android应用程序的字符串。对于启动项目,包的名称是 com.auth0.androidlogin.

如果你需要向现有的安卓项目添加认证,你可以在其项目清单文件中使用该应用的包名,其中包含了构建工具和安卓操作系统的信息。

清单文件。 AndroidManifest.xml,是一个XML文件。您可以在该文件的manifest 标签中找到应用程序的包名,在其 package属性中找到应用程序的包名。在下面的例子中,包的名称是 com.example.packagename:

<!-- Example AndroidManifest.xml file -->

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.example.packagename">
    
<!-- The rest of the file goes here... -->

记住这个包的名字--你将在下一步使用它!再一次,启动项目中的应用程序包名是 com.auth0.androidlogin.

在Auth0仪表板中注册应用程序

下一步是在你的Auth0仪表盘中注册该应用。在这个过程中,你将。

  1. 将该应用添加到你的Auth0仪表板的注册应用列表中。
  2. 收集应用程序委托登录/注销给Auth0所需的两个信息:你的租户的域名和Auth0将分配给应用程序的客户ID。
  3. 向Auth0提供必要的回调URL以联系应用程序:一个是在登录过程结束时调用,另一个是在注销过程结束时调用。

🚨 要执行这一步骤,你需要一个Auth0账户。🚨

🛠 如果你已经有一个Auth0账户,请登录,跳过下一节,然后进入标题为的部分 将应用程序添加到应用程序列表中。

如果你还没有一个Auth0账户...

🛠 ...就去注册一个吧!这是免费的,而且我们已经非常小心地使这个过程尽可能地无痛。

将该应用程序添加到应用程序列表中

🛠 在Auth0仪表板的左侧菜单中,点击应用程序

The main page of the Auth0 dashboard. The reader is directed to expand the “Applications” menu.

🛠 这将展开应用程序菜单。选择该菜单中的第一个项目,它也有一个名字叫 "应用程序"。

The main page of the Auth0 dashboard, with the “Applications” menu expanded. The reader is directed to select the “Applications” menu item.

现在你将进入 "应用程序"页面。它列出了所有你已经注册使用Auth0进行认证和授权的应用程序。

🛠 让我们来注册应用程序。点击页面右上方的 "创建应用程序"按钮来完成。

The “Applications” page. The reader is directed to click the “Create Application” button.

你会看到这个对话框出现。

The “Create application” dialog. The application’s name is set to “Android Login” and the selected application type is “Native.”

🛠 你需要提供两个信息才能继续。

  • 在名称栏中输入一个应用程序的名称。使用与你的Android Studio项目相同的名称可能是最简单的(如果你一直按照我的例子,使用名称Android Login )。
  • 指定应用程序的类型,在本例中是Native

点击创建。应用程序的快速启动页面将出现。

The “Quick Start” page. It contains several icons representing an operating system or platform.

这个页面为几个不同的平台提供了现成的项目,你可以把它们作为一个将登录/注销委托给Auth0的应用程序的基础。在这个练习中,你不会使用它们中的任何一个;相反,你将使用几个Auth0库并自己编写代码。这样做更有教育意义,更重要的是,更有乐趣

🛠 单击 "设置"选项卡,这将带你到这个页面。

The “Application” page’s “Settings” tab. The reader is directed to copy the values of the “Domain” and “Client ID” text fields

你要在这个页面上做两件关键的事情。

  1. 获取应用程序需要知道的关于Auth0的信息,以及
  2. 提供Auth0需要知道的关于该应用的信息。

让我们来处理第一件事,即获取应用程序需要的信息,即。

  • **域名。**你需要它来建立应用程序用于联系Auth0的URL。它可以唯一地识别你的Auth0租户,即你在你的Auth0账户中注册的应用程序、用户和其他信息的集合。
  • **客户端ID,**Auth0分配给应用程序的标识符。这是Auth0知道它在与哪个应用合作的方式。

🛠通过复制域名客户ID字段的内容来获得这一信息,以便以后参考。你很快就会把它们输入到你的Android Studio项目中。

🛠 向下滚动页面到应用程序URI部分。

The “Application URIs” section of the page. The reader is told that they’ll need to fill out the “Allowed Callback URLs” and “Allowed Logout URLs” fields.

这是你提供Auth0需要知道的关于应用程序的两个信息的地方,它们是:

  1. 回调URL:用户成功登录后Auth0将重定向到的URL。可以有不止一个。
  2. 注销URL:用户注销后Auth0将重定向到的URL。可以有一个以上的。

如果你想知道URI和URL的区别是什么,我们在这篇文章中为你提供了答案。 URL、URI、URN:有什么区别?

你可能在想"等一下--我在写一个Android应用。它没有你用URL导航的网页,而是在控制器里有底层代码的视图 "

你说得很对。在本地应用程序的情况下,回调和注销URL是相同的字符串,Auth0将该字符串发送给应用程序,以通知它用户已经登录或注销。

安卓本地应用程序用于回调URL和注销URL的字符串遵循以下格式。

{SCHEME}://{YOUR_DOMAIN}/android/{YOUR_APP_PACKAGE_NAME}/callback

🛠 要构建该字符串,请执行以下操作:

  • 替换为 {SCHEME}app{SCHEME}是URL的协议,如果你在写一个网络应用,这个值应该是http ,或者更好,https 。由于这是一个Android原生的应用程序,你可以为这个值选择任何字符串。我喜欢使用app
  • 替换为 {YOUR_DOMAIN}用你在本页前面看到的域名字段的值。
  • 替换为 {YOUR_APP_PACKAGE_NAME}替换为该应用的捆绑标识符。如果你没有在启动项目中改变包的名称,这个值就是 com.auth0.Android-Login.

🛠 在 "允许的回调URL"和 "允许的登录URL"两个字段中输入你刚刚构建的URL。记住,两个字段都要输入相同的URL。

🛠 你已经完成了本页需要做的一切。向下滚动到页面的底部,点击保存更改按钮。

The bottom of the page features the “Save Changes” button. An arrow directs the reader to click the button.

如果你的租户没有任何用户,请创建一个用户

如果你刚刚创建了一个Auth0账户,你的租户是全新的。它不会有任何用户账户,所以不会有任何方式登录到应用程序。如果是这种情况,请按照以下步骤创建一个用户。

🛠 在Auth0仪表板左侧的菜单中,点击用户管理

The bottom of the page. An arrow directs the reader to expand the “User Management” menu.

🛠 这将展开用户管理菜单。选择该菜单中的 "用户 "项目。

The bottom of the page now features an expanded “User Management” menu. An arrow directs the reader to expand the “Users” menu item.

将出现 "用户"页面。它列出了所有注册到你的租户的用户。如果没有用户,你会看到 "你还没有任何用户 "的信息。

The “Users” page. The page says, “You don’t have any users yet.” An arrow directs the reader to click the “Create User” button.

🛠 单击 "创建用户"按钮来创建一个新用户,这将使这个对话框出现。

The “Create User” dialog. It has fields for email and password, as well as a drop-down menu displaying “Username-Password-Authentication.”

🛠 为用户输入一个电子邮件地址和密码。连接的唯一选项将是用户名-密码-认证,所以保持原样。记下这个电子邮件地址和密码--你将用它们来登录应用程序。

🛠 点击创建按钮来创建用户。该用户的详细信息页面将出现。

The user’s “Details” page.

这就解决了你在Auth0仪表板上需要做的所有设置。现在是回到Android Studio和应用程序的时候了。

配置应用程序

在你开始编码之前,你需要配置应用程序与Auth0进行通信。

启用应用程序以响应回调和注销的URLs

在本练习的早些时候,你创建了回调和注销URL,Auth0用它们来通知应用程序用户已经登录或注销。在这一步,您将使应用程序能够对这些 URL 作出响应。

🛠 打开 build.gradle应用程序,而不是项目。在Android Studio的项目标签的文件列表中,打开 build.gradle (Module: Android_Login.app),位于Gradle Scripts 文件夹中。

The starter project in Android Studio. The reader is directed to open the “build.gradle” marked “(Module: Android_Login.app)”.

🛠 将android 的块更新为 build.gradle (Module: Android_Login.app)更新为以下内容。我已经用New code 的注释强调了你需要进行修改的地方。

// Gradle Scripts/build.gradle (Module: Android_Login.app)

// (Other code here)

android {
    compileSdk 32

    defaultConfig {
        applicationId "com.auth0.androidlogin"
        minSdk 21
        targetSdk 32
        versionCode 1
        versionName "1.0"
        // New code 👇🏽
        manifestPlaceholders = [auth0Domain: "@string/com_auth0_domain", auth0Scheme: "@string/com_auth0_scheme"]
        // New code 👆🏽

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    // (Other options here)
}

// (The rest of the file is here)

您刚刚在文件中添加了清单占位符。宣言文件中的这些变量定义了一个意图过滤器,它决定了应用程序如何响应意图。应用程序将把回调和注销 URL 解释为意图。

安装Auth0库

为了使应用程序与Auth0一起工作,你需要安装两个库。

  1. **Auth0.Android包。**这是一个库的集合,使Android应用程序能够使用Auth0的API,包括Authentication API,你将用它来实现应用程序中的登录和注销。
  2. **JWTDecode.Android库。**应用程序将使用它来解码用户的身份信息,这些信息是JSON Web Token(JWT)格式。

🛠 安装库,在dependencies 块中添加两个新项目,如下所示: build.gradle (Module: Android_Login.app)中添加两个新项目,如下图所示。

// Gradle Scripts/build.gradle (Module: Android_Login.app)

// (Other code here)

dependencies {
    implementation 'androidx.core:core-ktx:1.7.0'
    implementation 'androidx.appcompat:appcompat:1.4.2'
    implementation 'com.google.android.material:material:1.6.1'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'

    // New code 👇🏽
    // Auth0 dependencies
    implementation 'com.auth0.android:auth0:2.7.0'
    implementation 'com.auth0.android:jwtdecode:2.0.1'
    // New code 👆🏽
}

在对Gradle文件进行这些修改后,你需要将项目与你定义的新构建配置同步。Android Studio会通知你,它检测到了对Gradle文件的修改,并向你提供了将项目与更新的文件同步的选项。

🛠 单击 Android Studio 窗口顶部附近通知中出现的立即同步链接。

The starter project in Android Studio. The reader is directed to click the “Sync Now” link.

Android Studio会显示 "Gradle项目同步正在进行中...... "的信息,一会儿就会完成同步。

在Auth0资源文件中存储域名和客户端ID

在本练习的早些时候,你在Auth0仪表板中注册了应用程序,这给了Auth0与应用程序互动所需的信息。现在是时候在应用程序方面做同样的事情了,给它提供与Auth0互动所需的信息,即。

  1. 你的租户的域名,应用程序将使用该域名来确定它用来联系Auth0的URL。
  2. 应用程序的客户端ID,应用程序将用它来向Auth0识别自己。

你应该将这些信息存储在字符串资源文件中,这是Android项目存储文本字符串的首选之地。我们将遵循推荐的做法,使用一个单独的字符串资源文件,名为 auth0.xml来存储Auth0特定的字符串。启动项目已经有一个 auth0.xml文件以节省时间。

🛠Open auth0.xml,位于 app/res/values文件夹中。它应该包含以下内容。

<!-- app/res/values/auth0.xml -->

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <!--
    Auth0 identifiers and settings
    ==============================
    These values are required to connect to Auth0 for authorization,
    which is why we're storing them in their own string resource file.
    -->

    <string name="com_auth0_scheme">TODO: Enter the scheme for your app’s callback and logout URLs here.</string>

    <string name="com_auth0_domain">TODO: Enter your tenant’s domain name here.</string>
    <string name="com_auth0_client_id">TODO: Enter your app’s client ID here.</string>

</resources>

🛠 将标签的内容从 <string name="com_auth0_scheme">标签的内容,从 TODO: Enter the scheme for your app’s callback and logout URLs here.改为这些URL的方案,应该是app

🛠 将标签的内容从 <string name="com_auth0_domain">标签的内容从 TODO: Enter your tenant’s domain name here.改为你的租户的域名,这是你从Auth0仪表板的设置页面上复制的。

🛠 将标签的内容改为 <string name="com_auth0_client_id">标签的内容从 TODO: Enter your app’s client ID here.改为你的应用程序的客户ID,这是你从Auth0仪表板的设置页面上复制的。

使应用程序能够访问互联网

登录/注销过程需要应用程序与Auth0通信,它需要访问互联网的权限。你在应用程序的清单文件中指定这一权限。

🛠 打开应用程序的清单文件。 AndroidManifest.xml位于 app/manifests文件夹中的应用程序清单文件,并添加一个 <uses-permission>XML元素,如下图所示。

<!-- app/manifests/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"
    package="com.auth0.androidlogin">

    <!-- New code 👇🏽 -->
    <uses-permission android:name="android.permission.INTERNET" />
    <!-- New code 👆🏽 -->

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.AndroidLogin"
        tools:targetApi="31">
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

定义一个数据类来代表用户

用户登录后会发生什么

Auth0和应用程序两方面的所有必要设置都已完成。我们现在可以讨论登录过程了。

当用户按下登录按钮时,应用程序通过打开一个显示通用登录屏幕的嵌入式Web视图,将控制权委托给Auth0。

当 "通用登录 "屏幕关闭时,Auth0将控制权交还给应用程序。正如我前面提到的,有两种方法可以实现这一点。

  1. 用户按下 "取消"按钮。这样就会取消通用登录屏幕,应用程序就会收到用户取消了登录过程的通知。
  2. 用户输入一个Auth0识别的用户名和密码组合。应用程序会收到一个通知,表示用户已经成功登录。它还得到一个Credentials 对象,其属性包含令牌--关于用户是谁以及他们被允许做什么的数字信息。

这个应用程序将只使用一个Credentials 属性以保持简单:ID令牌。ID令牌是一个用户已被认证的证明。它包含了关于用户的信息和认证过程中发生的事情。应用程序将从ID令牌中提取用户的姓名、电子邮件地址和图片URL,并在用户登录时在屏幕上显示它们。

如果你想了解更多关于ID令牌以及它们的对应物--访问令牌,请看我们的文章。 ID令牌和访问令牌。有什么区别?这篇文章是我们的系列文章《困惑的开发者》的一部分。

ID令牌的形式为 JSON网络令牌简称JWT(你可能会听到有些人把 "JWT "读作 "jot")。JWTs是经过签名和编码的JSON对象,用于安全地发送信息。

你可以在jwt.io网站上试验JWT。这个网站有一个JWT调试器,可以让你互动地对JWT进行编码和解码。

实现User 数据类

该应用程序需要解码和存储ID令牌的内容。我们将通过实现User 数据类来赋予应用程序这种能力。给定一个JWT格式的ID令牌,它对令牌进行解码并创建一个实例,通过其属性公开用户信息。

🛠 将User 的内容(位于 app/java/com.auth0.androidlogin)的内容,使之成为以下内容。

// app/java/com.auth0.androidlogin/User

package com.example.login

import com.auth0.android.jwt.JWT


data class User(val idToken: String? = null) {

    var id = ""
    var name = ""
    var email = ""
    var emailVerified = ""
    var picture = ""
    var updatedAt = ""

    // New code 👇🏽
    init {
        try {
            // Attempt to decode the ID token. 
            val jwt = JWT(idToken ?: "")
            
            // The ID token is a valid JWT,
            // so extract information about the user from it.
            id = jwt.subject ?: ""
            name = jwt.getClaim("name").asString() ?: ""
            email = jwt.getClaim("email").asString() ?: ""
            emailVerified = jwt.getClaim("email_verified").asString() ?: ""
            picture = jwt.getClaim("picture").asString() ?: ""
            updatedAt = jwt.getClaim("updated_at").asString() ?: ""
            
        } catch (e: com.auth0.android.jwt.DecodeException) {
            // The ID token is NOT a valid JWT,
            // so leave the user properties as empty strings.
        }
    }
    // New code 👆🏽
}

User 它做了以下工作。

  • 它导入了 com.auth0.android.jwt.JWT是解码JWTs的库,作为Auth0.Android包的一部分。
  • 它还定义了一组字符串属性,以保存它将从ID令牌中提取的用户信息。这些属性是。
    • id:一个能唯一识别用户的字符串。
    • name:用户的全名。
    • email:用户的电子邮件地址。
    • emailVerified: true如果用户已经用Auth0验证了他们的电子邮件。
    • picture:用户照片的URL。
    • updatedAt:用户信息的最后更新日期。
  • 它试图对给定的ID标记进行解码。如果成功,它从令牌中提取用户信息,并使用它来填充User 对象的属性。如果它不能解码令牌(可能是因为它无效),User 对象属性保持空白。

实现登录

现在我们有了可以解码ID令牌并存储其包含的用户信息的东西,我们可以实现登录(以及最终的注销)。

这是一个单屏幕的应用程序,所以你将在应用程序的单一活动中实现登录(和注销),MainActivity

设置MainActivity

🛠 打开MainActivity (位于 app/java/com.auth0.androidlogin)并更新其 import语句更新为以下内容。

// app/java/com.auth0.androidlogin/MainActivity

// (Other code here)

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.core.view.isVisible
// New code 👇🏽
import com.auth0.android.Auth0
import com.auth0.android.authentication.AuthenticationException
import com.auth0.android.callback.Callback
import com.auth0.android.provider.WebAuthProvider
import com.auth0.android.result.Credentials
// New code 👆🏽
import com.google.android.material.Snackbar.Snackbar
import com.example.login.databinding.ActivityMainBinding

// (The rest of the file is here)

🛠 在MainActivity 类的App and user statusAuth0 data 部分添加属性,使它们显示如下。

// app/java/com.auth0.androidlogin/MainActivity

// (Other code here)

    // App and user status
    // New code 👇🏽
    private lateinit var account: Auth0
    private var appJustLaunched = true
    private var userIsAuthenticated = false
    // New code 👆🏽

    // Auth0 data
    // New code 👇🏽
    private var user = User()
    // New code 👆🏽
    
// (The rest of the file is here)

你刚刚添加的属性是:

  • account:一个包含应用程序的账户信息的对象,包括应用程序的客户ID和它的租户的域。
  • appJustLaunched: true只有在应用启动时才会立即出现,之后它的值将立即变成 false.你将使用这个属性在用户启动应用程序后立即在屏幕顶部显示欢迎信息。
  • userIsAuthenticated: true如果用户已经登录。 false否则。
  • user:User 类的一个实例,包含关于当前用户的身份信息。

🛠初始化MainActivity ,在其 onCreate()方法中添加代码,如下所示。

// app/java/com.auth0.androidlogin/MainActivity

// (Other code here)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        // New code 👇🏽
        account = Auth0(
            getString(R.string.com_auth0_client_id),
            getString(R.string.com_auth0_domain)
        )

        binding.buttonLogin.setOnClickListener { login() }
        binding.buttonLogout.setOnClickListener { logout() }
        // New code 👆🏽
    }
    
// (The rest of the file is here)

你刚才添加的代码做了以下工作:

  • 它用应用程序的客户端ID和其租户的域来初始化account 。这两个 login()logout()方法都使用account
  • 它将登录注销按钮连接到 login()logout()方法。

login()方法

该方法将调用Auth0认证服务器,并为成功和失败提供相应的操作。 login()方法将调用Auth0认证服务器,并为成功和失败的登录提供相应的操作。我们将一点点地实现它。

🛠 更新该方法为 login()方法,使之成为以下内容。

// app/java/com.auth0.androidlogin/MainActivity

// (Other code here)

    private fun login() {
        WebAuthProvider
            .login(account)
            .withScheme(getString(R.string.com_auth0_scheme))
            .start(this, object : Callback<Credentials, AuthenticationException> {

                override fun onFailure(exception: AuthenticationException) {
                    // The user either pressed the “Cancel” button
                    // on the Universal Login screen or something
                    // unusual happened.
                    // TODO: Handle login failure.
                }

                override fun onSuccess(credentials: Credentials) {
                    // The user successfully logged in.
                    // TODO: Handle login success.
                }
            })
    }
    
// (The rest of the file is here)

如果你忽略关闭、注释和换行符,这个方法中的代码有效地成为一个简短的方法调用链。

WebAuthProvider.login().withScheme().start()

这就是Builder设计模式的作用。链中的每个方法都返回一个对象,并将其传递给链中的下一个方法。

WebAuthProvider 对象提供了对Auth0库的网络认证库的访问。

login()方法设置了WebAuthProvider 对象来验证用户。它需要一个参数:MainActivity'saccount 属性,其中包含应用程序的客户端ID和租户的域。

withScheme()该方法的返回值来自 login()的返回值,并指定自定义URL的方案,Auth0将使用该方案在用户登录或退出时通知应用程序。在本例中,我们使用字符串app 作为方案,这意味着这些URL将以app 开始,而不是httphttps

默认方案是https 。我更喜欢使用自定义方案,如app ,因为在某些设置中,https 方案会导致Android打开一个新的浏览器窗口,而不是将控制权交还给应用程序。

start()方法接受从 withScheme()返回的值,并启动认证过程。这样做会打开一个嵌入的Web视图,显示通用登录页面。该方法的最后一个参数 start()所期望的最后参数是一个对象,有两个方法来应对可能的结果:

  • onFailure(),当用户在没有登录的情况下手动关闭网页视图时执行,以及
  • onSuccess()当用户成功登录并自动关闭网页视图时,执行该方法。

让我们来实现这些方法。

实施 onFailure()

🛠在 onFailure()中,替换掉 TODO:注释,使该方法看起来像这样。

// app/java/com.auth0.androidlogin/MainActivity

// (Other code here)

                override fun onFailure(exception: AuthenticationException) {
                    // The user either pressed the “Cancel” button
                    // on the Universal Login screen or something
                    // unusual happened.
                    showSnackbar(getString(R.string.login_failure_message))
                }
                
// (The rest of the file is here)

这个方法显示一个Snackbar,显示登录失败时发生的异常类型。

实施 onSuccess()

🛠 在 onSuccess()中,替换掉 TODO:注释,使该方法看起来像这样。

// app/java/com.auth0.androidlogin/MainActivity

// (Other code here)

                override fun onSuccess(credentials: Credentials) {
                    // The user successfully logged in.
                    val idToken = credentials.idToken
                    user = User(idToken)
                    userIsAuthenticated = true
                    showSnackbar("Success: ${user.name}")
                    updateUI()
                }
                
// (The rest of the file is here)

登录成功后,Auth0通过credentials 参数返回一个Credentials 对象。 onSuccess()做到以下几点。

  • 它从credentials ,并使用它来实例化user ,它对ID令牌进行解码并提取用户信息。
  • 它通过设置userIsAuthenticated 属性来标记用户已登录。 true.
  • 它更新用户界面以反映用户的登录状态。

实现注销

logout()方法类似于 login().就像 login()一样,我们每次都会实现它。

🛠 更新该 logout()方法如下。

// app/java/com.auth0.androidlogin/MainActivity

// (Other code here)

    private fun logout() {
        WebAuthProvider
            .logout(account)
            .start(this, object : Callback<Void?, AuthenticationException> {

                override fun onFailure(exception: AuthenticationException) {
                    // For some reason, logout failed.
                    // TODO: Handle logout failure.
                }

                override fun onSuccess(payload: Void?) {
                    // The user successfully logged out.
                    // TODO: Handle logout success.
                }

            })
    }
    
// (The rest of the file is here)

正如与 login(), logout()也使用了Auth0 SDK的WebAuthProvider 类和Builder模式。这一次,它调用 logout()方法,该方法也将MainActivity'saccount 属性作为其单一参数。

withScheme()方法的返回值来自 login()的返回值,并指定自定义URL的方案,Auth0将使用该方案在用户登录或退出时通知应用程序。

start()方法将用户注销。它需要一个对象,有两个方法来响应可能的结果。

  • onFailure(),在注销失败时执行,以及
  • onSuccess(),在用户成功注销时执行。

让我们来实现这些方法。

实施 onFailure()

🛠 In onFailure()中,替换掉 TODO:注释,使该方法看起来像这样。

// app/java/com.auth0.androidlogin/MainActivity

// (Other code here)

                override fun onFailure(exception: AuthenticationException) {
                    // For some reason, logout failed.
                    showSnackbar(getString(R.string.general_failure_with_exception_code,
                                           exception.getCode()))
                }
                
// (The rest of the file is here)

这个方法显示一个Snackbar,显示注销失败时发生的异常类型。

实施 onSuccess()

🛠 在 onSuccess()中,替换掉 TODO:注释,使该方法看起来像这样。

// app/java/com.auth0.androidlogin/MainActivity

// (Other code here)

                override fun onSuccess(payload: Void?) {
                    // The user successfully logged out.
                    user = User()
                    userIsAuthenticated = false
                    updateUI()
                }
                
// (The rest of the file is here)

在成功注销后。 onSuccess()做以下事情:

  • 它将user 改为一个空白的User 实例。
  • 它通过将userIsAuthenticated 属性设置为 ,将用户标记为注销。 false.
  • 它更新用户界面以反映用户的注销状态。

更新用户界面

只剩下一个任务:根据应用程序的状态更新用户界面。

🛠 In updateUI()中,替换掉 TODO:注释,使该方法看起来像这样。

// app/java/com.auth0.androidlogin/MainActivity

// (Other code here)

    private fun updateUI() {
        if (appJustLaunched) {
            binding.textviewTitle.text = getString(R.string.initial_title)
            appJustLaunched = false
        } else {
            binding.textviewTitle.text = if (userIsAuthenticated) {
                getString(R.string.logged_in_title)
            } else {
                getString(R.string.logged_out_title)
            }
        }

        binding.buttonLogin.isEnabled = !userIsAuthenticated
        binding.buttonLogout.isEnabled = userIsAuthenticated

        binding.textviewUserProfile.isVisible = userIsAuthenticated
        binding.textviewUserProfile.text = getString(R.string.user_profile,
            user.name,
            user.email)

        binding.imageviewUser.isVisible = userIsAuthenticated
        binding.imageviewUser.loadImage(user.picture)
    }
    
// (The rest of the file is here)

updateUI()做到以下几点:

  • 它将应用程序的标题设置为适当的文本。
  • 它将登录注销按钮设置为适当的状态。
  • 它将包含用户姓名和电子邮件地址的TextView和包含用户照片的ImageView设置为适当的内容和可见性。

运行已完成的应用程序

🛠 你已经完成了应用程序的编码!运行该应用程序,以确认它的运行。运行该应用程序以确认其工作。

总结

你刚刚建立了一个具有基本用户名/密码认证的安卓应用。除了记录已知用户的进出,它还可以从他们的用户资料中获取基本的用户信息并显示在屏幕上。

你可以在Auth0博客样本GitHub账户get-started-android-authentication-kotlin 仓库中找到启动和完成的项目。

请记住,了解该应用如何与Auth0集成的最好方法是获得启动项目,并按照本教程中的步骤努力完成一个项目俗话说:"我听了,我忘了;我看了,我记得;我做了,我明白了"。

请关注本博客,了解即将发表的关于使用Auth0进行移动开发的文章。未来的文章将对认证、授权以及标准的用户名和密码的替代方法进行深入探讨。请关注这个空间。