将身份验证添加到 Android 应用

摘要

本教程介绍如何使用支持的标识提供者将身份验证添加到 Android 上的待办事项列表快速入门项目。 本教程基于移动应用入门教程,必须先完成该教程。

注册应用以进行身份验证并配置 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#):

    在服务器项目中,导航到“控制器”>“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 语句

    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,请将传递给 login 方法的值更改为下列值之一: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 添加到 Android 应用程序的 build.gradle

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

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

警告

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

成功登录后,应用应可以正常运行,并且你应能查询后端服务并对数据进行更新。

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

上一示例介绍了标准登录过程,此过程要求在应用每次启动时客户端同时联系标识提供者和后端 Azure 服务。 此方法效率较低,并且许多用户同时启动应用时可能产生使用率相关方面的问题。 更好的方法是缓存 Azure 服务返回的授权令牌,并在使用基于提供者的登录之前首先尝试使用此令牌。

注意

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

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

    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 应用启用脱机同步。 了解如何使用移动应用后端向应用添加脱机支持。 使用脱机同步,用户可以与移动应用进行交互(查看、添加或修改数据),即使在没有网络连接时也是如此。