środa, 17 lutego 2010

Tomcat 6, proste uwierzytelnianie - część 1

W tym prostym tutorialu przedstawię jak w bardzo prosty sposób zabezpieczyć zasoby naszej aplikacji działającej na bardzo popularnym serwerze aplikacji jakim jest Apache Tomcat 6.

Baza Danych
Na początek musimy przygotować strukturę bazy danych, gdzie będziemy trzymać nazwy użytkowników i przydzielone do nich nazwy ról. Skrypt który to zrobi wygląda tak:
CREATE TABLE users (
 login VARCHAR(20),
 password VARCHAR(100)
);

CREATE TABLE user_roles (
 login VARCHAR(20),
 rolename VARCHAR(20)
);
Pierwsza tabela users przechowuje nazwy użytkowników (login) i hasła (password) do ich kont. Druga tabela user_roles przechowuje nazwę użytkownika (login) i nazwę roli (rolename) do której użytkownika należy. Teraz wystarczy uzupełnić nasze tabele danymi:
INSERT INTO users VALUES('admin','admin');
INSERT INTO user_roles VALUES('admin','admin');
INSERT INTO users VALUES('user1','user');
INSERT INTO user_roles VALUES('user1','user');
Ja będę używał bazy MySQL, dlatego musimy jeszcze ściągnąć connector, który należy rozpakować i skopiować do katalogu tomcat_home/lib.

Aplikacja
Teraz możemy zająć się tworzeniem aplikacji. Ja używam Eclipse. Tworzymy Dynamic Web project, eclipse zadba o to aby stworzyć odpowiedni układ katalogów. U mnie wygląda to tak:



Kiedy mamy już szkielet naszej aplikacji, dodamy kilka stron, aby mieć na czym testować nasze uwierzytelnianie. Dodajemy do WebContent trzy katalogi: user, admin i login. Katalog admin będzie zawierał podstrony do których ma dostęp tylko administrator, natomiast katalog user będą mogli przeglądać tylko użytkownicy zalogowani. Katalog login będzie zawierał podstrony które będą potrzebne do zalogowania i wylogowania użytkownika z naszej aplikacji. Potrzebujemy także strony startowej, dlatego bezpośrednio w katalogu WebContent tworzymy strone index.jsp. Na razie zostawimy ją pustą, późnie wypełnimy ją jakąś treścią.

W katalogu login tworzymy trzy pliki:
  • login.jsp - zawiera formularz logowania
  • login_err.jsp - zawiera komunikat o nieudanej próbie logowania
  • logout.jsp - zawiera krótki kawałek kodu który odpowiada za wylogowanie użytkownika
Strona login/login.jsp zawiera prosty formularz dzięki któremu użytkownik będzie mógł się zalogować. Należy zwrócić tutaj uwagą na akcje jaką wykona formularz po kliknięciu przycisku, oraz na wartości atrybutu name w polach gdzie wprowadzamy login i hasło.
<html>
 <head>
  <title>Login</title>
 </head>
 
 <body>
  <form method="post" action="j_security_check">
   <div>
    Login:
    <input type="text" name="j_username" />
   </div>
   <div>
    Hasło:
    <input type="password" name="j_password"/>
   </div>
   <div>
    <input type="submit" value="LOGIN" />
   </div>
  </form>
 </body>
</html>

Strona login/login_err.jsp zawiera prosty komunikat o błędnym zalogowaniu:
<html>
 <head>
  <title>Login Error</title>
 </head>
 
 <body>
  <div>Logowanie się nie powiodło. Sprobuj jeszcze raz.</div>
 </body>
</html>
Strona login/logout.jsp wylogowywuje użytkownika i przekierowuje nas na stronę index.jsp.
<%
session.invalidate();
response.sendRedirect("../index.jsp"); 
%>

W katalogu user tworzymy plik user/index.jsp, plik będzie zawierał krótki komunika "Witaj użytkowniku" i dwa linki, jeden do strony głównej (index.jsp), drugi do strony login/logout.jsp.


<html>
 <head>
  <title>Uzytkonik</title>
 </head>
 
 <body>
  Witaj uzytkowniku
  <br />
  <a href="../index.jsp">cofnij</a>
  <br />
  <a href="../login/logout.jsp">logout</a>
 </body>
</html>
W katalogu admin tworzymy plik admin/index.jsp, który podobnie jak plik user/index.jsp będzie zawierał komunikat powitalny, tym razem jednak administratora systemu, oraz dwa linki.

<html>
 <head>
  <title>Administrator</title>
 </head>
 
 <body>
  Witaj administratorze
  <br />
  <a href="../index.jsp">cofnij</a>
  <br />
  <a href="../login/logout.jsp">logout</a>
 </body>
</html>
Wracamy teraz do naszej głównej strony index.jsp, dodamy do niej krótki komunikat powitalny oraz linki do naszych stron użytkownika (user/index.jsp) oraz administratora (user/index.jsp). Dodamy też możliwość wylogowania się, dostępną tylko dla zalogowanych użytkowników.
<html>
 <head>
  <title>Uzytkownik</title>
 </head>
 
 <body>
  Witaj na stronie testowej.
  <br />
  <a href="admin/index.jsp">Administrator</a>
  <br />
  <a href="user/index.jsp">Uzytkownik</a>
  <br />
  <%
  if(request.getUserPrincipal() != null){
  %>
  <a href="login/logout.jsp">logout</a>
  <%
  }
  %>
 </body>
</html>
Po stworzeniu wszystkich stron naszej małej aplikacji układ katalogów powinien wygląda tak:


Pliki konfiguracyjne, czyli to co najważniejsze
Teraz kiedy nasza mini aplikacja jest już gotowa, możemy zablokować dostęp do niektórych katalogów, w celu zabezpieczenia danych które są tam przechowywane przed niewłaściwymi osobami.
W katalogu WEB-INF znajduje się plik web.xml, w którym umieścimy informacje o tym, jakie podstrony naszej aplikacje będą niedostępne dla niezalogowanych użytkowników.
Plik web.xml po zmodyfikowaniu powinie wyglądać tak:
<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

 <display-name>HelloWorld</display-name>
 <welcome-file-list>
  <welcome-file>index.jsp</welcome-file>
 </welcome-file-list>

 <security-constraint>
  <web-resource-collection>
   <web-resource-name>AdminPages</web-resource-name>
   <description> accessible by authorised users </description>
   <url-pattern>/admin/*</url-pattern>
   <http-method>DELETE</http-method>
   <http-method>GET</http-method>
   <http-method>POST</http-method>
   <http-method>PUT</http-method>
  </web-resource-collection>
  <auth-constraint>
   <role-name>admin</role-name>
  </auth-constraint>
  <user-data-constraint>
   <transport-guarantee>NONE</transport-guarantee>
  </user-data-constraint>
 </security-constraint>

 <security-constraint>
  <web-resource-collection>
   <web-resource-name>UserPages</web-resource-name>
   <description> accessible by authorised users </description>
   <url-pattern>/user/*</url-pattern>
   <http-method>DELETE</http-method>
   <http-method>GET</http-method>
   <http-method>POST</http-method>
   <http-method>PUT</http-method>
  </web-resource-collection>
  <auth-constraint>
   <role-name>user</role-name>
   <role-name>admin</role-name>
  </auth-constraint>
  <user-data-constraint>
   <transport-guarantee>NONE</transport-guarantee>
  </user-data-constraint>
 </security-constraint>

 <login-config>
  <auth-method>FORM</auth-method>
  <form-login-config>
   <form-login-page>/login/login.jsp</form-login-page>
   <form-error-page>/login/login_err.jsp</form-error-page>
  </form-login-config>
 </login-config>

 <security-role>
  <role-name>admin</role-name>
 </security-role>
 <security-role>
  <role-name>user</role-name>
 </security-role>
</web-app>
To teraz krótki opis, co znajduje się w powyższym pliku.
Jak można zauważyć, w pliku wydzielone są trzy główne elementy: <security-constraint>, <login-config> i <security-role>. Element <security-constraint> zawiera informacje o blokowanych zasobach, oraz o tym jakie grupy użytkowników mają dostęp do informacje zawartych na tych stronach. Element <login-config> zawiera informacje o formularzu który ma się wyświetlić w razie odniesienia się do zablokowanych zasobów, w celu sprawdzenia czy użytkownik ma wystarczające prawa do oglądania treści zawartych na stronie. Element <security-role> przechowuje nazwę grupy, nasza mini aplikacja posiada dwie grupy, user - czyli zwykli użytkonicy, oraz admin - czyli administrator strony.

Nasza aplikacja do weryfikacji użytkownika wykorzystuje bazę danych, dlatego musimy stworzyć plik który będzie informował Tomcat'a o tym gdzie ma szukać informacji o loginach, hasłach i przynależności do grup. W tym celu w katalogu META-INF tworzymy plik context.xml.
<?xml version="1.0" encoding="ISO-8859-1"?>
<Context antiResourceLocking="false" privileged="true">
<Realm className="org.apache.catalina.realm.JDBCRealm" debug="0"
 driverName="org.gjt.mm.mysql.Driver" connectionURL="jdbc:mysql://localhost/testdb"
 connectionName="root" connectionPassword="mysql" userTable="users"
 userNameCol="login" userCredCol="password" userRoleTable="user_roles"
 roleNameCol="rolename" />
</Context>
Plik zawiera główny element <Realm> który posiada kilka ważnych atrybutów:
  • driverName - informacje o sterowniku z jakiego używamy
  • connectionURL - lokalizacja bazy danych, my używamy MySQl ktory działa pod adresem localhost/nazwa_bazy_danych
  • connectionName - nazwa użytkownika w bazie danych
  • connectionPassword - hasło do bazy danych
  • userTable - nazwa tabeli która przechowuje informacje o loginach i hasłach
  • userNameCol - nazwa kolumny która przechowuje login użytkownika
  • userCredCol - nazwa kolumny która przechowuje hasło użytkownika
  • userRoleTable - nazwa tabeli która przechowuje login i nazwą roli użytkownika
  • roleNameCol - nazwa kolumny która przechowuje nazwę roli
Teraz wystarczy stworzyć paczkę war z naszej aplikacji z zdeployować ją na serwerze Apache Tomcat, jeśli używamy eclipse wystarczy dodać serwer w zakładce Servers i uruchomić wraz z nasza aplikacją.

Nasza aplikacja co prawda działa, ale hasła które są przechowywane w bazie danych nie są szyfrowane. Dlatego w części 2 artykułu naprawimy ten błąd.

Brak komentarzy:

Prześlij komentarz