Advanced   Java   Services
Basic Authentication
back next up home

Basic Authentication


Erhält ein Servlet eine Anfrage auf einen geschützten Bereich, so muß es die vorher erwähnten Schritte durchführen. Im folgenden wird dies am Beispiel der sogenannten BASIC Authentifizierung gezeigt. Diese einfache, aber nicht sehr sichere Authentifizierung beherrscht jeder Browser.

Erstes Auslesen des Headerfelds "Authorization"

String encodedAuth = request.getHeader("Authorization");

Falls kein Paßwort übermittelt wurde liefert diese Methode null, andernfalls das codierte Paßwort. Wir nehmen an, es sei kein Paßwort übermittelt worden.

Setzen eines Antwortheaders, Server fordert Authentifizierung

// HTTP Statuscode 401 setzen
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);

// Verschlüsselungsmethode bekannt geben
response.setHeader("WWW-Authenticate", "BASIC realm=\"protected\"");

// Laden der Seite vom Server erzwingen
//resp.setHeader("Expires", "Sat, 01 Jan 2000 00:00:00 GMT");

Zusammen mit dem Antwortheader muß im body noch eine komplette HTML-Seite gesendet werden, für den Fall, daß die Authentifizierung mißlingt, etwa so:

PrintWriter out;
out = response.getWriter() ;
out.println("<html>");
out.println("<head><title>");
out.println("401 Authorization Required");
out.println("</title><head>");
out.println("<body>");
out.println("<p>");
out.println("This server could not verify that you are authorized to access the document");
out.println("requested.  Either you supplied the wrong credentials (e.g., bad password),");
out.println("or your browser doesn't understand how to supply the credentials required.");
out.println("</body>
out.println("</html>");
out.close() ;


Client (Browser) erhält Authentifizierungsaufforderung und zeigt Dialog

Erhält der Client einen Header, der eine Authentifizierung verlangt, so wird in der Regel ein modaler Dialog angeboten, der dazu auffordert Benitzername und Paßwort einzugeben. Das Fenster, das der Browser anbietet ist uns vertraut:

Aufforderung zur Authentifizierung in Netscape/Mozilla


Aufforderung zur Authentifizierung im InternetExplorer


Die beiden Eingaben in diesen modalen Dialog werden vom Browser zu einem String einzigen String zusammengefaßt, wobei Benutzername und Kennwort durch einen Doppelpunkt getrennt werden. Der so entstandene String wird sodann vom Browser nach der BASE64-Methode codiert. Der codierte String erhält zudem noch als Vorspann das Wort Basic, gefolgt von einem Leerzeichen. Der so aufgebaute String wird dann vom Browser an den Server geschickt. Der auf diese Weise konstruierte String sieht dann etwa folgendermaßen aus:

Basic bWF4OnN0cmF1Yg==


Erneutes Auslesen des Headerfelds "Authorization"

Beim Servlet angekommen, liefert der Methodenaufruf

String encodedAuth = request.getHeader("Authorization");

einen String dieser Bauart. Dieser muß nun vom Servlet decodiert werden, damit man Benutzernamen und Paßwort prüfen kann.


Die BASE64 Codierung


Das Codieren und Decodieren einer Zeichenkette nach dem BASE64-Schema ist mittlerweile public domain. Es existieren eine ganze Reihe von Javaprogrammen im Internet, die dies leisten. Eine der elegantesten Implementierungen dieses Algorithmus stammt von Mikael Grev ( www.miginfocom.com), den er in verschiedenen newsgroups veröffentlichte und der hier verwendet wird (siehe etwa bei javalobby). Ich habe seinen Code um zwei einfache Methoden ergänzt, die genau auf die Situation in einem Browser zugeschnitten sind. Damit wird das Decodieren der vom Browser übermittelten Codierung trivial.

Class Base64 extends java.lang.Object

A fast Java class to encode and decode to and from BASE64 in full accordance with RFC 2045. On Windows XP (sp1 with JRE 1.4.2_04 and later) this encoder and decoder is about 10 to 30 times faster on small arrays (100 - 1000 bytes) and 2-3 times as fast on larger arrays (100000 - 1000000 bytes). The encoder produces the same output as the Sun one except that the Sun's encoder appends a trailing line separator if the last character isn't a pad. Unclear why, but it only adds to the length and is probably a side effect. Both are in conformance with RFC 2045 though.
Licence: Free to use for any legal purpose, though sending an email to base64 @ miginfocom . com to tell me you're using it will ut a smile on my face! :) Reprint/republish for public consumption is allowed as long as this licence is included.

Author:
Mikael Grev (2004-aug-02)
Ergänzungen: Herbert Max Straub
ReturntypName der Methode
static final byte[]
 
 
 
 
 
 
decode(String s)
Decodes a BASE64 encoded string. All illegal characters will be igored and can handle both strings with and without line separators. (Author Mikael Grev)
Parameters:
    s - The string. null or "" will return an empty array.
Returns:
    The decoded array of bytes. Never null but may be of length 0.
static final String
 
 
 
 
 
 
 
 
 
encode(byte[] bArr, String lineSep)
Encodes a raw byte array into a BASE64 string representation i accordance with RFC 2045.
(Author Mikael Grev)
Parameters:
    bArr - The bytes to convert. If null or length 0, "" will be returned.
    lineSep - Optional line separator after 76 characters, unless end of file. Max 2 chars length. If null or length 0 no line breaks will be inserted. No line separator will be in breach of RFC 2045 which specifies max 76 per line but will be a little faster.
Returns:
    A BASE64 encoded string. Never null.
static final String
 
 
 
 
 
 
 
 
 
 
encode(String name, String pwd)
Encodes a name and a pwd like the browser does: first creates a new string out of the name, a colon and the pwd, then encodes this string and prefixes the encoded string with "Basic " and returns the result.
(Author Herbert Max Straub)
Parameters:
    name - The name of the client.
    pwd - The password of the client.
Returns:
    A string starting with "Basic " followed by a BASE64 encoded part.
Throws:
    IllegalArgumentException - if one of the arguments is null.
static final String[]
 
 
 
 
 
 
 
 
 
 
 
 
decode2(String s)
Decodes an encoded string, assuming it starts with the clear text "Basic " followed by an encoded name + colon + pwd. Skips the prefix and decode the following part. Then looks for the colon in the decoded string and splits it up in a name part and a pwd part. Returns an array of length 2 including the name at index 0 and the pwd at index 1.
(Author Herbert Max Straub)
Parameters:
    s - A string starting with the text "Basic ".
Returns:
    A string array with the decoded name at index 0 and the decoded pwd at index 1.
Throws:
    IllegalArgumentException - if the paramater does not start with "Basic " or the decoded parameter does not contain a colon.


Das Decodieren erfolgt nun mit der Methode decode2.

String[] decoded = Base64.decode2(encodedAuth);
String name = decoded[0];
String pwd = decoded[1];

try catch ist natürlich angebracht. Es folge der Sourcecode von Mikael Grev's Klasse inklusiver der kleinen Erweiterungen.

Quellcode: Mikael Grev's Klasse Base64


import java.util.Arrays;

/** A fast Java class to encode and decode to and from BASE64 in full accordance with RFC 2045.
 * On Windows XP sp1 with 1.4.2_04 and later ;), this encoder and decoder is about 10 to 30 times
 * faster on small arrays (100 - 1000 bytes) and 2-3 times as fast on larger arrays
 * (100000 - 1000000 bytes). The encoder produces the same output as the Sun one except that the
 * Sun's encoder appends a trailing line separator if the last character isn't a pad. Unclear why,
 * but it only adds to the length and is probably a side effect. Both are in conformance with
 * RFC 2045 though.
 *
 * Licence:
 * Free to use for any legal purpose, though sending an email to base64@miginfocom.com to tell
 * me you're using it will ut a smile on my face! :)
 * Reprint/republish for public consumption is allowed as long as this licence is included.
 * @author Mikael Grev
 *         Date: 2004-aug-02
 *         Time: 11:31:11
 */

public class Base64
{
   private static final
      char[] CA = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray();
   private static final int[] IA = new int[256];

   static
   {
      Arrays.fill(IA, -1);
      for (int i = 0, iS = CA.length; i < iS; i++)
         IA[CA[i]] = i;
   }

   // private constructor
   private Base64()
   {}


   /** Encodes a raw byte array into a BASE64 string representation i accordance with RFC 2045.
      
(Author Mikael Grev) * @param bArr The bytes to convert. If null or length 0, "" will be returned. * @param lineSep Optional line separator after 76 characters, unless end of file. * Max 2 chars length. If null or length 0 no line breaks will be inserted. * No line separator will be in breach of RFC 2045 which specifies max 76 per line but will * be a little faster. * @return A BASE64 encoded string. Never null. */ public final static String encode(byte[] bArr, String lineSep) { // Check special case if (bArr == null || bArr.length == 0) return ""; int last = bArr.length - 1; // Last byte int cCnt = (last / 3 + 1) << 2; // Character count int sepLen = lineSep != null ? lineSep.length() : 0; int sepCnt = ((cCnt - 1) / 76) * sepLen; // line separator count int encArrLen = cCnt + sepCnt; // Length of returned string char[] encArr = new char[encArrLen]; for (int rOff = 0, cOff = 0, sepAdd = 0; rOff <= last; rOff += 3) { int left = last - rOff; int bEnd = (left > 1 ? 2 : left); // Collect 1 to 3 bytes to encode int block = 0; for (int i = 0, r = 16; i <= bEnd; i++, r -= 8) { int n = bArr[rOff + i]; block += (n < 0 ? n + 256 : n) << r; } // Encode into 2-4 chars appending '=' if not enough data left. encArr[cOff++] = CA[(block >>> 18) & 0x3f]; // >>> is faster than >> !! encArr[cOff++] = CA[(block >>> 12) & 0x3f]; encArr[cOff++] = left > 0 ? CA[(block >>> 6) & 0x3f] : '='; encArr[cOff++] = left > 1 ? CA[block & 0x3f] : '='; // Possibly insert line break after character 76. if (sepCnt > 0 && encArrLen > cOff) // If we have a separator and not end if buf { if ((cOff - sepAdd) % 76 == 0) // If after char 76 { encArr[cOff++] = lineSep.charAt(0); if (sepLen > 1) encArr[cOff++] = lineSep.charAt(1); sepAdd += sepLen; } } } return new String(encArr); } /** * encodes a name and a pwd like the browser does: * makes a new string out of the name, a colon and the pwd, encodes this string * and prefixes the encoded string with "Basic " and returns the result. *
(Author Herbert Max Straub) * @throws IllegalArgumentException if one of the arguments is null. * @return A string starting with "Basic " followed by a BASE64 encoded string. * */ public final static String encode(String name , String pwd) { if(name==null || pwd==null) throw new IllegalArgumentException("both parameters must not be null"); return "Basic " + encode( (name + ":" + pwd).getBytes() , null) ; } /** * Decodes a BASE64 encoded string. All illegal characters will be igored and can handle * both strings with * and without line separators. *
(Author Mikael Grev) * @param s The string. null or "" will return an empty array. * @return The decoded array of bytes. Never null but may be of length 0. */ public final static byte[] decode(String s) { // Check special case if (s == null || s.length() == 0) return new byte[0]; // Note. Making an array of s is faster somtimes but slower sometimes. // Timing values are more uneven due to garbage creation. // Count '=' at end and disregard them int pad = 0; for (int i = s.length() - 1; s.charAt(i) == '='; i--) pad++; // Count illegal characters to know what size the returned byte[] will have so we // don't have to reallocate it later. int sepCnt = 0; // Number of separator characters. // (Actually illegal characters, but that's a bonus...) for (int i = 0, iS = s.length() - pad; i < iS; i++) { if (IA[s.charAt(i)] < 0) sepCnt++; } int len = (((s.length() - sepCnt) * 6) >> 3) - pad; byte[] b = new byte[len]; // Preallocate byte[] of exact length for (int i = 0, iS = s.length() - pad, bIx = 0; i < iS; bIx += 3) { // Assemble three bytes into an int from four "valid" characters. int bits = 0; for (int j = 0; j < 4 && i < iS;) // j only increased if a valid char was found. { int c = IA[s.charAt(i++)]; if (c >= 0) bits += c << (18 - 6 * j++); } // Add the bytes for (int j = 0, r = 16; j < 3 && bIx + j < len; j++, r -= 8) b[bIx + j] = (byte) ((bits >> r) & 0xff); } return b; } /** * decodes an encoded string, assuming it starts with the clear text "Basic " followed by * an encoded name + colon + pwd. skips the prefix, decode and * looks for the colon in the decoded string and splits it up in a * name part and a pwd part. returns an array of length 2 including * the name at index 0 and the pwd at index 1. *
(Author Herbert Max Straub) * @throws IllegalArgumentException if the paramater does not start with Basic * or the decoded parameter does noct contain a colon. * @return A string array with the decoded name at index 0 and the decoded pwd at index 1. */ public final static String[] decode2(String s) { if(s==null) throw new IllegalArgumentException("parameter most not be null"); if(!s.toUpperCase().startsWith("BASIC") ) throw new IllegalArgumentException("parameter does not start with BASIC " + s); String decoded = new String( decode(s.substring(6)) ) ; int colon = decoded.indexOf(":"); if(colon <0) throw new IllegalArgumentException("decoded parameter does not contain a colon"); return new String[] { decoded.substring(0,colon), decoded.substring(colon+1) } ; } }

top back next up home