Samouczek: logowanie użytkowników w aplikacji mobilnej systemu iOS (Swift)
Jest to trzeci samouczek z serii samouczków, który przeprowadzi Cię przez proces logowania użytkowników przy użyciu identyfikatora Entra firmy Microsoft.
Przed rozpoczęciem użyj selektora Wybierz typ najemcy znajdującego się w górnej części tej strony, aby wybrać typ najemcy. Microsoft Entra ID oferuje dwie konfiguracje dzierżawy: pracowników i zewnętrzna. Konfiguracja dzierżawy zasobów pracowniczych obejmuje pracowników, aplikacje wewnętrzne i inne zasoby organizacyjne. Zewnętrzny dzierżawca jest przeznaczony dla aplikacji skierowanych do klientów.
W tym samouczku:
- Zaloguj się użytkownik.
- Wyloguj użytkownika.
- Tworzenie interfejsu użytkownika aplikacji
Warunki wstępne
Logowanie użytkownika
Istnieją dwie główne opcje logowania użytkowników przy użyciu biblioteki Microsoft Authentication Library (MSAL) dla systemu iOS: uzyskiwanie tokenów interaktywnie lub dyskretnie.
Aby interaktywnie zalogować się użytkownika, użyj następującego kodu:
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() } }
Właściwość
promptType
elementuMSALInteractiveTokenParameters
konfiguruje zachowanie uwierzytelniania i monitu o wyrażenie zgody. Obsługiwane są następujące wartości:-
.promptIfNecessary
(wartość domyślna) — użytkownik jest monitowany tylko w razie potrzeby. Doświadczenie logowania jednokrotnego (SSO) jest określane przez obecność plików cookie w interfejsie przeglądarki oraz rodzaj konta. Jeśli wielu użytkowników jest zalogowanych, zostanie wyświetlony proces wyboru konta. Jest to domyślne zachowanie. -
.selectAccount
— jeśli żaden użytkownik nie zostanie określony, w widoku internetowym uwierzytelniania zostanie wyświetlona lista aktualnie zalogowanych kont dla użytkownika do wyboru. -
.login
— wymaga uwierzytelnienia użytkownika w widoku internetowym. Tylko jedno konto może być zalogowane naraz, jeśli określisz tę wartość. -
.consent
— wymaga od użytkownika wyrażenia zgody na bieżący zestaw zakresów żądania.
-
Aby zalogować użytkownika w trybie dyskretnym, użyj następującego kodu:
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() } }
Metoda
acquireTokenSilently
próbuje w sposób dyskretny uzyskać token dostępu dla istniejącego konta MSAL. UżywaapplicationContext
do żądania tokenu z określonymi zakresami. Jeśli wystąpi błąd, sprawdza, czy jest wymagana interakcja użytkownika, a jeśli tak, inicjuje pozyskiwanie tokenu interakcyjnego. Po powodzeniu aktualizuje token dostępu, rejestruje wynik, włącza przycisk wylogowywanie i pobiera zawartość przy użyciu tokenu.
Obsługa wywołania zwrotnego logowania (tylko system iOS)
Otwórz plik AppDelegate.swift. Aby obsłużyć wywołanie zwrotne po zalogowaniu, dodaj MSALPublicClientApplication.handleMSALResponse
do klasy appDelegate
w następujący sposób:
// 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)
}
Jeśli używasz środowiska Xcode 11, należy umieścić wywołanie zwrotne biblioteki MSAL w SceneDelegate.swift. Jeśli dla zgodności ze starszymi wersjami iOS obsługiwane są zarówno UISceneDelegate, jak i UIApplicationDelegate, wywołanie zwrotne MSAL musi zostać umieszczone w obu plikach.
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)
}
Wyloguj użytkownika
Ważny
Wylogowywanie przy użyciu biblioteki MSAL usuwa wszystkie znane informacje o użytkowniku z aplikacji, a także usuwa aktywną sesję na urządzeniu, jeśli jest to dozwolone przez konfigurację urządzenia. Możesz również opcjonalnie wylogować użytkownika z przeglądarki.
Aby dodać możliwość wylogowywania, dodaj następujący kod w klasie 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)
})
}
}
Tworzenie interfejsu użytkownika aplikacji
Teraz utwórz interfejs użytkownika zawierający przycisk wywołujący interfejs API programu Microsoft Graph, inny do wylogowania i widok tekstowy, aby wyświetlić dane wyjściowe, dodając następujący kod do klasy ViewController
:
Interfejs użytkownika systemu 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()
}
Interfejs użytkownika systemu 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() {}
Następnie w klasie ViewController
zastąp metodę 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()
}
Następne kroki
Jest to trzeci samouczek z serii samouczków, który przeprowadzi Cię przez proces logowania użytkowników przy użyciu identyfikatora Entra firmy Microsoft.
Przed rozpoczęciem użyj selektora Wybierz typ najemcy na górze tej strony, aby wybrać typ najemcy. Identyfikator Entra Microsoft udostępnia dwa ustawienia dzierżawy, pracownicy i zewnętrzni. Konfiguracja struktury pracowniczej dotyczy pracowników, aplikacji wewnętrznych i innych zasobów organizacji. Zewnętrzny najemca przeznaczony jest dla aplikacji dla klientów.
W tym samouczku:
- Zaloguj się użytkownik.
- Wyloguj użytkownika.
Warunki wstępne
Logowanie użytkownika
Istnieją dwie główne opcje logowania użytkowników przy użyciu biblioteki Microsoft Authentication Library (MSAL) dla systemu iOS: uzyskiwanie tokenów interaktywnie lub dyskretnie.
Aby interaktywnie zalogować się użytkownika, użyj następującego kodu:
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) } }
Kod najpierw sprawdza, czy kontekst aplikacji i parametry widoku internetowego są dostępne. Następnie aktualizuje logowanie, aby wskazać, że token jest uzyskiwany interaktywnie. Następnie konfiguruje parametry pozyskiwania tokenu interakcyjnego, określając zakresy i parametry widoku internetowego. Ustawia również rodzaj monitu na wybór konta.
Następnie wywołuje metodę
acquireToken
w kontekście aplikacji ze zdefiniowanymi parametrami. W funkcji obsługi zakończenia sprawdza się wszelkie błędy. Jeśli wystąpi błąd, zaktualizuje on rejestrowanie za pomocą komunikatu o błędzie. W przypadku powodzenia pobiera token dostępu z wyniku, aktualizuje rejestrowanie przy użyciu tokenu i aktualizuje bieżące konto.Gdy aplikacja uzyska token dostępu, możesz pobrać oświadczenia skojarzone z bieżącym kontem. W tym celu użyj następującego fragmentu kodu:
let claims = result.account.accountClaims let preferredUsername = claims?["preferred_username"] as? String
Kod odczytuje oświadczenia z konta, korzystając z właściwości
accountClaims
obiekturesult.account
. Następnie pobiera wartość oświadczenia "preferred_username" ze słownika oświadczeń i przypisuje ją do zmiennejpreferredUsername
.Aby zalogować użytkownika w trybie dyskretnym, użyj następującego kodu:
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) } }
Kod inicjuje proces uzyskiwania tokenów w trybie dyskretnym. Najpierw próbuje załadować bieżące konto. Jeśli zostanie znalezione bieżące konto, przejdzie do uzyskania tokenu w trybie dyskretnym przy użyciu tego konta. Jeśli nie znaleziono bieżącego konta, system zaktualizuje logowanie, aby wskazać, że nie znaleziono tokenu, i sugeruje, aby spróbować zdobyć token w sposób interaktywny najpierw.
W powyższym kodzie wywołujemy dwie funkcje,
loadCurrentAccount
iacquireTokenSilently
. FunkcjaloadCurrentAccount
powinna mieć następujący kod: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) } }) }
Kod używa biblioteki MSAL dla systemu iOS do załadowania bieżącego konta. Sprawdza błędy i odpowiednio aktualizuje rejestrowanie. Jeśli zostanie znalezione bieżące konto, aktualizuje je i próbuje uzyskać tokeny w trybie dyskretnym. Jeśli istnieje poprzednie konto, rejestruje wylogowanie. Jeśli nie znaleziono żadnych kont, czyści token dostępu. Na koniec wykonuje blok ukończenia, jeśli zostanie podany.
Funkcja
acquireTokenSilently
powinna zawierać następujący kod: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) } }
Ta funkcja używa biblioteki MSAL dla systemu iOS do dyskretnego uzyskiwania tokenu dla istniejącego konta. Po zweryfikowaniu
applicationContext
loguje proces pozyskiwania tokenu. Przy użyciuMSALSilentTokenParameters
definiuje wymagane parametry. Następnie próbuje uzyskać token w trybie dyskretnym. Jeśli wystąpią błędy, sprawdza wymagania dotyczące interakcji z użytkownikiem, inicjując w razie potrzeby interaktywny proces. Po pomyślnym zakończeniu zaktualizuje właściwośćaccessToken
i zapisze odświeżony token, kończąc przez włączenie przycisku wylogowania.
Wyloguj użytkownika
Aby wylogować użytkownika z aplikacji systemu iOS (Swift) przy użyciu biblioteki MSAL dla systemu iOS, użyj następującego kodu:
@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)
})
}
}
Kod weryfikuje istnienie applicationContext
, currentAccount
i webViewParameters
. Następnie rejestruje proces wylogowywania. Kod usuwa wszystkie tokeny z pamięci podręcznej dla podanego konta. W zależności od bieżącego trybu urządzenia określa, czy wylogować się z przeglądarki. Po zakończeniu aktualizuje odpowiednio tekst rejestrowania. Jeśli podczas procesu wylogowania wystąpi błąd, rejestruje komunikat o błędzie. Po pomyślnym wylogowaniu zmienia token dostępu na pusty ciąg znaków i usuwa dane bieżącego konta.