Hello,

I would like to expose a home-made solution in order to avoid the unauthorized file downloading via the generation and use of unique token (PART I). The second part (PART II) contains an example of download file servlet allowing several methods of writing file’s content to client.
 
PART I
Here, a singleton named AntiDownloadSingleton used on server side to:

  • generate an unique token,
  • store the available tokens,
  • use/check if a token is available with automatically token’s removing

 

package com.ho.file.antidownload;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Singleton used in SERVER side in order to avoid the unauthorized of file downloading via the generation and use of unique token.
 */
public class AntiDownloadSingleton {

	// ---------------------------------------------------- PRIVATE ATTRIBUTES
	private static AntiDownloadSingleton instance = null;
	private Map<String, Token> tokens = null;

	// ---------------------------------------------------------- CONSTRUCTORS
	private AntiDownloadSingleton(){
		tokens=new ConcurrentHashMap<String, Token>();
	}

	public static synchronized AntiDownloadSingleton getInstance(){
		if (instance==null){
			instance = new AntiDownloadSingleton();
		}
		return instance;
	}

	// ------------------------------------------------------ PUBLIC FUNCTIONS
	public String generateToken(String userName) {
		Token token = new Token(java.util.UUID.randomUUID().toString(), userName);
		tokens.put(token.getId(), token);
		return token.getId();
	}

	public boolean useToken(String id, String userName){
		if (id!=null && userName!=null && tokens.containsKey(id) && userName.equals(tokens.containsKey(id).getUserName())){
			System.out.println("Use of token : "+id);
			tokens.remove(id);
			return true;
		}else{
			System.out.println("An abnormal attempt to load with the token : "+id);
		}
		return false;
	}

	public Token getToken(String id){
		Token token = null;
		if (id!=null && tokens.containsKey(id)){
			token = tokens.get(id);
		}
		return token;
	}

	// --------------------------------------------------------- INNER CLASSES
	private class Token{
		private String id;
		private String userName;
		public Token(String id, String userName) {
			this.id = id;
			this.userName = userName;
		}
		public void setId(String id) { this.id = id; }
		public String getId() { return id; }
		public void setUserName(String userName) { this.userName = userName; }
		public String getUserName() { return userName ; }
	}
}

 

 

So, the kinematics of events is:

  1. Generation of unique token on server side:
    //Generate unique token for authorized file download
    String tokenId = AntiDownloadSingleton.getInstance().generateToken(username);
    
  2. Returning of token value from server to client:
    public ModelAndView handleLoad(HttpServletRequest request, HttpServletResponse response) throws Exception {
    	//...
    	// Store the data in an request attribute
    	docDetails.setDownloadToken(tokenId)
    	request.setAttribute("REQ_CLIENT_DATA", docDetails);
    	return new ModelAndView("/docdetails");
    }
  3. Sending of token value from client to server in parameter of an file download request from client to server via a POST hidden field or URL GET parameter:
    <input type="hidden" name="token" value="ddsfsdf4zerez4rzer4zezrze4"/>
    
    http://mywebserver:8080/downloadFile?token=ddsfsdf4zerez4rzer4zezrze4&docId=123456
  4. Check/use of received token on server side:
    String token = req.getParameter("token");
    
    HttpSession session = req.getSession();
    String userName = (String) session.getAttribute("userName");
    
    //...
    if (!AntiDownloadSingleton.getInstance().useToken(token, userName)){
    	logger.debug("Echec sur "+documentId+", "+token);
    	sendErrorWeb(req, resp, HttpServletResponse.SC_BAD_REQUEST, "The file downloading is unauthorized.");
    	return;
    }
    //...
    
    private void sendError(HttpServletResponse resp, int codeHttp, String text) throws IOException {
    	resp.sendError(codeHttp, text);
    }
    
    private void sendErrorWeb(HttpServletRequest req, HttpServletResponse resp, int scBadRequest, String msg)  {
    	try {
    		if (isIE(req)){
    			sendErrorPdf(resp, msg); // Send a PDF containing a ERROR message
    		}else{
    			resp.sendRedirect("/myapplication/downloadfileerror.do?action=handleError&msg="+msg);
    		}
    	} catch (Exception e) {
    		try {
    			sendError(resp, scBadRequest, msg);
    		} catch (IOException ioe) {
    			logger.error("ERROR : ",e);
    		}
    	}
    }
    	
    private boolean isIE(HttpServletRequest req) {
    	boolean navigatorIE = false;
    	String userAgent = req.getHeader("User-Agent");
    	if (userAgent!=null){
    		navigatorIE = userAgent.toLowerCase().indexOf("msie")>=0;
    	}
    	return navigatorIE;
    }
    

 


 
PART II
Some source code of a download file servlet:

// HttpServletRequest req
// HttpServletResponse resp
// PDDocument pdfDocument

String documentId = req.getParameter("documentId");

ServletOutputStream op = resp.getOutputStream();

resp.setContentType("application/pdf");

// STEP  : Write file stream in a PipedOutputStream/PipedInputStream
PipedInputStream inStream = pipePdfDocumentToOutStream(pdfDocument);
			
// STEP 2 : write InputStream to HTTP response's OuputStream
writeInputStreamToOutputStream(op, inStream);

 
 
…This method is used to write PDF stream in the PipedOutputStream via distinct Thread. The PipedOutputStream is connected to a PipedInputStream in order to create communication pipe.

/*
* Stream manipulation
*/
private PipedInputStream pipePdfDocumentToOutStream (final PDDocument pdfDocument) throws Exception {
	// Write PDF stream in the PipedOutputStream via distinct Thread 
	// The PipedOutputStream is connected to a PipedInputStream in order to create communication pipe 
	final PipedInputStream inStream = new PipedInputStream();
	final PipedOutputStream outStream = new PipedOutputStream(inStream);

	try {
		// Creation of Writer Thread of PDF stream to PipedOutputStream
		new Thread(
			new Runnable(){
		      		public void run(){
		        	try {
					pdfDocument.save(outStream); // write to PipedOutputStream
				} catch (IOException e) {
					throw new RuntimeException("IO exception in the Writer Thread", e);
				}
	      		}
	    	}
		).start();
	} catch (RuntimeException e){
		throw new Exception(e.getMessage());
	}

	// Return PipedInputStream
	return inStream;
}

 
 
…The last point concerns methods of writing file’s content to client:

  • org.apache.commons.compress.utils.IOUtils.copy(…)
  • org.apache.commons.io.IOUtils.copy(…)
  • org.apache.commons.io.IOUtils.copyLarge(…)
  • org.apache.commons.compress.utils.IOUtils.copy(…, int buffersize=BUFFER_LENGTH)
  • Write InputStream of the file content to the OutputStream of HTTP response with buffersize=BUFFER_LENGTH
private void writeInputStreamToOutputStream(OutputStream myOutputStream, InputStream fileStream) throws IOException {
	InputStream is = null;
	BufferedInputStream bfis = null;
	int MODE_SENDING = 1;
			
	try{
		is = fileStream;
		
		try{
			if(MODE_SENDING == 1){ // org.apache.commons.compress.utils.IOUtils.copy(...)
				org.apache.commons.compress.utils.IOUtils.copy(is, myOutputStream);

			}else if(MODE_SENDING == 2){ // org.apache.commons.io.IOUtils.copy(...)
				org.apache.commons.io.IOUtils.copy(is, myOutputStream);

			}else if(MODE_SENDING == 3){ // org.apache.commons.io.IOUtils.copyLarge(...)
				org.apache.commons.io.IOUtils.copyLarge(is, myOutputStream);
					
			}else if(MODE_SENDING == 4){ // org.apache.commons.compress.utils.IOUtils.copy(..., int buffersize=BUFFER_LENGTH)
				org.apache.commons.compress.utils.IOUtils.copy(is, myOutputStream, BUFFER_LENGTH);

			}else{ // Write InputStream of the file content to the OutputStream of HTTP response with buffersize=BUFFER_LENGTH
				int counter=0;
				int bytesRead=0;
				bfis = new BufferedInputStream(is);
				byte[] buffer = new byte[BUFFER_LENGTH];
				while ((bytesRead = bfis.read(buffer)) != -1) {
					myOutputStream.write(buffer, 0, bytesRead);
					counter++;
				}
			}
		}catch(IOException ex){
			throw ex;
		}
	}finally{
		//try{if(is!=null)is.close();}catch(Throwable ignore){}
		//try{if(bfis!=null)bfis.close();}catch(Throwable ignore){}
		//try{if(fileStream!=null)fileStream.close();}catch(Throwable ignore){}
	}
}

 
 

That’s all!!!

Huseyin OZVEREN