Autenticazione nativa Windows Active Directory in Tomcat

Postato da ROb | nella categoria Java, Sviluppo web | venerdì, 22 luglio 2011

0

Per un interessante progetto a cui sto lavorando, ho avuto la necessità di configurare Tomcat per poter autenticare nativamente i client (con browser IE7 e IE8 e Firefox) attraverso il repository Active Directory di Windows 2003 Server.

Per farlo ci sono a disposizione, come spesso avviene, numerose possibilità.
Dopo averne provate un paio alla fine ho scelto la soluzione proposta dalla libreria spnego.

spnego implementa il protocollo Kerberos/SPNEGO invece del classico NTLM per dialogare con i domain controller e, attraverso un filtro inserito nella nostra web application, ci offre la possibilità di verificare l’identità degli utenti (attraverso la chiamata java request.getRemoteUser() ) già autenticati in Windows dopo un logon positivo nel dominio di Active Directory.

La guida offerta dal sito della libreria per il raggiungimento di tale integrazione è molto ben fatta. Qui sotto in breve voglio riassumere i miei passi, che in larga parte seguono appunto tale guida.

  • creazione di un utente in Active Directory, che sarà utilizzato dal filtro per loggarsi al dominio e verificare le credenziali degli utenti.
  • creazione del file krb5.conf (all’interno della directory di esecuzione di Tomcat, nel mio caso la bin) con tale contenuto:
    [libdefaults]
    	default_realm = <NOME_DOMINIO>
    	default_tkt_enctypes = aes128-cts rc4-hmac des3-cbc-sha1 des-cbc-md5 des-cbc-crc
    	default_tgs_enctypes = aes128-cts rc4-hmac des3-cbc-sha1 des-cbc-md5 des-cbc-crc
    	permitted_enctypes   = aes128-cts rc4-hmac des3-cbc-sha1 des-cbc-md5 des-cbc-crc
    
    [realms]
    	<NOME_DOMINIO>  = {
    		kdc = <DOMAIN_CONTROLLER>
    		default_domain =  <NOME_DOMINIO>
    }
    
    [domain_realm]
    	. <NOME_DOMINIO> =  <NOME_DOMINIO>
    
  • creazione del file login.conf (nella stessa posizione del file krb5.conf) con il seguente contenuto
    spnego-client {
    	com.sun.security.auth.module.Krb5LoginModule required;
    };
    
    spnego-server {
    	com.sun.security.auth.module.Krb5LoginModule required
    	storeKey=true
    	isInitiator=false;
    };
    
  • creazione di una web application (nel mio caso il suo nome è testad) con dentro solamente la directory WEB-INF con dentro una cartella lib con la libreria jar di spnego (nel mio caso l’ultima versione disponibile era la 7, spnego-r7.jar) e il file web.xml con il seguente contenuto (rimpiazzate opportunamente l’utente e la password dell’utente di Active Directory):
    <?xml version="1.0" encoding="ISO-8859-1"?>
    
    <web-app xmlns="http://java.sun.com/xml/ns/javaee"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
       version="2.5">
    
      <filter>
        <filter-name>SpnegoHttpFilter</filter-name>
        <filter-class>net.sourceforge.spnego.SpnegoHttpFilter</filter-class>
    
        <init-param>
            <param-name>spnego.allow.basic</param-name>
            <param-value>true</param-value>
        </init-param>
    
        <init-param>
            <param-name>spnego.allow.localhost</param-name>
            <param-value>false</param-value>
        </init-param>
    
        <init-param>
            <param-name>spnego.allow.unsecure.basic</param-name>
            <param-value>true</param-value>
        </init-param>
    
        <init-param>
            <param-name>spnego.login.client.module</param-name>
            <param-value>spnego-client</param-value>
        </init-param>
    
        <init-param>
            <param-name>spnego.krb5.conf</param-name>
            <param-value>krb5.conf</param-value>
        </init-param>
    
        <init-param>
            <param-name>spnego.login.conf</param-name>
            <param-value>login.conf</param-value>
        </init-param>
    
        <init-param>
            <param-name>spnego.preauth.username</param-name>
            <param-value>utentead</param-value>
        </init-param>
    
        <init-param>
            <param-name>spnego.preauth.password</param-name>
            <param-value>password_utentead</param-value>
        </init-param>
    
        <init-param>
            <param-name>spnego.login.server.module</param-name>
            <param-value>spnego-server</param-value>
        </init-param>
    
        <init-param>
            <param-name>spnego.prompt.ntlm</param-name>
            <param-value>true</param-value>
        </init-param>
    
        <init-param>
            <param-name>spnego.logger.level</param-name>
            <param-value>1</param-value>
        </init-param>
    </filter>
    
    <filter-mapping>
        <filter-name>SpnegoHttpFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    
    </web-app>
    

    aggiungete anche il file index.jsp nella root della web application con il seguente contenuto:

    <%@page import="java.security.Principal" %>
    <html>
     <head>
      <title>Protected Page for Examples</title>
     </head>
     <body bgcolor="white">
      You are logged in as remote user <b><%= request.getRemoteUser() %></b> in session <b><%= session.getId() %></b>
      <br><br>
      <%
    	if (request.getUserPrincipal() != null) {
      %>
      Your user principal name is <b><%= request.getUserPrincipal().getName() %></b>.
      <br><br>
      <%
       } else {
      %>
       No user principal could be identified.
       <br><br>
      <%
      }
      %>
      <%
      String role = request.getParameter("role");
      if (role == null)
        role = "";
      if (role.length() > 0) {
        if (request.isUserInRole(role)) {
      %>
      You have been granted role <b><%= role %></b>.
      <br><br>
      <%
       } else {
      %>
      You have <i>not</i> been granted role <b><%= role %></b>.
      <br><br>
      <%
       }
      }
      %>
      To check whether your username has been granted a particular role, enter it here:
      <form method="GET" action='<%= response.encodeURL("index.jsp") %>'>
       <input type="text" name="role" value="<%= role %>">
      </form>
      <br><br>
      You can logoff by clicking
      <a href='<%= response.encodeURL("index.jsp?logoff=true") %>'>here</a>.
      This should cause automatic re-logon with Waffle and a new session ID.
      <br>
     </body>
    </html>
    
  • a questo punto inserite questa web application nelle applicazioni del vostro Tomcat
  • per completare il processo bisogna lanciare nel domain controller un paio di istruzioni per abilitare il tab delegation all’utente Active Directory che effettuerà la verifica dei token del Kerberos.
    setspn.exe -A HTTP/<sito> utentead
    setspn.exe -A HTTP/<sito_con_dominio> utentead
    

    poi aprendo le proprietà dell’utente, nel tab “Delegation”, abilitare la funzione “Trust this user for delegation to any service (Kerberos only)”.

  • per permettere al browser di passare le credenziali al server bisognerà, nel caso di IE, inserire il sito all’interno dell’Area Intranet e verificare che sotto la voce Autenticazione Utente (dentro livello personalizzato) sia impostato come “Accesso automatico solo nell’area Internet”, mentre per Firefox (testato con il 3.x) bisogna impostare le seguenti chiavi con il valore del vostro sito web
    network.negotiate-auth.delegation-uris = http://<sito>
    network.negotiate-auth.trusted-uris = http://<sito>
    
  • riavviate ora Tomcat e accedete alla root della vostra nuova web application
  • il filtro di spnego proteggerà tutti i file della web application e chiederà l’autenticazione nativa (o eventualmente l’autenticazione basic tramite prompt). Se l’autenticazione avverrà correttamente accederete al file index.jsp e leggerete il vostro nome utente nella pagina.

Grazie per l’attenzione e buon lavoro!

Generazione di un hash md5 in Java

Postato da ROb | nella categoria Java, Sviluppo web | mercoledì, 10 novembre 2010

0

Con questa semplice funzione è possibile generare una stringa in codifica md5 a partire da una stringa di testo.

       /**
	 * Generates the md5sum of the given digest
	 *
	 * @param digest
	 * @return
	 */
	public static String md5sum(String digest) {
		String message = null;

		if (digest != null) {
			try {
				MessageDigest md = MessageDigest.getInstance("MD5");
				md.reset();
				byte[] messageBytes = md.digest(digest.getBytes());
				BigInteger number = new BigInteger(1, messageBytes);
				message = number.toString(16);
			} catch (NoSuchAlgorithmException e) {
				e.printStackTrace();
			}
		}

		return message;
	}

Come caricare un file sul server in AJAX

Postato da ROb | nella categoria Sviluppo web | martedì, 2 marzo 2010

2

I form html tradizionali negli anni hanno dimostrato numerosi limiti. Quelli più notevoli erano impossibilità di effettuare validazione server side, impossibilità di gestire dei campi autopopolati durante la digitazione, post completo del form senza possibilità di parzializzazione.

Ajax file upload

Tutte queste cose hanno reso il web uno strumento difficilmente utilizzabile per applicazioni che richiedessero un’elevata interazione con l’utente.
Negli ultimi anni AJAX ha rivoluzionato i tradizionali form html permettendo ai programmatori di poter dialogare agilmente con il loro backend e permettendo agli utenti di godere una più dinamica esperienza web.

Recentemente ho avuto la necessità di gestire un campo file in un form ajax. Per form ajax intendo un form che invia i suoi dati serializzati al server e non fa il post dell’intera pagina.
La tecnologia utilizzata era jQuery così ho cercato un plugin che si integrasse bene con tale tecnologia e ho trovato AJAX Upload.

Inseriamo innanzitutto gli script javascript nell’header necessari alla definizione di jQuery e del plugin AJAX Upload:

...
<head>
...
<script type="text/javascript" src="js/jquery-1.3.2.min.js"></script>
<script type="text/javascript" src="js/ajaxupload.js"></script>
...
</head>
...

A questo punto bisogna creare il div che cliccato avvierà la procedura di caricamento file:

<div id="upload_button">Upload</div>

Infine dobbiamo inizializzare i campi di tipo file all’interno del nostro form:

$(document).ready(function() {
    new AjaxUpload('upload_button_id', {action: 'uploadServletPath'});
}

Al rendering della pagina cliccando sul div upload_button si aprirà la finestra di dialogo per la scelta del file e il file sarà direttamente inviato via POST (senza però il POST dell’intera pagina) alla servlet di upload che abbiamo specificato nell’inizializzazione del componente.

Nel costruttore di inizializzazione possiamo anche passare altri parametri di configurazione e gestire alcuni eventi precedenti (o successivi) al caricamento utili per valorizzare altre variabili del form o per visualizzare la progressione del caricamento o il nome del file caricato sul server.
E’ anche possibile stabilire il tipo di risposta ricevuta dal server: html, xml o json.

Per chi fosse interessato ecco il codice Java necessario al salvataggio del file:

/*
 *	author: Roberto Rossi
 */
package it.bits4beats.servlets;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.*;
import java.util.Iterator;
import java.util.List;

public class UploadServlet extends HttpServlet {
	private static Log log = LogFactory.getLog(UploadServlet.class);
	public static final String DEFAULT_UPLOAD_DIR_INIT_PARAM = "DefaultUploadDir";

	/**
	 *
	 */
	private static final long serialVersionUID = 1L;

	/**
	 * This is a default constructor for UploadServlet class.
	 *
	 */
	public UploadServlet(){
	}

	public void init() throws ServletException {
		super.init();
	}

	/**
	 * Receive a file to be uploaded and save it into filesystem
	 */
	public void doGet(HttpServletRequest request,
			HttpServletResponse response)
	throws ServletException,java.io.IOException {
		log.info("method not supported!");
	}

	public void doPost(HttpServletRequest request,
			HttpServletResponse response)
	throws ServletException,java.io.IOException {

		String defaultUploadDir = getServletConfig().getInitParameter(DEFAULT_UPLOAD_DIR_INIT_PARAM);
		if (defaultUploadDir != null && !defaultUploadDir.equals("")) {
			log.debug("> default upload dir: " + defaultUploadDir);
		}

		/* the uploading user */
		String username = request.getParameter("username");
		/* max size of file upload */
		String sizeParam = request.getParameter("sz");
		Long maxBytes = null;
		if (sizeParam != null && !sizeParam.isEmpty()) {
			try {
				/* max 5Mb file uploaded */
				maxBytes = new Long(5242880);
			}
			catch (NumberFormatException nfe) {
				/* do nothing */
			}
		}

		log.debug("remote user: " + request.getRemoteUser());
		log.debug("user principal: " + (request.getUserPrincipal() == null ? "" : request.getUserPrincipal().getName()) );
		log.debug("username: " + username);

		String status = "true";
		String message = "";
		String savedfilename = "";
		String attachmentname = "";
		String originalFilename = "";

		/* reading file streams */
		boolean isMultipart = ServletFileUpload.isMultipartContent(request);

		if (isMultipart) {

			FileItemFactory factory = new DiskFileItemFactory();
			ServletFileUpload upload = new ServletFileUpload(factory);
			List items = null;
			try {
				items = upload.parseRequest(request);

				if (items != null && items.size() > 0) {

					File uploadDirFile = null;
					String uploadDirFileRelativePath = null;

					uploadDirFile = new File(getServletContext().getRealPath(defaultUploadDir));
					uploadDirFileRelativePath = defaultUploadDir;

					Iterator itr = items.iterator();

					FileItem fileItem = null;
					FileItem filenameItem = null;

					while(itr.hasNext()) {
						FileItem item = (FileItem) itr.next();

						if (!item.isFormField()) {
							fileItem = item;
						}
						else {
							if (item.getFieldName().equals("attachmentFilename")) {
								filenameItem = item;
								log.debug("filenameItem name: " + filenameItem.getFieldName() + " valore: " + filenameItem.getString());
							}
						}
					}

					if (fileItem != null) {
						/* there is a file item in the request */
						String fileName = fileItem.getName();
						log.debug("fileItem name: " + fileName);

						if (fileItem.getName().contains("\\")) {
							log.debug("Windows file");

							/* windows file path */
							fileName = fileItem.getName().substring(fileItem.getName().lastIndexOf("\\") +1);
							log.debug("fileItem name: " + fileName);
						}

						File fullFile  = new File(fileName);
						File savedFile = new File(uploadDirFile, fileName);

						if (savedFile != null) {

							/* check file size */
							if (maxBytes != null) {
								if (fileItem.getSize() > maxBytes.longValue()) {
									FileTooBigException ftbe = new FileTooBigException();
									ftbe.setMaxSize(new Long(maxBytes.longValue()/1024/1024).toString());
									throw ftbe;
								}
							}

							if (savedFile.exists()) {
								/* we need to create a file variant, save it and send it to the user */
								String fileNameWithoutExtension = savedFile.getName().substring(0, savedFile.getName().lastIndexOf("."));
								originalFilename = fileNameWithoutExtension;
								String suffix = savedFile.getName().substring(savedFile.getName().lastIndexOf(".") + 1);

								File newFile = File.createTempFile(fileNameWithoutExtension + "_", "." + suffix, uploadDirFile);
								fileItem.write(newFile);

								log.debug("newFile: " + newFile.getAbsolutePath());
								status = "true";
								message = "OK " + newFile.length() + " byte salvati";

								/* relative file path */
								savedfilename = uploadDirFileRelativePath + newFile.getName();
								String filenameWithExt =  extractFilenameWithExtensionFromPath(savedfilename);
								attachmentname = extractFilename(filenameWithExt);
							}
							else {
								log.debug("writing file to: " + savedFile.getAbsolutePath());
								originalFilename = extractFilename(extractFilenameWithExtensionFromPath(savedFile.getAbsolutePath()));
								fileItem.write(savedFile);

								status = "true";
								message = "OK " + savedFile.length() + " byte salvati";

								/* relative file path */
								savedfilename = uploadDirFileRelativePath + fullFile.getName();
								String filenameWithExt =  extractFilenameWithExtensionFromPath(savedfilename);
								attachmentname = extractFilename(filenameWithExt);
							}
						}
						else {
							log.error("cannot create file");
							status = "false";
							message = "Errore generico di salvataggio";
						}
					}

				}
				else {
					log.debug("no item to be saved");
				}
			}
			catch (FileTooBigException ftbe) {
				status = "false";
				message = "File troppo grande, dimensione massima (Mb): " + ftbe.getMaxSize();
			}
			catch (Exception e1) {
				e1.printStackTrace();
				log.error("error in uploading");

				status = "false";
				message = "Errore generico di salvataggio";
			}
		}

		PrintWriter out = response.getWriter();

		String responseMessage = "{\"success\":" + status + ",\"message\":\"" + message + "\",\"savedfilename\":\"" + savedfilename + "\",\"attachmentname\":\"" + attachmentname + "\",\"originalfilename\":\"" + originalFilename + "\"}";
		log.debug("responseMessage: " + responseMessage);
	    out.println(responseMessage);
	}

	/**
	 * This method extract filename (with extension) from filepath
	 *
	 * @param filepath
	 * @return filename without extension
	 */
	public static String extractFilenameWithExtensionFromPath(String filepath) {
		if (filepath != null) {
			int index = filepath.lastIndexOf("/");

			if (index > 0 && index < (filepath.length() -1)) {
				return filepath.substring(index + 1);
			}
			else return null;
		}
		else {
			return null;
		}
	}

	/**
	 * This method extract filename (without extension) from filename with extension
	 *
	 * @param filepath
	 * @return filename without extension
	 */
	public static String extractFilename(String filenameWithExtension) {
		if (filenameWithExtension != null) {
			int index = filenameWithExtension.lastIndexOf(".");

			if (index > 0) {
				return filenameWithExtension.substring(0, index);
			}
			else return filenameWithExtension;
		}
		else {
			return null;
		}
	}

	class FileTooBigException extends Exception {
		private static final long serialVersionUID = 1L;

		private String maxSize = null;

		public String getMaxSize() {
			return maxSize;
		}

		public void setMaxSize(String maxSize) {
			this.maxSize = maxSize;
		}

		public FileTooBigException() {
			super();
		}

		public FileTooBigException(String message) {
			super(message);
		}
	}

}

Nel sito del plugin trovate anche una demo completa e funzionante di AJAX upload.

Aggiungere o sottrarre giorni, mesi, anni a una data in Java

Postato da ROb | nella categoria Java | lunedì, 22 febbraio 2010

0

Può sembrare banale ma la prima volta che ho cercato di farlo ci ho messo un pochino per trovare la soluzione giusta. Ho deciso così di pubblicare queste poche righe di codice.

date

Per sottrarre o aggiungere un giorno a una data, bisogna creare un’istanza della classe Calendar, impostando una data e sommando a questa 1 di tipo giorno.

/* adding 1 day to the actual date */
Date eventDate = new Date();
Calendar calendar = Calendar.getInstance();
calendar.setTime(eventDate);
calendar.add(Calendar.DATE, 1);
Date pastDate = calendar.getTime();

Se vogliamo invece sottrarre un giorno dobbiamo usare “-1″ anziché “1″ alla riga 5.
Nel caso in cui dobbiamo aggiungere o sottrarre mesi o anni possiamo usare, sempre alla riga 5, Calendar.MONTH o Calendar.YEAR .

Comprimere il codice Javascript con YUI Compressor

Postato da ROb | nella categoria Sviluppo web | mercoledì, 17 febbraio 2010

0

Molte librerie javascript vengono distribuite in un formato compresso (o minimizzato).

Se aprite i file in questo formato non riuscirete a leggere granché per via del fatto che tutti gli spazi e gli a capo sono stati sacrificati per risparmiare byte preziosi.

Compressione librerie Javascript

Mi sono sempre chiesto come fossero riusciti a realizzare tali file.
Beh la risposta è molto semplice: ci sono programmi appositi per farlo.

Uno di questi è YUI Compressor, un tool sviluppato dal YUI Team.
YUI Compressor è stato scritto in java e il suo utilizzo è veramente immediato.

Dopo aver scaricato la distribuzione (la versione attuale è la 2.4.2) e ipotizzando di averla scompattata nella directory /opt/ , potete avviare la minimizzazione del vostro codice attraverso questo comando:

java -jar /opt/yuicompressor-2.4.2/yuicompressor-2.4.2.jar --type js vostrofilejavascript.js

Il codice minimizzato verrà stampato a video. A questo punto potete salvarlo in un file.

Se volete invece salvarlo in un file minimizzato potete redirezionare direttamente lo standard output in questo modo:

java -jar /opt/yuicompressor-2.4.2/yuicompressor-2.4.2.jar --type js vostrofilejavascript.js > vostrofilejavascript.min.js