Play Silhouette und Social logins
Die meisten Benutzer können sich nicht die Mühe machen, ein Formular mit ihrer E-Mail, ihrem vollständigen Namen, demselben Login wie auf jeder anderen Website und (dem schwierigsten Teil) zwei identischen, komplizierten und starken Passwörtern auszufüllen. Dann müssen sie sich jedes Mal, wenn sie auf Ihre Website zurückkehren, erneut anmelden, was bedeutet, dass sie sich daran erinnern müssen, welches Passwort sie benutzt haben. Eine Möglichkeit, dies zu vereinfachen, ist die Autorisierung von Nutzern für Ihre App mit Hilfe von Social Logins.
Play und Silhouette
Silhouette ist eine beliebte Scala-Authentifizierungsbibliothek für das Play Framework. Sie unterstützt mehrere Authentifizierungsmethoden – einschließlich OAuth2. Sie enthält außerdem mehrere vorkonfigurierte Provider-Klassen für beliebte soziale Websites, was den Arbeitsaufwand minimiert.
Konfiguration
Als Erstes müssen wir den Anbieter der state parameter
konfigurieren. Wir werden ihn verwenden, um CSRF-Angriffe zu verhindern.
Der erste Schritt ist das Hinzufügen einiger Konfigurationsschlüssel und -werte zu silhouette.conf
. Unser Schnipsel unten zeigt die Konfiguration sowohl für den CSRF-State-Handler als auch für den OAuth2-State-Provider – da wir beide benötigen werden.
# OAuth2 state provider settings oauth2StateProvider.cookieName="OAuth2TokenSecret" oauth2StateProvider.cookiePath="/" oauth2StateProvider.secureCookie=false oauth2StateProvider.httpOnlyCookie=true oauth2StateProvider.sameSite="Lax" oauth2StateProvider.expirationTime=5 minutes oauth2StateProvider.signer.key = "[changeme]" oauth2StateProvider.crypter.key = "[changeme]" # Social state handler socialStateHandler.signer.key = "[changeme]" # CSRF state item handler settings csrfStateItemHandler.cookieName="OAuth2State" csrfStateItemHandler.cookiePath="/" csrfStateItemHandler.secureCookie=false csrfStateItemHandler.httpOnlyCookie=true csrfStateItemHandler.sameSite="Lax" csrfStateItemHandler.expirationTime=5 minutes csrfStateItemHandler.signer.key = "[changeme]"
Signers
Als Nächstes müssenSigner
für den CSRF State Item Handler und den OAuth2 State Provider erstellt werden. Sie werden verwendet, um clientseitige Cookies zu signieren. Diese Implementierung sollte wie die untenstehende aussehen und in der gleichen Datei wie der Rest der Silhouette-Funktionen platziert werden, z. B. SilhouetteModule.scala
.
/** * Provides the signer for the social state handler. * * @param configuration The Play configuration. * @return The signer for the social state handler. */ @Provides @Named("social-state-signer") def provideSocialStateSigner(configuration: Configuration): Signer = { val config = configuration.underlying.as[JcaSignerSettings]("silhouette.socialStateHandler.signer") new JcaSigner(config) } /** * Provides the signer for the CSRF state item handler. * * @param configuration The Play configuration. * @return The signer for the CSRF state item handler. */ @Provides @Named("csrf-state-item-signer") def provideCSRFStateItemSigner(configuration: Configuration): Signer = { val config = configuration.underlying.as[JcaSignerSettings]("silhouette.csrfStateItemHandler.signer") new JcaSigner(config) }
Anbieter
Nachdem wir Signer
erstellt haben, können wir sowohl einen CSRF State Item Handler als auch einen Social State Handler erstellen. Zunächst erstellen wir einen CSRF-Item-Handler.
/** * Provides the CSRF state item handler. * * @param idGenerator The ID generator implementation. * @param signer The signer implementation. * @param configuration The Play configuration. * @return The CSRF state item implementation. */ @Provides def provideCsrfStateItemHandler( idGenerator: IDGenerator, @Named("csrf-state-item-signer") signer: Signer, configuration: Configuration): CsrfStateItemHandler = { val settings = configuration.underlying.as[CsrfStateSettings]("silhouette.csrfStateItemHandler") new CsrfStateItemHandler(settings, idGenerator, signer) }
Mit diesem CSRF-State-Item-Handler werden wir nun den Social-State-Handler erstellen. Wir werden DefaultSocialStateHandler
verwenden, da die Silhouette-Dokumentation nahelegt, dass dies die beste Option ist.
/** * Provides the social state handler. * * @param signer The signer implementation. * @return The social state handler implementation. */ @Provides def provideSocialStateHandler( @Named("social-state-signer") signer: Signer, csrfStateItemHandler: CsrfStateItemHandler): SocialStateHandler = { new DefaultSocialStateHandler(Set(csrfStateItemHandler), signer) }
Anbieter sozialer Medien
Nachdem wir nun Silhouette für den OAuth2-Workflow konfiguriert und gegen CSRF-Angriffe gesichert haben, können wir unsere Anbieter für soziale Websites erstellen. Für die Zwecke dieses Artikels werden wir nur den Google-Anbieter implementieren, aber andere Konfigurationen sind sehr ähnlich. Sie können Beispielkonfigurationen in der Silhouette-Dokumentation finden. Höchstwahrscheinlich werden Sie mehr als einen Anbieter für den Komfort Ihrer Benutzer verwenden wollen. Der in diesem Teil des Artikels vorgestellte Code ist leicht erweiterbar.
Anbieter-Konfiguration
Als Erstes müssen Sie die Konfiguration des Anbieters in die Datei silhouette.conf
aufnehmen. Für Google sollte sie ähnlich wie die folgende aussehen.
google { authorizationURL="https://accounts.google.com/o/oauth2/auth" accessTokenURL="https://accounts.google.com/o/oauth2/token" redirectURL=YOUR_APP_DOMAIN/authenticate/google" clientID="some_client_id" clientSecret="some_client_secret" scope="profile email" }
Hinweis: Sie müssen clientID und clientSecret in Ihrem Google-Konto erstellen.
Der nächste Schritt besteht darin, einen GoogleProvider
. zu erstellen. Auch hier werden wir dies in der Klasse SilhouetteModule
tun. Das folgende Codeschnipsel zeigt eine Implementierung, die die in silhouette.conf
gespeicherte Konfiguration verwendet.
/** * Provides the Google provider. * * @param httpLayer The HTTP layer implementation. * @param socialStateHandler The social state handler implementation. * @param configuration The Play configuration. * @return The Google provider. */ @Provides def provideGoogleProvider( httpLayer: HTTPLayer, socialStateHandler: SocialStateHandler, configuration: Configuration): GoogleProvider = { new GoogleProvider(httpLayer, socialStateHandler, configuration.underlying.as[OAuth2Settings]("silhouette.google")) }
Jetzt könnten wir nur diesen Anbieter für die weitere Implementierung verwenden, aber um die Erweiterbarkeit zu erleichtern, werden wir SocialProviderRegistry
erstellen. So können wir in Zukunft leicht weitere Anbieter hinzufügen, ohne dass wir die Implementierung anderer Klassen ändern müssen. Wir können diese Registry wie folgt erstellen:
/** * Provides the social provider registry. * * @param facebookProvider The Facebook provider implementation. * @param googleProvider The Google provider implementation. * @param linkedInProvider The LinkedIn provider implementation. * @return The Silhouette environment. */ @Provides def provideSocialProviderRegistry( googleProvider: GoogleProvider // add more providers here ): SocialProviderRegistry = { SocialProviderRegistry(Seq( googleProvider // add more providers here )) }
Authentifizierungs-Controller
Nachdem wir nun Anbieter erstellt haben, müssen wir einen Endpunkt für die Authentifizierung erstellen. Um auf unser Repository zuzugreifen, müssen wir es zunächst in den Controller injizieren, indem wir @Inject()(socialProviderRegistry: SocialProviderRegistry)
zur Controllerklasse hinzufügen. Der nächste Schritt besteht darin, eineAction
zu erstellen, mit der sich der Benutzer auf der Grundlage der vom Anbieter gesendeten Daten anmeldet. Die folgende Methode verwendet den provider
String, um festzustellen, welcher soziale Anbieter von unserem Benutzer verwendet wurde, und verwendet den richtigen. Anschließend wird versucht, den Benutzer anhand des zuvor ermittelten Anbieters zu authentifizieren. Als Nächstes wird versucht, den Benutzer mit Hilfe des zuvor bestimmten Anbieters zu authentifizieren. Wenn dies erfolgreich ist (richtig), werden die vom sozialen Anbieter gesendeten Profildaten abgerufen und in der Datenbank gespeichert (userService.save(profile)
ist eine benutzerdefinierte Funktion, die Sie selbst erstellen müssen). Wenn der Dienst Some(user) zurückgibt, was in unserer Implementierung bedeutet, dass die Daten korrekt gespeichert wurden, wird ein Authenticator erstellt und ein LoginEvent
veröffentlicht, mit dem ein neuer Benutzer angemeldet wird.
/** * Social log in * * @param provider login provider */ def socialAuthenticate(provider: String) = silhouette.UnsecuredAction.async { implicit request => (socialProviderRegistry.get[SocialProvider](provider) match { case Some(p: SocialProvider with CommonSocialProfileBuilder) => p.authenticate().flatMap { case Left(result) => Future.successful(result) // Return authentication result with error case Right(authInfo) => (for { profile <- p.retrieveProfile(authInfo) optionalUser <- userService.save(profile) } yield optionalUser match { case Some(user) => for { authenticator <- silhouette.env.authenticatorService.create(user.loginInfo) value <- silhouette.env.authenticatorService.init(authenticator) result <- silhouette.env.authenticatorService.embed(value, Redirect(routes.HomeController.index())) } yield { silhouette.env.eventBus.publish(LoginEvent(user, request)) result } case None => // Error handling while saving user Future.successful(Redirect(routes.AuthController.login).flashing("warning" -> Messages("auth.social.alreadyRegistered"))) }).flatten } case _ => Future.failed(new ProviderException(s"Cannot authenticate with unexpected social provider $provider")) // Wrong provider key }).recover { // Any other unforeseen error handling case e: ProviderException => logger.error("Unexpected provider error", e) Redirect(routes.AuthController.login).flashing("warning" -> Messages("auth.social.couldNotAuthenticate")) } }
Wie bei jedem Endpunkt müssen wir ihn zur routes
Konfiguration hinzufügen. Sie sollte in etwa so aussehen:
GET /authenticate/:provider controllers.AuthController.socialAuthenticate(provider: String)
Vorlage
Ein letzter Punkt, der noch erledigt werden muss, ist, den Benutzern den Zugang zu dieser Anmeldemethode zu ermöglichen. Dazu fügen wir dieses Snippet in die Anmeldungsvorlage ein.
@if(socialProviders.providers.nonEmpty) { <div class="social-providers mt-2"> <p class="col-sm-12 justify-content-center d-flex">@messages("auth.signIn.useSocial")</p> <div class="col-sm-12 justify-content-center d-flex"> @for(p <- socialProviders.providers) { <a href="@controllers.routes.AuthController.socialAuthenticate(p.id)" class="social-auth provider @p.id" title="@messages(p.id)"><i aria-hidden="true" class="fab fa-@{p.id} font-50 m-2"></i></a> } </div> </div> }
Damit werden Links zu allen sozialen Anbietern aus unserem Repository erstellt. class="fab fa-@{p.id} font-50 m-2"
holt Anbieter-Icons von FontAwesome.