5.9 Authentifizierung
Wir haben jetzt eine kleine fertige Applikation, mit der wir Bookmarks verwalten können. Würden wir die Seite veröffentlichen, könnte jeder nicht nur die Liste der Bookmarks sehen, sondern auch Einträge hinzufügen, ändern oder sogar löschen. Da wir das nicht möchten, müssen wir einen Authentifizierungsmechanismus hinzufügen.
Wir müssen also einen Log-in-Bereich schaffen, und erst nach erfolgreichem Log-in darf man Einträge ändern, löschen oder neue Einträge hinzufügen. Die Liste aller angelegten Bookmarks und die Detailseite zu einem Bookmark soll aber für alle sichtbar sein.
Eigenes Authentifizierungs- system
Für die Lösung dieses Problems könnten wir eine HTTP-Authentifizierung wie in Kapitel 3 gezeigt einsetzen oder ein fertiges Plug-in (siehe Kapitel 15), mit dem man sogar mehrere User verwalten kann. Wir wollen hier eine sehr einfache Lösung vorstellen, die ohne Plug-ins auskommt, damit Sie die Möglichkeit haben zu verstehen, was passiert.
Zunächst überlegen wir, welche Auswirkungen ein Authentifizierungssystem auf unsere Index-Seite hat. Wenn ein User die Seite aufruft, der nicht angemeldet ist, soll nur der Link zur Detailseite sichtbar sein. Die Links zum Ändern, Löschen und zur Neuanlage eines Favoriten werden nur angezeigt, wenn ein angemeldeter User die Seite aufruft.
Das bedeutet, der View index.html.erb muss vor der Anzeige der Links abfragen, ob der aktuelle User angemeldet ist oder nicht.
Angenommen, wir hätten eine Methode admin?, die true zurückliefert, wenn ein User angemeldet ist, und false, wenn nicht. Dann könnten wir in der index.html.erb den Link zum Neuanlegen eines Favoriten nur dann anzeigen, wenn die Methode admin? den Wert true zurückliefert:
Listing app/views/bookmarks/index.html.erb
... <% if admin? %> <p> <%= link_to "Neuen Favorit erstellen", :action => "new" %> </p> <% end %>
Das Gleiche gilt für die Links zum Ändern und Löschen, die wir in das Partial _bookmark.html.erb ausgelagert haben:
Listing app/views/bookmarks/_bookmark.html.erb
<li> <%= link_to h(bookmark.title), h(bookmark.url) %> (<%= link_to "Details", :action => "show", :id => bookmark.id %> <%if admin? %> |<%= link_to "ändern", :action => "edit", :id => bookmark.id %> | <%= link_to "löschen", {:action => "destroy", :id => bookmark.id}, :confirm => "Wollen Sie diesen Datensatz wirklich löschen?" %> <%end %> ) </li>
Methode admin? anlegen
Bevor wir das testen können, müssen wir die Methode admin? anlegen. Da wir die Methode in den Views des Bookmarks-Controller nutzen, können wir sie als Helper in der Datei app/helpers/bookmarks_helper.rb anlegen. Die Abfrage, ob ein User angemeldet ist oder nicht, ist aber etwas Allgemeingültiges, das wir auch in Views anderer Controller nutzen könnten. Deshalb definieren wir die Methode als Application-Helper in der Datei app/helpers/application_helper.rb:
Listing app/helpers/application_helper.rb
module ApplicationHelper def german_time(time) time.strftime("%d.%m.%Y %H:%M") end def admin? false end end
Um die Funktionalität der Abfragen, die wir in die Views gesetzt haben, testen zu können, setzen wir den Rückgabewert der Methode erst einmal auf false und rufen die Index-Seite im Browser auf:
Abbildung admin? == false
Wenn wir den Rückgabewert der Methode admin? auf true setzen, werden die vorher ausgeblendeten Links angezeigt:
Abbildung admin? == true
Session
Das heißt, das System funktioniert. Jetzt müssen wir noch dafür sorgen, dass der Rückgabewert der Methode admin? in Abhängigkeit von einem Log-in gesetzt wird. Dazu werden wir über einen neuen Controller eine Session-Variable setzen und in der Methode admin? abfragen, ob sie gesetzt ist. Wenn sie gesetzt ist, liefert die Methode admin? true zurück:
def admin? session[:admin] == true end
Der neue Controller, der für die Authentifizierung und damit für das Setzen der Session-Variablen zuständig ist, heißt authentication und hat den View login.html.erb:
ruby script/generate controller authentication login exists app/controllers/ exists app/helpers/ create app/views/authentication exists test/functional/ create app/controllers/authentication_controller.rb create test/functional/authentication_controller_test.rb create app/helpers/authentication_helper.rb create app/views/authentication/login.html.erb
form_tag
Neben Test- und Helper-Dateien wurden die Controller-Datei authentication_controller.rb mit der Action login und dem dazugehörigen View login.html.erb erstellt. Im View legen wir das Log-in-Formular an. Da dieses Formular nicht auf einem Model basiert, können wir nicht den Helper form_for benutzen, sondern wir verwenden den Helper form_tag zur Erstellung eines modelunabhängigen Formulars. Zur Erstellung der einzelnen Formularfelder stehen die Helper text_field_tag und password_field_tag zur Verfügung. Das Formular schicken wir an den Authentication-Controller und die Action check, die wir noch im Controller anlegen müssen:
Listing app/views/authentication/login.html.erb
<h2>Login</h2> <% form_tag :controller => "authentication", :action => "check" do %> <p> <label for="user" >Benutzername</label> <%= text_field_tag :user %> </p> <p> <label for="password" >Passwort</label> <%= password_field_tag :password %> </p> <p> <%= submit_tag "login"%> </p> <% end %>
Das Formular können Sie über den Aufruf der URL
http://localhost:3000/authentication/login
testen:
Abbildung http://localhost:3000/authentication/login
Formular- verarbeitung
Abschicken können Sie das Formular noch nicht, da im Authentication-Controller die Action check, an die das Formular geschickt wird, noch fehlt. Die Action check gestalten wir recht einfach, indem wir die beiden Parameter user und password auf feste Werte abfragen. Sind die Werte korrekt, wird die Session-Variable gesetzt, eine Flash-Message ausgegeben, dass die Anmeldung erfolgreich war, und es wird auf die Index-Seite der Bookmarkverwaltung weitergeleitet. Ist die Anmeldung nicht erfolgreich, wird sicherheitshalber die Session gelöscht, eine Flash-Message ausgegeben, dass die Anmeldung nicht erfolgreich war, und es wird wieder das Log-in-Formular aufgerufen:
Listing app/controllers/authentication_controller.rb
class AuthenticationController < ApplicationController def login end def check if params[:user] == "ich" && params[:password] == "geheim" session[:admin] = true flash[:notice] = "Erfolgreich angemeldet" redirect_to :controller => "bookmarks", :action => "index" else reset_session flash[:notice] = "Fehler bei der Anmeldung" redirect_to :controller => "authentication", :action => "login" end end end
Wenn Sie das Formular mit dem Benutzernamen »ich« und dem Passwort »geheim« abschicken, werden Sie erfolgreich angemeldet, es wird eine Flash-Message ausgegeben, dass Sie erfolgreich angemeldet sind, und es werden Ihnen alle Links zum Arbeiten in der Bookmarkverwaltung angezeigt.
Abbildung Erfolgreiche Anmeldung
logout
Damit wir uns nach erfolgreicher Anmeldung auch wieder abmelden können, brauchen wir noch eine Action logout im Controller authentication . Beim Log-out wird die Session gelöscht, es wird eine Flash-Message ausgegeben, dass der User abgemeldet wurde, und es wird zur Index-Seite der Bookmarkverwaltung weitergeleitet:
def logout reset_session flash[:notice] = "Sie wurden abgemeldet" redirect_to :controller => "bookmarks", :action => "index" end
Damit wir den Log-out nicht über die URL aufrufen müssen, benötigen wir noch einen Link zum Log-out bzw. auch für den Log-in. Da dieser Link für alle Views angezeigt werden soll, definieren wir ihn in der application.html.erb im Verzeichnis app/views/layouts:
Listing app/views/layouts/application.html.erb
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<meta http-equiv="Content-type" content="text/html;
charset=utf-8" >
<title>Favoritenverwaltung - <%= @title %></title>
<%= stylesheet_link_tag 'global' %>
</head>
<body>
<div id="container" >
<div id="header" >
<h1>Meine Linksammlung</h1>
</div>
<div id="content" >
<% if flash[:notice] %>
<div class="flash notice" >
<%= flash[:notice] %>
</div>
<% end %>
<%= yield %>
</div>
<div id="footer" >
© 2007 |
<% if admin? %>
<%= link_to "logout", :controller => "authentication",
:action => "logout" %>
<% else%>
<%= link_to "login", :controller => "authentication",
:action => "login" %>
<% end %>
</div>
</div>
</body>
</html>
Abbildung Log-out-Link
Error-Message
Wenn wir uns mit einem falschen Benutzernamen anmelden, fällt auf, dass die Fehlermeldung genauso formatiert ist wie die Bestätigungsmeldungen. Das liegt daran, dass wir sowohl für die Bestätigungsmeldungen als auch für die Fehlermeldungen flash[:notice] benutzen. Für die Fehlermeldungen sollten wir aber besser flash[:error] verwenden. Wenn wir das tun, werden die Fehlermeldungen rot formatiert, weil wir das bereits entsprechend in unserer CSS-Datei hinterlegt haben. Voraussetzung ist allerdings, dass wir in der application.html.erb auch noch die Error-Messages ausgeben:
Listing app/views/layouts/application.html.erb
... <div id="container" > <div id="header" > <h1>Meine Linksammlung</h1> </div> <div id="content" > <% if flash[:notice] %> <div class="flash notice" > <%= flash[:notice] %> </div> <% end %> <% if flash[:error] %> <div class="flash error" > <%= flash[:error] %> </div> <% end %> <%= yield %> ...
Sicherheitslücke
Man könnte annehmen, wir wären fertig. Die Links zum Ändern und Hinzufügen eines Bookmarks werden nur nach einer erfolgreichen Anmeldung angezeigt. Da wir nur die Anzeige der Links unterdrückt haben, ist das eine erhebliche Sicherheitslücke in unserem System. Ein nicht angemeldeter User kann durch händisches Eingeben der URL
http://localhost:3000/bookmarks/edit/1
einen Datensatz ändern!
Um das zu verhindern, müssen wir im Bookmarks-Controller festlegen, dass ein Zugriff nur dann erlaubt ist, wenn der User, der versucht, auf die Actions new, edit und destroy zuzugreifen, angemeldet ist. Dazu stellt uns Rails die Methode verify zur Verfügung, der wir angeben müssen, welchen Wert sie überprüfen soll. In unserem Fall ist das die Session-Variable admin . Wenn sie nicht gesetzt ist, soll eine Fehlermeldung ausgegeben und der User zum Log-in-Formular weitergeleitet werden:
Listing app/controllers/bookmarks_controller.rb
class BookmarksController < ApplicationController verify :session => :admin, :add_flash => {:error => "Sie sind nicht angemeldet!"}, :redirect_to => {:controller => "authentication", :action => "login"} ...
Wenn wir jetzt noch einmal versuchen, einen Datensatz zu ändern, indem wir die URL
http://localhost:3000/bookmarks/edit/1
aufrufen, werden wir zum Log-in-Formular weitergeleitet, und es wird die Fehlermeldung »Sie sind nicht angemeldet!« angezeigt.
Fertig? - Nein.
Denn wenn wir uns jetzt anmelden und anschließend wieder abmelden, haben wir ein Problem:
Abbildung Abmelden mit implementierter verify-Methode
Ausnahme definieren
Da beim Abmelden unsere Session gelöscht wird und wir zur Index-Seite weitergeleitet werden, wir aber im Bookmarks-Controller definiert haben, dass alle Actions dieses Controllers nur für authentifizierte User zugänglich sind, trifft das auch auf die Index-Seite zu. Das soll aber prinzipiell nicht so sein, weil wir eigentlich wollten, dass die Index-Seite für alle User zugänglich ist. Gleiches gilt für den Aufruf der Detailseite, also der Action show . Das heißt, wir müssen eine Ausnahme definieren. Die verify -Methode soll für alle Actions aufgerufen werden, außer für die index und für die show:
Listing app/controllers/bookmarks_controller.rb
class BookmarksController < ApplicationController verify :except => ["index", "show"], :session => :admin, :add_flash => {:error => "Sie sind nicht angemeldet!"}, :redirect_to => {:controller => "authentication", :action => "login"} ...
Jetzt ist die Authentifizierung unserer Bookmarkverwaltung fertig! Sie haben nun anhand eines relativ einfachen Authentifizierungssystems die Prinzipien kennengelernt, die auch aufwendigen Authentifizierungssystemen mit datenbankgestützter User-Verwaltung zugrunde liegen.
Ihre Meinung
Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.