Dies hat eine ganze Weile gedauert, bis ich es herausgefunden habe, aber ich habe schließlich eine funktionierende Lösung gefunden.
Den Großteil des Credits möchte ich auch Jordan MacDonald geben, der die Frage, die ich oben erwähnt habe, in der Devise Google Group gepostet hat. Obwohl dieser Thread keine Antwort hatte, fand ich das Projekt, an dem er gearbeitet hatte, las den Code und passte ihn an meine Bedürfnisse an. Das Projekt ist Triage und ich empfehle dringend, die Implementierungen des SessionController und des Routings zu lesen.
Ich empfehle auch Jordan's Blog-Post über Devise: http://www.wastedintelligence.com/blog/2013/04/07/understanding-devise/
Modell
Wie oben ist mein Modell wie folgt, und ich verwende das Paket devise_ldap_authenticatable
. In diesem Beispiel habe ich zwei Benutzer, LdapUser
und LocalUser
, aber ich sehe keinen Grund, warum dies nicht für beliebige zwei Devise-Benutzermodelle funktionieren würde, solange Sie eine Möglichkeit haben, sie zu unterscheiden.
class User < ActiveRecord::Base
end
class LdapUser < User
devise :ldap_authenticatable, :rememberable, :trackable
end
class LocalUser < User
devise :database_authenticatable, :registerable, :confirmable, :recoverable, :trackable
end
Controller
Der erste Teil, den wir benötigen, ist der Controller. Er sollte von Devise::SessionsController
erben und wählt aus, um welchen Typ Benutzer es sich handelt, leitet dies explizit an die Authentifizierungsphase weiter, die von Warden behandelt wird.
Weil ich LDAP gegen eine Active Directory-Domäne für einen Teil der Authentifizierung verwendet habe, konnte ich leicht erkennen, welche Details gegen LDAP authentifiziert werden sollten und welche nicht, aber das ist implementationsabhängig.
class SessionsController < Devise::SessionsController
def create
# Ermittle, um welchen Typ Benutzer es sich handelt.
# Die Methode 'type_if_user' ist implementationsabhängig und nicht bereitgestellt.
user_class = nil
error_string = 'Anmeldung fehlgeschlagen'
if type_of_user(request.params['user']) == :something
user_class = :local_user
error_string = 'Benutzername oder Passwort inkorrekt'
else
user_class = :ldap_user
error_string = 'LDAP-Details inkorrekt'
end
# Kopiere Benutzerdaten zu ldap_user und local_user
request.params['ldap_user'] = request.params['local_user'] = request.params['user']
# Verwende Warden, um den Benutzer zu authentifizieren, falls wir nil zurückbekommen, ist es fehlgeschlagen.
self.resource = warden.authenticate scope: user_class
if self.resource.nil?
flash[:error] = error_string
return redirect_to new_session_path
end
# Jetzt, da wir wissen, dass der Benutzer authentifiziert ist, melde ihn auf der Website mit Devise an
# Zu diesem Zeitpunkt ist self.resource ein gültiges Benutzerkonto.
sign_in(user_class, self.resource)
respond_with self.resource, :location => after_sign_in_path_for(self.resource)
end
def destroy
# Sitzung beenden
end
def new
# Lege eine leere Ressource für die Ansichtsrendering fest
self.resource = User.new
end
end
Routen
Devise richtet viele Routen für jeden Benutzertyp ein, und größtenteils möchten wir dies zulassen. Da wir jedoch den SessionsController
überschreiben, müssen wir diesen Teil überspringen.
Nachdem es seine Routen eingerichtet hat, möchten wir dann unsere eigenen Handler für sign_in
und sign_out
hinzufügen. Beachten Sie, dass der Devise-Scope local_user
keine Rolle spielt. Er braucht nur einen Standardbereich, den wir sowieso im Controller überschreiben. Beachten Sie auch, dass dies local_user
singular ist. Dies hat mich überrascht und verursachte viele Probleme.
devise_for :ldap_users, :local_users, skip: [ :sessions ]
devise_scope :local_user do
get 'sign_in' => 'sessions#new', :as => :new_session
post 'sign_in' => 'sessions#create', :as => :create_session
delete 'sign_out' => 'sessions#destroy', :as => :destroy_session
end
Ansicht
Die Ansicht ist sehr einfach und kann ohne allzu viele Probleme geändert werden.
<%= form_for(resource, :as => 'user', url: create_session_path) do %>
Anmelden
LDAP-Benutzername oder Datenbank-E-Mail
Passwort
<% end %>
Ich hoffe, dass dies jemand anderem hilft. Dies ist die zweite Web-App, an der ich gearbeitet habe, die sowohl LDAP- als auch lokale Authentifizierung benötigte (die erste war eine C# MVC4-Anwendung), und beide Male hatte ich erhebliche Schwierigkeiten, Authentifizierungs-Frameworks dazu zu bringen, dies ordentlich zu handhaben.