将身份验证添加到 Android 应用

概要

在本教程中,你将使用受支持的标识提供者向 Android 上的 todolist 快速入门项目添加身份验证。 本教程基于 移动应用 入门教程,必须先完成本教程。

注册应用以进行身份验证并配置 Azure 应用服务

首先,需要在标识提供者的站点上注册应用,然后在移动应用后端设置提供程序生成的凭据。

  1. 请根据提供商的具体说明配置您的首选身份提供商。

  2. 对要在应用中支持的每个提供商重复上述步骤。

将应用添加到允许的外部重定向 URL

安全身份验证要求为应用定义新的 URL 方案。 这样,身份验证系统就可以在身份验证过程完成后重定向回应用。 在本教程中,我们将在整个过程中使用 url 方案 appname。 但是,可以使用你选择的任何 URL 方案。 它应该是您移动应用程序独有的。 若要在服务器端启用重定向,请执行以下作:

  1. Azure 门户中,选择应用服务。

  2. 单击 身份验证/授权 菜单选项。

  3. 允许的外部重定向URL中,输入 appname://easyauth.callback。 此字符串中的 appname 是移动应用程序的 URL 方案。 它应遵循协议的正常 URL 规范(仅使用字母和数字,以字母开头)。 应记下所选字符串,因为需要在多个位置使用 URL 方案调整移动应用程序代码。

  4. 单击 “确定”

  5. 单击“ 保存”。

限制对经过身份验证的用户的权限

默认情况下,移动应用后端中的 API 可以匿名调用。 接下来,需要限制仅对经过身份验证的客户端的访问。

  • Node.js 后端(通过 Azure 门户)

    在移动应用设置中,单击“简易表” 并选择表。 单击 “更改权限”,选择 “仅限身份验证访问” 作为所有权限的设置,然后单击 “保存”

  • .NET 后端 (C#)

    在服务器项目中,导航至 Controllers>TodoItemController.cs。 将 [Authorize] 属性添加到 TodoItemController 类,如下所示。 若要仅限制对特定方法的访问,还可以仅将此属性应用于这些方法而不是类。 重新发布服务器项目。

      [Authorize]
      public class TodoItemController : TableController<TodoItem>
    
  • Node.js 后端(通过 Node.js 代码)

    若要要求对表访问进行身份验证,请将以下行添加到 Node.js 服务器脚本:

      table.access = 'authenticated';
    

    有关详细信息,请参阅 如何要求身份验证才能访问表。 若要了解如何从网站下载快速入门代码项目,请参阅 如何:使用 Git下载 Node.js 后端快速入门代码项目。

  • 在 Android Studio 中,打开使用移动应用 入门教程完成的项目。 在运行菜单中,点击运行应用,并在应用程序启动后验证是否抛出状态代码为 401(未授权)的未处理的异常。

    发生此异常的原因是应用尝试以未经过身份验证的用户身份访问后端,但 TodoItem 数据表现在需要身份验证。

接下来,在从移动应用后端请求资源之前,将应用更新为对用户进行身份验证。

将身份验证添加到应用

  1. 在 Android Studio 中打开项目。

  2. 在 Android Studio 中的项目资源管理器 中,打开 ToDoActivity.java 该文件并添加以下导入语句:

    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.atomic.AtomicBoolean;
    
    import android.content.Context;
    import android.content.SharedPreferences;
    import android.content.SharedPreferences.Editor;
    
    import com.microsoft.windowsazure.mobileservices.authentication.MobileServiceAuthenticationProvider;
    import com.microsoft.windowsazure.mobileservices.authentication.MobileServiceUser;
    
  3. 将以下方法添加到 ToDoActivity 类:

    // You can choose any unique number here to differentiate auth providers from each other. Note this is the same code at login() and onActivityResult().
    public static final int GOOGLE_LOGIN_REQUEST_CODE = 1;
    
    private void authenticate() {
        // Sign in using the Google provider.
        mClient.login(MobileServiceAuthenticationProvider.Google, "{url_scheme_of_your_app}", GOOGLE_LOGIN_REQUEST_CODE);
    }
    
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        // When request completes
        if (resultCode == RESULT_OK) {
            // Check the request code matches the one we send in the login request
            if (requestCode == GOOGLE_LOGIN_REQUEST_CODE) {
                MobileServiceActivityResult result = mClient.onActivityResult(data);
                if (result.isLoggedIn()) {
                    // sign-in succeeded
                    createAndShowDialog(String.format("You are now signed in - %1$2s", mClient.getCurrentUser().getUserId()), "Success");
                    createTable();
                } else {
                    // sign-in failed, check the error message
                    String errorMessage = result.getErrorMessage();
                    createAndShowDialog(errorMessage, "Error");
                }
            }
        }
    }
    

    此代码创建一个处理 Google 身份验证过程的方法。 对话框显示经过身份验证的用户的 ID。 只有身份验证成功后,你才能继续。

    注释

    如果使用 Google 以外的标识提供者,请将传递给 登录 方法的值更改为以下值之一: MicrosoftAccountFacebookTwitterwindowsazureactivedirectory

  4. onCreate 方法中,在实例化 MobileServiceClient 对象的代码后面添加以下代码行。

    authenticate();
    

    此调用将启动身份验证过程。

  5. onCreate 方法中 authenticate(); 之后的剩余代码移动到一个新的 createTable 方法中。

    private void createTable() {
    
        // Get the table instance to use.
        mToDoTable = mClient.getTable(ToDoItem.class);
    
        mTextNewToDo = (EditText) findViewById(R.id.textNewToDo);
    
        // Create an adapter to bind the items with the view.
        mAdapter = new ToDoItemAdapter(this, R.layout.row_list_to_do);
        ListView listViewToDo = (ListView) findViewById(R.id.listViewToDo);
        listViewToDo.setAdapter(mAdapter);
    
        // Load the items from Azure.
        refreshItemsFromTable();
    }
    
  6. 若要确保重定向按预期工作,请将以下代码片段 RedirectUrlActivity 添加到 AndroidManifest.xml 中:

    <activity android:name="com.microsoft.windowsazure.mobileservices.authentication.RedirectUrlActivity">
        <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="{url_scheme_of_your_app}"
                android:host="easyauth.callback"/>
        </intent-filter>
    </activity>
    
  7. redirectUriScheme 添加到 build.gradle 的 Android 应用程序中。

    android {
        buildTypes {
            release {
                // ...
                manifestPlaceholders = ['redirectUriScheme': '{url_scheme_of_your_app}://easyauth.callback']
            }
            debug {
                // ...
                manifestPlaceholders = ['redirectUriScheme': '{url_scheme_of_your_app}://easyauth.callback']
            }
        }
    }
    
  8. 在你的build.gradle中添加com.android.support:customtabs:23.0.1到依赖项中。

    dependencies {
        // ...
        compile 'com.android.support:customtabs:23.0.1'
    }
    
  9. 在“ 运行 ”菜单中,单击“ 运行”应用 以启动应用并使用所选标识提供者登录。

警告

提到的 URL 方案区分大小写。 确保 {url_scheme_of_you_app} 的所有出现都使用相同的大小写。

成功登录后,应用应运行且不会出现错误,并且应该能够查询后端服务并更新数据。

在客户端上缓存身份验证令牌

前面的示例显示了一个标准登录,要求客户端在每次启动应用时都联系标识提供者和后端 Azure 服务。 此方法效率低下,如果许多客户尝试同时启动应用,则可以遇到与使用情况相关的问题。 更好的方法是缓存 Azure 服务返回的授权令牌,并在使用基于提供程序的登录之前先尝试使用此令牌。

注释

无论使用的是客户端托管服务还是服务托管身份验证,都可以缓存后端 Azure 服务颁发的令牌。 本教程使用服务管理的身份验证。

  1. 打开ToDoActivity.java文件并添加以下导入语句:

    import android.content.Context;
    import android.content.SharedPreferences;
    import android.content.SharedPreferences.Editor;
    
  2. 将以下成员添加到 ToDoActivity 类。

    public static final String SHAREDPREFFILE = "temp";
    public static final String USERIDPREF = "uid";
    public static final String TOKENPREF = "tkn";
    
  3. 在ToDoActivity.java文件中,为该方法添加以下定义 cacheUserToken

    private void cacheUserToken(MobileServiceUser user)
    {
        SharedPreferences prefs = getSharedPreferences(SHAREDPREFFILE, Context.MODE_PRIVATE);
        Editor editor = prefs.edit();
        editor.putString(USERIDPREF, user.getUserId());
        editor.putString(TOKENPREF, user.getAuthenticationToken());
        editor.commit();
    }
    

    此方法将用户 ID 和令牌存储在标记为私有的首选项文件中。 这应保护对缓存的访问,以便设备上的其他应用无权访问令牌。 该偏好设置为应用进行了沙盒化。 但是,如果有人获得对设备的访问权限,他们可能通过其他方式获取对令牌缓存的访问权限。

    注释

    如果对数据的令牌访问被视为高度敏感,并且有人可能会获得对设备的访问权限,则可以进一步保护令牌进行加密。 但是,完全安全的解决方案超出了本教程的范围,具体取决于安全要求。

  4. 在ToDoActivity.java文件中,为该方法添加以下定义 loadUserTokenCache

    private boolean loadUserTokenCache(MobileServiceClient client)
    {
        SharedPreferences prefs = getSharedPreferences(SHAREDPREFFILE, Context.MODE_PRIVATE);
        String userId = prefs.getString(USERIDPREF, null);
        if (userId == null)
            return false;
        String token = prefs.getString(TOKENPREF, null);
        if (token == null)
            return false;
    
        MobileServiceUser user = new MobileServiceUser(userId);
        user.setAuthenticationToken(token);
        client.setCurrentUser(user);
    
        return true;
    }
    
  5. ToDoActivity.java 文件中,将 authenticateonActivityResult 方法替换为以下使用令牌缓存的方法。 如果要使用 Google 以外的帐户,请更改登录提供程序。

    private void authenticate() {
        // We first try to load a token cache if one exists.
        if (loadUserTokenCache(mClient))
        {
            createTable();
        }
        // If we failed to load a token cache, sign in and create a token cache
        else
        {
            // Sign in using the Google provider.
            mClient.login(MobileServiceAuthenticationProvider.Google, "{url_scheme_of_your_app}", GOOGLE_LOGIN_REQUEST_CODE);
        }
    }
    
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        // When request completes
        if (resultCode == RESULT_OK) {
            // Check the request code matches the one we send in the sign-in request
            if (requestCode == GOOGLE_LOGIN_REQUEST_CODE) {
                MobileServiceActivityResult result = mClient.onActivityResult(data);
                if (result.isLoggedIn()) {
                    // sign-in succeeded
                    createAndShowDialog(String.format("You are now signed in - %1$2s", mClient.getCurrentUser().getUserId()), "Success");
                    cacheUserToken(mClient.getCurrentUser());
                    createTable();
                } else {
                    // sign-in failed, check the error message
                    String errorMessage = result.getErrorMessage();
                    createAndShowDialog(errorMessage, "Error");
                }
            }
        }
    }
    
  6. 使用有效的帐户生成应用并测试身份验证。 至少运行两次。 在第一次运行期间,应会收到登录和创建令牌缓存的提示。 之后,每次运行都会尝试加载令牌缓存进行身份验证。 无需登录。

后续步骤

完成此基本身份验证教程后,请考虑继续学习以下教程之一:

  • 将推送通知添加到 Android 应用。 了解如何将移动应用后端配置为使用 Azure 通知中心发送推送通知。
  • 为 Android 应用启用脱机同步。 了解如何使用移动应用后端向应用添加脱机支持。 使用脱机同步,即使没有网络连接,用户也可以与移动应用进行交互(查看、添加或修改数据)。