Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Применяется к:
Клиенты рабочей силы
Внешние клиенты (дополнительные сведения)
Это третий учебник в серии учебников, который поможет вам выполнить вход пользователей с помощью идентификатора Microsoft Entra.
Прежде чем начать, используйте селектор Выбрать тип арендатора в верхней части этой страницы, чтобы выбрать тип арендатора. Microsoft Entra ID предоставляет два варианта конфигурации клиента: для рабочих нужд и для внешних. Конфигурация клиента для сотрудников, внутренних приложений и других организационных ресурсов. Внешний клиент предназначен для ваших клиентских приложений.
В этом руководстве вы:
- Войдите как пользователь.
- Выйти из учетной записи пользователя.
- Создание пользовательского интерфейса приложения
Необходимые условия
- Руководство: подготовка приложения iOS (Swift) для проверки подлинности.
Вход пользователя
У вас есть два основных варианта авторизации пользователей с помощью библиотеки подлинности Майкрософт (MSAL) для iOS: интерактивное или незаметное получение токенов.
Для интерактивного входа в систему используйте следующий код:
func acquireTokenInteractively() { guard let applicationContext = self.applicationContext else { return } guard let webViewParameters = self.webViewParameters else { return } // #1 let parameters = MSALInteractiveTokenParameters(scopes: kScopes, webviewParameters: webViewParameters) parameters.promptType = .selectAccount // #2 applicationContext.acquireToken(with: parameters) { (result, error) in // #3 if let error = error { self.updateLogging(text: "Could not acquire token: \(error)") return } guard let result = result else { self.updateLogging(text: "Could not acquire token: No result returned") return } // #4 self.accessToken = result.accessToken self.updateLogging(text: "Access token is \(self.accessToken)") self.updateCurrentAccount(account: result.account) self.getContentWithToken() } }Свойство
promptTypeвMSALInteractiveTokenParametersнастраивает поведение всплывающих запросов проверки подлинности и согласия. Поддерживаются следующие значения:-
.promptIfNecessary(по умолчанию) — пользователю предлагается только при необходимости. Опыт SSO определяется наличием файлов cookie в веб-интерфейсе и типом учетной записи. При входе нескольких пользователей отображается интерфейс выбора учетной записи. Это поведение по умолчанию. -
.selectAccount- Если пользователь не указан, веб-форма аутентификации покажет список учетных записей, которые уже вошли в систему, из которых пользователь может выбрать. -
.login. Требуется, чтобы пользователь прошел проверку подлинности в веб-представлении. При указании этого значения можно входить только в одну учетную запись одновременно. -
.consent. Требуется, чтобы пользователь согласился с текущим набором областей для запроса.
-
Чтобы выполнить тихий вход для пользователя, используйте следующий код:
func acquireTokenSilently(_ account : MSALAccount!) { guard let applicationContext = self.applicationContext else { return } /** Acquire a token for an existing account silently - forScopes: Permissions you want included in the access token received in the result in the completionBlock. Not all scopes are guaranteed to be included in the access token returned. - account: An account object that we retrieved from the application object before that the authentication flow will be locked down to. - completionBlock: The completion block that will be called when the authentication flow completes, or encounters an error. */ let parameters = MSALSilentTokenParameters(scopes: kScopes, account: account) applicationContext.acquireTokenSilent(with: parameters) { (result, error) in if let error = error { let nsError = error as NSError // interactionRequired means we need to ask the user to sign-in. This usually happens // when the user's Refresh Token is expired or if the user has changed their password // among other possible reasons. if (nsError.domain == MSALErrorDomain) { if (nsError.code == MSALError.interactionRequired.rawValue) { DispatchQueue.main.async { self.acquireTokenInteractively() } return } } self.updateLogging(text: "Could not acquire token silently: \(error)") return } guard let result = result else { self.updateLogging(text: "Could not acquire token: No result returned") return } self.accessToken = result.accessToken self.updateLogging(text: "Refreshed Access token is \(self.accessToken)") self.updateSignOutButton(enabled: true) self.getContentWithToken() } }Метод
acquireTokenSilentlyпытается незаметно получить токен доступа для существующей учетной записи MSAL. Он используетapplicationContextдля запроса токена с указанными полномочиями. При возникновении ошибки проверяется, требуется ли взаимодействие с пользователем, и в случае необходимости инициируется интерактивное получение токена. После успешного выполнения он обновляет маркер доступа, регистрирует результат, включает кнопку выхода и извлекает содержимое с помощью маркера.
Обработать обратный вызов для логина (только для iOS)
Откройте файл AppDelegate.swift. Чтобы выполнить обратный вызов после входа, добавьте MSALPublicClientApplication.handleMSALResponse в класс appDelegate следующим образом:
// Inside AppDelegate...
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
return MSALPublicClientApplication.handleMSALResponse(url, sourceApplication: options[UIApplication.OpenURLOptionsKey.sourceApplication] as? String)
}
Если вы используете Xcode 11, то вместо этого следует поместить обратный вызов MSAL в SceneDelegate.swift. Если вы поддерживаете UISceneDelegate и UIApplicationDelegate для совместимости с более старыми версиями iOS, вызов MSAL необходимо поместить в оба файла.
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
guard let urlContext = URLContexts.first else {
return
}
let url = urlContext.url
let sourceApp = urlContext.options.sourceApplication
MSALPublicClientApplication.handleMSALResponse(url, sourceApplication: sourceApp)
}
Выход из учётной записи пользователя
Важный
При выходе с помощью MSAL удаляются все известные сведения о пользователе из приложения, а также активный сеанс на устройстве удаляется, если это разрешено конфигурацией устройства. Кроме того, вы можете по желанию выйти из браузера.
Чтобы добавить возможность выхода, добавьте следующий код в класс ViewController.
@objc func signOut(_ sender: AnyObject) {
guard let applicationContext = self.applicationContext else { return }
guard let account = self.currentAccount else { return }
do {
/**
Removes all tokens from the cache for this application for the provided account
- account: The account to remove from the cache
*/
let signoutParameters = MSALSignoutParameters(webviewParameters: self.webViewParameters!)
signoutParameters.signoutFromBrowser = false // set this to true if you also want to signout from browser or webview
applicationContext.signout(with: account, signoutParameters: signoutParameters, completionBlock: {(success, error) in
if let error = error {
self.updateLogging(text: "Couldn't sign out account with error: \(error)")
return
}
self.updateLogging(text: "Sign out completed successfully")
self.accessToken = ""
self.updateCurrentAccount(account: nil)
})
}
}
Создание пользовательского интерфейса приложения
Теперь создайте пользовательский интерфейс, который включает кнопку для вызова API Microsoft Graph, другой для выхода и текстового представления, чтобы просмотреть некоторые выходные данные, добавив следующий код в класс ViewController:
Пользовательский интерфейс iOS
var loggingText: UITextView!
var signOutButton: UIButton!
var callGraphButton: UIButton!
var usernameLabel: UILabel!
func initUI() {
usernameLabel = UILabel()
usernameLabel.translatesAutoresizingMaskIntoConstraints = false
usernameLabel.text = ""
usernameLabel.textColor = .darkGray
usernameLabel.textAlignment = .right
self.view.addSubview(usernameLabel)
usernameLabel.topAnchor.constraint(equalTo: view.topAnchor, constant: 50.0).isActive = true
usernameLabel.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -10.0).isActive = true
usernameLabel.widthAnchor.constraint(equalToConstant: 300.0).isActive = true
usernameLabel.heightAnchor.constraint(equalToConstant: 50.0).isActive = true
// Add call Graph button
callGraphButton = UIButton()
callGraphButton.translatesAutoresizingMaskIntoConstraints = false
callGraphButton.setTitle("Call Microsoft Graph API", for: .normal)
callGraphButton.setTitleColor(.blue, for: .normal)
callGraphButton.addTarget(self, action: #selector(callGraphAPI(_:)), for: .touchUpInside)
self.view.addSubview(callGraphButton)
callGraphButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
callGraphButton.topAnchor.constraint(equalTo: view.topAnchor, constant: 120.0).isActive = true
callGraphButton.widthAnchor.constraint(equalToConstant: 300.0).isActive = true
callGraphButton.heightAnchor.constraint(equalToConstant: 50.0).isActive = true
// Add sign out button
signOutButton = UIButton()
signOutButton.translatesAutoresizingMaskIntoConstraints = false
signOutButton.setTitle("Sign Out", for: .normal)
signOutButton.setTitleColor(.blue, for: .normal)
signOutButton.setTitleColor(.gray, for: .disabled)
signOutButton.addTarget(self, action: #selector(signOut(_:)), for: .touchUpInside)
self.view.addSubview(signOutButton)
signOutButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
signOutButton.topAnchor.constraint(equalTo: callGraphButton.bottomAnchor, constant: 10.0).isActive = true
signOutButton.widthAnchor.constraint(equalToConstant: 150.0).isActive = true
signOutButton.heightAnchor.constraint(equalToConstant: 50.0).isActive = true
let deviceModeButton = UIButton()
deviceModeButton.translatesAutoresizingMaskIntoConstraints = false
deviceModeButton.setTitle("Get device info", for: .normal);
deviceModeButton.setTitleColor(.blue, for: .normal);
deviceModeButton.addTarget(self, action: #selector(getDeviceMode(_:)), for: .touchUpInside)
self.view.addSubview(deviceModeButton)
deviceModeButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
deviceModeButton.topAnchor.constraint(equalTo: signOutButton.bottomAnchor, constant: 10.0).isActive = true
deviceModeButton.widthAnchor.constraint(equalToConstant: 150.0).isActive = true
deviceModeButton.heightAnchor.constraint(equalToConstant: 50.0).isActive = true
// Add logging textfield
loggingText = UITextView()
loggingText.isUserInteractionEnabled = false
loggingText.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(loggingText)
loggingText.topAnchor.constraint(equalTo: deviceModeButton.bottomAnchor, constant: 10.0).isActive = true
loggingText.leftAnchor.constraint(equalTo: self.view.leftAnchor, constant: 10.0).isActive = true
loggingText.rightAnchor.constraint(equalTo: self.view.rightAnchor, constant: -10.0).isActive = true
loggingText.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: 10.0).isActive = true
}
func platformViewDidLoadSetup() {
NotificationCenter.default.addObserver(self,
selector: #selector(appCameToForeGround(notification:)),
name: UIApplication.willEnterForegroundNotification,
object: nil)
}
@objc func appCameToForeGround(notification: Notification) {
self.loadCurrentAccount()
}
Пользовательский интерфейс macOS
var callGraphButton: NSButton!
var loggingText: NSTextView!
var signOutButton: NSButton!
var usernameLabel: NSTextField!
func initUI() {
usernameLabel = NSTextField()
usernameLabel.translatesAutoresizingMaskIntoConstraints = false
usernameLabel.stringValue = ""
usernameLabel.isEditable = false
usernameLabel.isBezeled = false
self.view.addSubview(usernameLabel)
usernameLabel.topAnchor.constraint(equalTo: view.topAnchor, constant: 30.0).isActive = true
usernameLabel.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -10.0).isActive = true
// Add call Graph button
callGraphButton = NSButton()
callGraphButton.translatesAutoresizingMaskIntoConstraints = false
callGraphButton.title = "Call Microsoft Graph API"
callGraphButton.target = self
callGraphButton.action = #selector(callGraphAPI(_:))
callGraphButton.bezelStyle = .rounded
self.view.addSubview(callGraphButton)
callGraphButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
callGraphButton.topAnchor.constraint(equalTo: view.topAnchor, constant: 50.0).isActive = true
callGraphButton.heightAnchor.constraint(equalToConstant: 34.0).isActive = true
// Add sign out button
signOutButton = NSButton()
signOutButton.translatesAutoresizingMaskIntoConstraints = false
signOutButton.title = "Sign Out"
signOutButton.target = self
signOutButton.action = #selector(signOut(_:))
signOutButton.bezelStyle = .texturedRounded
self.view.addSubview(signOutButton)
signOutButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
signOutButton.topAnchor.constraint(equalTo: callGraphButton.bottomAnchor, constant: 10.0).isActive = true
signOutButton.heightAnchor.constraint(equalToConstant: 34.0).isActive = true
signOutButton.isEnabled = false
// Add logging textfield
loggingText = NSTextView()
loggingText.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(loggingText)
loggingText.topAnchor.constraint(equalTo: signOutButton.bottomAnchor, constant: 10.0).isActive = true
loggingText.leftAnchor.constraint(equalTo: self.view.leftAnchor, constant: 10.0).isActive = true
loggingText.rightAnchor.constraint(equalTo: self.view.rightAnchor, constant: -10.0).isActive = true
loggingText.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -10.0).isActive = true
loggingText.widthAnchor.constraint(equalToConstant: 500.0).isActive = true
loggingText.heightAnchor.constraint(equalToConstant: 300.0).isActive = true
}
func platformViewDidLoadSetup() {}
Затем в классе ViewController замените метод viewDidLoad() следующим образом:
override func viewDidLoad() {
super.viewDidLoad()
initUI()
do {
try self.initMSAL()
} catch let error {
self.updateLogging(text: "Unable to create Application Context \(error)")
}
self.loadCurrentAccount()
self.platformViewDidLoadSetup()
}
Дальнейшие действия
Руководство: вызов защищенного веб-API в приложении iOS (Swift)
Это третий учебник в серии учебников, который поможет вам выполнить вход пользователей с помощью идентификатора Microsoft Entra.
Прежде чем начать, используйте селектор Выбрать тип арендатора в верхней части этой страницы, чтобы выбрать тип арендатора. Microsoft Entra ID предоставляет два варианта конфигурации клиента: для рабочих нужд и для внешних. Конфигурация клиента для сотрудников, внутренних приложений и других организационных ресурсов. Внешний клиент предназначен для ваших клиентских приложений.
В этом руководстве вы:
- Войдите как пользователь.
- Выйти из учетной записи пользователя.
Необходимые условия
- Руководство: подготовка приложения iOS (Swift) для проверки подлинности.
Вход пользователя
У вас есть два основных варианта авторизации пользователей с помощью библиотеки подлинности Майкрософт (MSAL) для iOS: интерактивное или незаметное получение токенов.
Для интерактивного входа в систему используйте следующий код:
acquireTokenInteractively() { guard let applicationContext = self.applicationContext else { return } guard let webViewParameters = self.webViewParameters else { return } updateLogging(text: "Acquiring token interactively...") let parameters = MSALInteractiveTokenParameters(scopes: Configuration.kScopes, webviewParameters: webViewParameters) parameters.promptType = .selectAccount applicationContext.acquireToken(with: parameters) { (result, error) in if let error = error { self.updateLogging(text: "Could not acquire token: \(error)") return } guard let result = result else { self.updateLogging(text: "Could not acquire token: No result returned") return } self.accessToken = result.accessToken self.updateLogging(text: "Access token is \(self.accessToken)") self.updateCurrentAccount(account: result.account) } }Код сначала проверяет, доступны ли параметры контекста приложения и веб-представления. Затем он обновляет логирование, чтобы указать, что токен получается в интерактивном режиме. Затем он устанавливает параметры для интерактивного приобретения токенов, указывая области действия и параметры веб-интерфейса. Он также задает тип запроса для выбора учетной записи.
Затем он вызывает метод
acquireTokenв контексте приложения с определенными параметрами. В обработчике завершения он проверяет наличие ошибок. Если возникла ошибка, он обновляет журналирование с сообщением об ошибке. При успешном выполнении извлекается токен доступа из результата, обновляется логирование с использованием токена и обновляется текущая учетная запись.После получения токена доступа приложение сможет извлечь утверждения, связанные с текущей учетной записью. Для этого используйте следующий фрагмент кода:
let claims = result.account.accountClaims let preferredUsername = claims?["preferred_username"] as? StringКод считывает утверждения из учетной записи путем доступа к свойству
accountClaimsобъектаresult.account. Затем он извлекает значение утверждения "preferred_username" из словаря утверждений и назначает его переменнойpreferredUsername.Чтобы выполнить тихий вход для пользователя, используйте следующий код:
func acquireTokenSilently() { self.loadCurrentAccount { (account) in guard let currentAccount = account else { self.updateLogging(text: "No token found, try to acquire a token interactively first") return } self.acquireTokenSilently(currentAccount) } }Код инициирует процесс получения токенов в фоновом режиме. Сначала он пытается загрузить текущую учетную запись. При обнаружении текущей учетной записи токен автоматически запрашивается с использованием этой учетной записи. Если текущая учетная запись не найдена, журналирование обновляется таким образом, чтобы указать на отсутствие токена, и предлагает сначала попытаться получить токен в интерактивном режиме.
В приведенном выше коде мы вызываем две функции,
loadCurrentAccountиacquireTokenSilently. ФункцияloadCurrentAccountдолжна иметь следующий код:func loadCurrentAccount(completion: AccountCompletion? = nil) { guard let applicationContext = self.applicationContext else { return } let msalParameters = MSALParameters() msalParameters.completionBlockQueue = DispatchQueue.main // Note that this sample showcases an app that signs in a single account at a time applicationContext.getCurrentAccount(with: msalParameters, completionBlock: { (currentAccount, previousAccount, error) in if let error = error { self.updateLogging(text: "Couldn't query current account with error: \(error)") return } if let currentAccount = currentAccount { self.updateCurrentAccount(account: currentAccount) self.acquireTokenSilently(currentAccount) if let completion = completion { completion(self.currentAccount) } return } // If testing with Microsoft's shared device mode, see the account that has been signed out from another app. More details here: // https://docs.microsoft.com/azure/active-directory/develop/msal-ios-shared-devices if let previousAccount = previousAccount { self.updateLogging(text: "The account with username \(String(describing: previousAccount.username)) has been signed out.") } else { self.updateLogging(text: "") } self.accessToken = "" self.updateCurrentAccount(account: nil) if let completion = completion { completion(nil) } }) }Код использует MSAL для iOS для загрузки текущей учетной записи. Он проверяет наличие ошибок и обновляет ведение журнала соответствующим образом. Если обнаружена текущая учетная запись, она обновляется и пытается в фоновом режиме получить токены. Если предыдущая учетная запись существует, то регистрируется выход. Если учетные записи не найдены, она очищает токен доступа. Наконец, он выполняет блок завершения, если он указан.
Функция
acquireTokenSilentlyдолжна содержать следующий код:func acquireTokenSilently(_ account : MSALAccount) { guard let applicationContext = self.applicationContext else { return } /** Acquire a token for an existing account silently - forScopes: Permissions you want included in the access token received in the result in the completionBlock. Not all scopes are guaranteed to be included in the access token returned. - account: An account object that we retrieved from the application object before that the authentication flow will be locked down to. - completionBlock: The completion block that will be called when the authentication flow completes, or encounters an error. */ updateLogging(text: "Acquiring token silently...") let parameters = MSALSilentTokenParameters(scopes: Configuration.kScopes, account: account) applicationContext.acquireTokenSilent(with: parameters) { (result, error) in if let error = error { let nsError = error as NSError // interactionRequired means we need to ask the user to sign-in. This usually happens // when the user's Refresh Token is expired or if the user has changed their password // among other possible reasons. if (nsError.domain == MSALErrorDomain) { if (nsError.code == MSALError.interactionRequired.rawValue) { DispatchQueue.main.async { self.acquireTokenInteractively() } return } } self.updateLogging(text: "Could not acquire token silently: \(error)") return } guard let result = result else { self.updateLogging(text: "Could not acquire token: No result returned") return } self.accessToken = result.accessToken self.updateLogging(text: "Refreshed Access token is \(self.accessToken)") self.updateSignOutButton(enabled: true) } }Эта функция использует MSAL для iOS, чтобы незаметно получить токен для существующей учетной записи. После проверки
applicationContextон логирует процесс приобретения токена. ИспользуяMSALSilentTokenParameters, он определяет необходимые параметры. Затем он пытается получить токен незаметно. Если возникают ошибки, он проверяет требования взаимодействия с пользователем, инициируя интерактивный процесс при необходимости. После успешного завершения он обновляет свойствоaccessToken, логирует обновленный токен и завершает процесс, активируя кнопку выхода.
Выход из учётной записи пользователя
Чтобы вывести пользователя из приложения iOS (Swift) с помощью MSAL для iOS, используйте следующий код:
@IBAction func signOut(_ sender: UIButton) {
guard let applicationContext = self.applicationContext else { return }
guard let account = self.currentAccount else { return }
guard let webViewParameters = self.webViewParameters else { return }
updateLogging(text: "Signing out...")
do {
/**
Removes all tokens from the cache for this application for the provided account
- account: The account to remove from the cache
*/
let signoutParameters = MSALSignoutParameters(webviewParameters: webViewParameters)
// If testing with Microsoft's shared device mode, trigger signout from browser. More details here:
// https://docs.microsoft.com/azure/active-directory/develop/msal-ios-shared-devices
if (self.currentDeviceMode == .shared) {
signoutParameters.signoutFromBrowser = true
} else {
signoutParameters.signoutFromBrowser = false
}
applicationContext.signout(with: account, signoutParameters: signoutParameters, completionBlock: {(success, error) in
if let error = error {
self.updateLogging(text: "Couldn't sign out account with error: \(error)")
return
}
self.updateLogging(text: "Sign out completed successfully")
self.accessToken = ""
self.updateCurrentAccount(account: nil)
})
}
}
Код проверяет наличие applicationContext, currentAccountи webViewParameters. Затем он регистрирует процесс выхода. Код удаляет все маркеры из кэша для предоставленной учетной записи. В зависимости от текущего режима устройства определяется, следует ли выйти из браузера. По завершении он обновляет текст ведения журнала соответствующим образом. Если во время процесса выхода возникает ошибка, он регистрирует сообщение об ошибке. После успешного выхода он обновляет маркер доступа до пустой строки и очищает текущую учетную запись.
Дальнейшие действия
Руководство: вызов защищенного веб-API в приложении iOS (Swift)