البرنامج التعليمي: تسجيل الدخول للمستخدمين والاتصال بواجهة برمجة تطبيقات Microsoft Graph من تطبيق Android

في هذا البرنامج التعليمي، يمكنك إنشاء تطبيق Android يتكامل مع النظام الأساسي للهويات في Microsoft لتسجيل الدخول للمستخدمين والحصول على رمز وصول للاتصال بواجهة برمجة تطبيقات Microsoft Graph.

عند الانتهاء من هذا البرنامج التعليمي، سيقبل التطبيق تسجيل الدخول لحسابات Microsoft الشخصية (بما في ذلك outlook.com live.com وغيرها) بالإضافة إلى حسابات العمل أو المدرسة من أي شركة أو مؤسسة تستخدم Azure Active Directory.

في هذا البرنامج التعليمي:

  • إنشاء مشروع تطبيق Android في Android Studio
  • تسجيل التطبيق في مدخل Microsoft Azure
  • إضافة تعليمة برمجية لدعم المستخدم في تسجيل الدخول وتسجيل الخروج
  • إضافة تعليمات برمجية للاتصال بواجهة برمجة تطبيقات Microsoft Graph
  • اختبار التطبيق

المتطلبات الأساسية

  • Android Studio 3.5+

كيف يعمل هذا البرنامج التعليمي

Shows how the sample app generated by this tutorial works

التطبيق في هذا البرنامج التعليمي سيقوم بتسجيل دخول المستخدمين والحصول على البيانات نيابة عنهم. سيتم الوصول إلى هذه البيانات من خلال واجهة برمجة تطبيقات محمية (واجهة برمجة تطبيقات Microsoft Graph) والتي تتطلب التخويل ومحمية من قبل النظام الأساسي للهويات في Microsoft.

أكثر تحديداً:

  • سيقوم تطبيقك بتسجيل دخول المستخدم إما من خلال متصفح أو Microsoft Authenticator وIntune Company Portal.
  • سيقبل المستخدم النهائي الأذونات التي طلبها تطبيقك.
  • سيصدر لتطبيقك رمز وصول مميز لواجهة برمجة تطبيقات Microsoft Graph.
  • سيتم تضمين رمز الوصول في طلب HTTP إلى واجهة برمجة تطبيقات الويب.
  • معالجة استجابة Microsoft Graph.

يستخدم هذا النموذج مكتبة مصادقة Microsoft (MSAL) لتنفيذ المصادقة: com.microsoft.identity.client.

ستقوم MSAL تلقائيّاً بتجديد الرموز المميزة وتوفير تسجيل الدخول الأحادي بين التطبيقات الأخرى على الجهاز، وإدارة الحساب (الحسابات).

يوضح هذا البرنامج التعليمي أمثلة مبسطة للعمل باستخدام MSAL لنظام Android. للتبسيط، فإنه يستخدم وضع الحساب الفردي فقط. لاستكشاف سيناريوهات أكثر تعقيداً، راجع نموذج التعليمة البرمجية العمل المكتمل على GitHub.

إنشاء مشروع

إذا لم يكن لديك تطبيق Android بالفعل، فاتبع هذه الخطوات لإعداد مشروع جديد.

  1. من Android Studio، حدد بدء مشروع Android Studio جديداً.
  2. حدد النشاط الأساسي وحدد التالي.
  3. قم بتسمية تطبيقك.
  4. احفظ اسم الحزمة. ستقوم بإدخاله لاحقاً في مدخل Microsoft Azure.
  5. قم بتغيير اللغة من Kotlin إلى Java.
  6. قم بتعيين أدنى مستوى لواجهة برمجة التطبيقات إلى واجهة برمجة التطبيقات 19 أو أعلى، ثم انقر فوق إنهاء.
  7. في طريقة عرض المشروع، اختر المشروع في القائمة المنسدلة لعرض ملفات المشروع المصدر وغير المصدر، افتح app/build.gradle ثم قم بتعيين targetSdkVersion إلى 28.

التكامل مع مكتبة مصادقة Microsoft

تسجيل طلبك

  1. تسجيل الدخول إلى ⁧⁩مدخل Microsoft Azure⁧⁩.

  2. إذا كنت تتمكن من الوصول إلى عدة مستأجرين، فاستخدمعامل تصفية Directory + subscription في القائمة العلوية لتحديد المستأجر المُراد تسجيل التطبيق فيه.

  3. ابحث عن Azure Active Directory ثم حدده.

  4. ضمن Manage، حدد App registrationsتسجيل >جديد.

  5. أدخِل ⁧⁩ اسماً ⁧⁩ لتطبيقك. قد يرى مستخدمو تطبيقك هذا الاسم، ويمكنك تغييره لاحقاً.

  6. اختر ⁧⁩تسجيل⁧⁩.

  7. ضمن إدارة، حدد المصادقة>إضافة منصة>Android.

  8. أدخل اسم حزمة مشروعك. إذا قمت بتحميل التعليمة البرمجية، فستكون هذه القيمة هي com.azuresamples.msalandroidapp.

  9. في قسم Signature hash من صفحة Configure your Android app، حدد Generating a development Signature Hash. وانسخ أمر KeyTool لاستخدامه مع النظام الأساسي الخاص بك.

    KeyTool.exe مثبت كجزء من Java Development Kit (JDK). عليك أيضاً تثبيت الأداة OpenSSL لتنفيذ الأمر KeyTool. لمزيد من المعلومات، راجع وثائق Android الخاصة بإنشاء مفتاح.

  10. أدخل تجزئة التوقيع المنشأة بواسطة KeyTool.

  11. حدد تكوين واحفظ تكوين MSAL الذي يظهر في صفحة تكوين MSAL حتى تتمكن من إدخاله عند تهيئة تطبيقك لاحقاً.

  12. حدد ⁧⁩Done⁧⁩.

تكوين تطبيقك

  1. في جزء مشروع Android Studio، انتقل إلى app\src\main\res.

  2. انقر بزر الماوس الأيمن فوق res، واختر الدليل>الجديد. أدخل raw كاسم دليل جديد، وانقر فوق موافق.

  3. في app>src>main>res>raw، قم بإنشاء ملف JSON جديد يسمى auth_config_single_account.json وألصق تكوين MSAL الذي قمت بحفظه سابقاً.

    أسفل إعادة توجيه عنوان URL، ألصق:

      "account_mode" : "SINGLE",
    

    يجب أن يشبه ملف التكوين الخاص بك هذا المثال:

    {
      "client_id" : "0984a7b6-bc13-4141-8b0d-8f767e136bb7",
      "authorization_user_agent" : "DEFAULT",
      "redirect_uri" : "msauth://com.azuresamples.msalandroidapp/1wIqXSqBj7w%2Bh11ZifsnqwgyKrY%3D",
      "broker_redirect_uri_registered" : true,
      "account_mode" : "SINGLE",
      "authorities" : [
        {
          "type": "AAD",
          "audience": {
            "type": "AzureADandPersonalMicrosoftAccount",
            "tenant_id": "common"
          }
        }
      ]
    }
    

    لا يوضح هذا البرنامج التعليمي سوى كيفية تكوين تطبيق في وضع الحساب الفردي. عرض الوثائق للحصول على مزيد من المعلومات حول وضع الحساب الفردي مقابل المتعدد وتكوين تطبيقك

  4. في app>src>main>AndroidManifest.xml، أضف النشاط BrowserTabActivity أدناه إلى نص التطبيق. يسمح هذا الإدخال لـ Microsoft بالاتصال مرة أخرى بتطبيقك بعد إكمال المصادقة:

    <!--Intent filter to capture System Browser or Authenticator calling back to our app after sign-in-->
    <activity
        android:name="com.microsoft.identity.client.BrowserTabActivity">
        <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="msauth"
                android:host="Enter_the_Package_Name"
                android:path="/Enter_the_Signature_Hash" />
        </intent-filter>
    </activity>
    

    استبدل اسم الحزمة التي قمت بتسجيلها في مدخل Microsoft Azure بالقيمة android:host=. استبدل تجزئة المفتاح التي قمت بتسجيلها في مدخل Microsoft Azure بالقيمة android:path=. يجب ألا يتم ترميز تجزئة التوقيع بعنوان URL. تأكد من وجود / بادئة في بداية تجزئة التوقيع الخاصة بك.

    يجب أن يبدو "اسم الحزمة" الذي سيحل محل القيمة android:host مشابهاً لما يلي: com.azuresamples.msalandroidapp. يجب أن تبدو تجزئة التوقيع التي ستحل محل قيمتك android:path مشابهة لما يلي: /1wIqXSqBj7w+h11ZifsnqwgyKrY=.

    كما ستتمكن من العثور على هذه القيم في وحدة المصادقة لتسجيل تطبيقك. لاحظ أن URI لإعادة التوجيه الخاصة بك سوف تبدو مشابهة لما يلي: msauth://com.azuresamples.msalandroidapp/1wIqXSqBj7w%2Bh11ZifsnqwgyKrY%3D. بينما يتم ترميز تجزئة التوقيع لعنوان URL في نهاية هذه القيمة، يجب ألا تكون تجزئة التوقيع لعنوان URL مشفرة في قيمتك android:path.

استخدام MSAL

إضافة MSAL لمشروعك

  1. في إطار مشروع Android Studio، انتقل إلى التطبيق>build.gradle وأضف ما يلي:

    apply plugin: 'com.android.application'
    
    allprojects {
     repositories {
        mavenCentral()
        google()
        mavenLocal()
        maven {
            url 'https://pkgs.dev.azure.com/MicrosoftDeviceSDK/DuoSDK-Public/_packaging/Duo-SDK-Feed/maven/v1'
        }
        maven {
            name "vsts-maven-adal-android"
            url "https://identitydivision.pkgs.visualstudio.com/_packaging/AndroidADAL/maven/v1"
            credentials {
                username System.getenv("ENV_VSTS_MVN_ANDROIDADAL_USERNAME") != null ? System.getenv("ENV_VSTS_MVN_ANDROIDADAL_USERNAME") : project.findProperty("vstsUsername")
                password System.getenv("ENV_VSTS_MVN_ANDROIDADAL_ACCESSTOKEN") != null ? System.getenv("ENV_VSTS_MVN_ANDROIDADAL_ACCESSTOKEN") : project.findProperty("vstsMavenAccessToken")
            }
        }
        jcenter()
     }
    }
    dependencies{
     implementation 'com.microsoft.identity.client:msal:2.+'
     implementation 'com.microsoft.graph:microsoft-graph:1.5.+'
     }
    packagingOptions{
     exclude("META-INF/jersey-module-version")
    }
    

    المزيد عن Microsoft Graph SDK

عمليات الاستيراد المطلوبة

أضف ما يلي إلى الجزء العلوي من app>src>main>java>com.example(yourapp)>MainActivity.java

import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import com.google.gson.JsonObject;
import com.microsoft.graph.authentication.IAuthenticationProvider; //Imports the Graph sdk Auth interface
import com.microsoft.graph.concurrency.ICallback;
import com.microsoft.graph.core.ClientException;
import com.microsoft.graph.http.IHttpRequest;
import com.microsoft.graph.models.extensions.*;
import com.microsoft.graph.requests.extensions.GraphServiceClient;
import com.microsoft.identity.client.AuthenticationCallback; // Imports MSAL auth methods
import com.microsoft.identity.client.*;
import com.microsoft.identity.client.exception.*;

إنشاء مثيل لـ PublicClientApplication

تهيئة المتغيرات

private final static String[] SCOPES = {"Files.Read"};
/* Azure AD v2 Configs */
final static String AUTHORITY = "https://login.microsoftonline.com/common";
private ISingleAccountPublicClientApplication mSingleAccountApp;

private static final String TAG = MainActivity.class.getSimpleName();

/* UI & Debugging Variables */
Button signInButton;
Button signOutButton;
Button callGraphApiInteractiveButton;
Button callGraphApiSilentButton;
TextView logTextView;
TextView currentUserTextView;

onCreate

داخل الفئة MainActivity، راجع الطريقة التالية onCreate() لإنشاء مثيل MSAL باستخدام SingleAccountPublicClientApplication.

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    initializeUI();

    PublicClientApplication.createSingleAccountPublicClientApplication(getApplicationContext(),
            R.raw.auth_config_single_account, new IPublicClientApplication.ISingleAccountApplicationCreatedListener() {
                @Override
                public void onCreated(ISingleAccountPublicClientApplication application) {
                    mSingleAccountApp = application;
                    loadAccount();
                }
                @Override
                public void onError(MsalException exception) {
                    displayError(exception);
                }
            });
}

loadAccount

//When app comes to the foreground, load existing account to determine if user is signed in
private void loadAccount() {
    if (mSingleAccountApp == null) {
        return;
    }

    mSingleAccountApp.getCurrentAccountAsync(new ISingleAccountPublicClientApplication.CurrentAccountCallback() {
        @Override
        public void onAccountLoaded(@Nullable IAccount activeAccount) {
            // You can use the account data to update your UI or your app database.
            updateUI(activeAccount);
        }

        @Override
        public void onAccountChanged(@Nullable IAccount priorAccount, @Nullable IAccount currentAccount) {
            if (currentAccount == null) {
                // Perform a cleanup task as the signed-in account changed.
                performOperationOnSignOut();
            }
        }

        @Override
        public void onError(@NonNull MsalException exception) {
            displayError(exception);
        }
    });
}

initializeUI

الاستجابة إلى أزرار وطرق الاتصال أو أخطاء السجل وفقاً لذلك.

private void initializeUI(){
        signInButton = findViewById(R.id.signIn);
        callGraphApiSilentButton = findViewById(R.id.callGraphSilent);
        callGraphApiInteractiveButton = findViewById(R.id.callGraphInteractive);
        signOutButton = findViewById(R.id.clearCache);
        logTextView = findViewById(R.id.txt_log);
        currentUserTextView = findViewById(R.id.current_user);

        //Sign in user
        signInButton.setOnClickListener(new View.OnClickListener(){
            public void onClick(View v) {
                if (mSingleAccountApp == null) {
                    return;
                }
                mSingleAccountApp.signIn(MainActivity.this, null, SCOPES, getAuthInteractiveCallback());
            }
        });

        //Sign out user
        signOutButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mSingleAccountApp == null){
                    return;
                }
                mSingleAccountApp.signOut(new ISingleAccountPublicClientApplication.SignOutCallback() {
                    @Override
                    public void onSignOut() {
                        updateUI(null);
                        performOperationOnSignOut();
                    }
                    @Override
                    public void onError(@NonNull MsalException exception){
                        displayError(exception);
                    }
                });
            }
        });

        //Interactive
        callGraphApiInteractiveButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mSingleAccountApp == null) {
                    return;
                }
                mSingleAccountApp.acquireToken(MainActivity.this, SCOPES, getAuthInteractiveCallback());
            }
        });

        //Silent
        callGraphApiSilentButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mSingleAccountApp == null){
                    return;
                }
                mSingleAccountApp.acquireTokenSilentAsync(SCOPES, AUTHORITY, getAuthSilentCallback());
            }
        });
    }

هام

يؤدي تسجيل الخروج باستخدام MSAL إلى إزالة جميع المعلومات المعروفة عن المستخدم من التطبيق، ولكن المستخدم لا يزال لديه جلسة عمل نشطة على أجهزته. إذا حاول المستخدم تسجيل الدخول مرة أخرى، فقد يرى واجهة مستخدم لتسجيل الدخول، ولكنه قد لا يحتاج إلى إعادة إدخال معلومات تسجيل الدخول الخاصة به لأن جلسة عمل الجهاز لا تزال نشطة.

getAuthInteractiveCallback

الاستدعاء المستخدم للطلبات التفاعلية.

private AuthenticationCallback getAuthInteractiveCallback() {
    return new AuthenticationCallback() {
        @Override
        public void onSuccess(IAuthenticationResult authenticationResult) {
            /* Successfully got a token, use it to call a protected resource - MSGraph */
            Log.d(TAG, "Successfully authenticated");
            /* Update UI */
            updateUI(authenticationResult.getAccount());
            /* call graph */
            callGraphAPI(authenticationResult);
        }

        @Override
        public void onError(MsalException exception) {
            /* Failed to acquireToken */
            Log.d(TAG, "Authentication failed: " + exception.toString());
            displayError(exception);
        }
        @Override
        public void onCancel() {
            /* User canceled the authentication */
            Log.d(TAG, "User cancelled login.");
        }
    };
}

getAuthSilentCallback

الاستدعاء المستخدم للطلبات الصامتة

private SilentAuthenticationCallback getAuthSilentCallback() {
    return new SilentAuthenticationCallback() {
        @Override
        public void onSuccess(IAuthenticationResult authenticationResult) {
            Log.d(TAG, "Successfully authenticated");
            callGraphAPI(authenticationResult);
        }
        @Override
        public void onError(MsalException exception) {
            Log.d(TAG, "Authentication failed: " + exception.toString());
            displayError(exception);
        }
    };
}

استدعاء واجهة تسجيل المستخدم لـ Microsoft Graph

توضح التعليمة البرمجية التالية كيفية استدعاء GraphAPI باستخدام SDK Graph.

callGraphAPI

private void callGraphAPI(IAuthenticationResult authenticationResult) {

    final String accessToken = authenticationResult.getAccessToken();

    IGraphServiceClient graphClient =
            GraphServiceClient
                    .builder()
                    .authenticationProvider(new IAuthenticationProvider() {
                        @Override
                        public void authenticateRequest(IHttpRequest request) {
                            Log.d(TAG, "Authenticating request," + request.getRequestUrl());
                            request.addHeader("Authorization", "Bearer " + accessToken);
                        }
                    })
                    .buildClient();
    graphClient
            .me()
            .drive()
            .buildRequest()
            .get(new ICallback<Drive>() {
                @Override
                public void success(final Drive drive) {
                    Log.d(TAG, "Found Drive " + drive.id);
                    displayGraphResult(drive.getRawObject());
                }

                @Override
                public void failure(ClientException ex) {
                    displayError(ex);
                }
            });
}

أضف واجهة المستخدم

النشاط

إذا كنت ترغب في نمذجة واجهة المستخدم الخاصة بك من هذا البرنامج التعليمي، الأساليب التالية توفر دليلاً لتحديث النص والاستجابة إلى الأزرار.

updateUI

تمكين/ تعطيل الأزرار استناداً إلى حالة تسجيل الدخول وتعيين النص.

private void updateUI(@Nullable final IAccount account) {
    if (account != null) {
        signInButton.setEnabled(false);
        signOutButton.setEnabled(true);
        callGraphApiInteractiveButton.setEnabled(true);
        callGraphApiSilentButton.setEnabled(true);
        currentUserTextView.setText(account.getUsername());
    } else {
        signInButton.setEnabled(true);
        signOutButton.setEnabled(false);
        callGraphApiInteractiveButton.setEnabled(false);
        callGraphApiSilentButton.setEnabled(false);
        currentUserTextView.setText("");
        logTextView.setText("");
    }
}

displayError

private void displayError(@NonNull final Exception exception) {
       logTextView.setText(exception.toString());
   }

displayGraphResult

private void displayGraphResult(@NonNull final JsonObject graphResponse) {
      logTextView.setText(graphResponse.toString());
  }

performOperationOnSignOut

أسلوب تحديث النص في واجهة المستخدم ليعكس تسجيل الخروج.

private void performOperationOnSignOut() {
    final String signOutText = "Signed Out.";
    currentUserTextView.setText("");
    Toast.makeText(getApplicationContext(), signOutText, Toast.LENGTH_SHORT)
            .show();
}

Layout

ملف activity_main.xml عينة لعرض الأزرار ومربعات النص.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#FFFFFF"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:paddingTop="5dp"
        android:paddingBottom="5dp"
        android:weightSum="10">

        <Button
            android:id="@+id/signIn"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="5"
            android:gravity="center"
            android:text="Sign In"/>

        <Button
            android:id="@+id/clearCache"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="5"
            android:gravity="center"
            android:text="Sign Out"
            android:enabled="false"/>

    </LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:orientation="horizontal">

        <Button
            android:id="@+id/callGraphInteractive"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="5"
            android:text="Get Graph Data Interactively"
            android:enabled="false"/>

        <Button
            android:id="@+id/callGraphSilent"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="5"
            android:text="Get Graph Data Silently"
            android:enabled="false"/>
    </LinearLayout>

    <TextView
        android:text="Getting Graph Data..."
        android:textColor="#3f3f3f"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="5dp"
        android:id="@+id/graphData"
        android:visibility="invisible"/>

    <TextView
        android:id="@+id/current_user"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_marginTop="20dp"
        android:layout_weight="0.8"
        android:text="Account info goes here..." />

    <TextView
        android:id="@+id/txt_log"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_marginTop="20dp"
        android:layout_weight="0.8"
        android:text="Output goes here..." />
</LinearLayout>

اختبار التطبيق الخاص بك

شغّل محلياً

قم بإنشاء التطبيق ونشره على جهاز اختبار أو محاكٍ. يجب أن تكون قادراً على تسجيل الدخول والحصول على رموز مميزة لـ Microsoft Azure Active Directory أو حسابات Microsoft الشخصية.

بعد تسجيل الدخول، سيعرض التطبيق البيانات التي تم إرجاعها من نقطة نهاية Microsoft Graph/me. PR 4

في المرة الأولى التي يسجل فيها المستخدم الدخول إلى تطبيقك، ستتم مطالبتهم من خلال هوية Microsoft بالموافقة على الأذونات المطلوبة. قام بعض مستأجري Microsoft Azure Active Directory بتعطيل موافقة المستخدم، والتي تتطلب موافقة المسؤولين نيابةً عن جميع المستخدمين. لدعم هذا السيناريو، ستحتاج إما إلى إنشاء مستأجر خاص بك أو الحصول على موافقة المسؤول.

تنظيف الموارد

عند عدم الحاجة، احذف كائن التطبيق الذي أنشأته في خطوة Register your application.

الدعم والتعليمات

إذا كنت بحاجة إلى مساعدة، أو تريد الإبلاغ عن مشكلة، أو ترغب في التعرف على خيارات الدعم الخاصة بك، فراجع التعليمات والدعم للمطورين.

الخطوات التالية

تعرّف على المزيد حول إنشاء تطبيقات الأجهزة المحمولة التي تستدعي واجهات برمجة تطبيقات الويب المحمية في سلسلة مخططات افتراضية متعددة الجهات.