将身份验证添加到 Android 应用

摘要

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

注册应用以进行身份验证并配置 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 中,打开通过 Get started with Mobile Apps教程完成的项目。 从“运行”菜单中单击“运行应用”;验证启动该应用后,是否会引发状态代码为 401(“未授权”)的未经处理的异常。

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

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

向应用程序添加身份验证

  1. 在 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;
    
  2. 将以下方法添加到 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() {
         // Login using the Google provider.
         mClient.login("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()) {
                     // login succeeded
                     createAndShowDialog(String.format("You are now logged in - %1$2s", mClient.getCurrentUser().getUserId()), "Success");
                     createTable();
                 } else {
                     // login failed, check the error message
                     String errorMessage = result.getErrorMessage();
                     createAndShowDialog(errorMessage, "Error");
                 }
             }
         }
     }
    

    这将会创建一个用于处理身份验证过程的新方法。 使用 Google 登录对用户进行身份验证。 出现的对话框中会显示已经过身份验证的用户 ID。 如果未正常完成身份验证,你将无法继续操作。

    Note

    如果使用的标识提供者不是 Google,请将传递给上述 login 方法的值更改为下列其中一项:MicrosoftAccountFacebookTwitterwindowsazureactivedirectory

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

     authenticate();
    

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

  4. 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();
     }
    
  5. 将下面的 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>
    
  6. 将 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']
            }
        }
    }
    
  7. 将 com.android.support:customtabs:23.0.1 添加到 build.gradle 中的依赖项:

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

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

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

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

Note

无论使用客户端管理的还是服务管理的身份验证,都可以缓存由后端 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 和令牌存储在标记为私有的首选项文件中。 这可保护对缓存的访问,这样设备上的其他应用便无权访问此令牌。 因为应用的首选项已经过沙盒处理。 但是,如果有人获取了设备的访问权,则它们可能会通过其他方式获得对令牌缓存的访问权。

    Note

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

  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 文件中,将 authenticate 方法替换为下面这种使用令牌缓存的方法。 如果要使用的帐户不是 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, login and create a token cache
         else
         {
             // Login using the Google provider.    
             ListenableFuture<MobileServiceUser> mLogin = mClient.login(MobileServiceAuthenticationProvider.Google);
    
             Futures.addCallback(mLogin, new FutureCallback<MobileServiceUser>() {
                 @Override
                 public void onFailure(Throwable exc) {
                     createAndShowDialog("You must log in. Login Required", "Error");
                 }           
                 @Override
                 public void onSuccess(MobileServiceUser user) {
                     createAndShowDialog(String.format(
                             "You are now logged in - %1$2s",
                             user.getUserId()), "Success");
                     cacheUserToken(mClient.getCurrentUser());
                     createTable();    
                 }
             });
         }
     }
    
  6. 构建此应用并使用有效帐户来测试验证。 至少两次运行它。 在第一次运行期间,你应会收到要求登录并创建令牌缓存的提示。 之后,每次运行时,系统都会尝试加载用于身份验证的令牌缓存。 而你无需再次登录。

后续步骤

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

  • 为 Android 应用启用脱机同步 了解如何使用移动应用后端向应用添加脱机支持。 使用脱机同步,用户可以与移动应用进行交互—查看、添加或修改数据—,即使在没有网络连接时也是如此。