分类:Java, Web UI & Ajax, 服务器端技术, 程序设计, 计算机与互联网 作者: 秋天 on 24-10-2009
问题:在Cookie value中储存特殊字符并读取。
典型案例:email地址在cookie value中的存取。
解决方法:通过将Cookie value进行Base54编码后储存,读取,并解码。
环境:Java & Javascript
问题延伸:Javascript中读取Base64值错误的解决方法。
我个人认为使用email地址来作为用户帐户登录名称是一个比较不错的方法。首先email地址本身已经具备一定程度上的唯一性。通过一定的正则表达式判断或者直接使用现成的一些判断类和Service判断后可以使数据库中的该字段价值更高。其次在同一用户需要使用多个帐户的情况下,相比“用户名”来说,更加直接并且容易记忆。
使用传统的方式创建的cookie:
Cookie cookie = new Cookie(cookieName, cookieValue); ((BridgeExternalContext)context.getExternalContext()).addCookie(cookie); // icefaces中使用ExternalContext储存cookie
此时如果cookieValue的值是包含一些特定字符,比如空格,大括号,小括号,逗号,以及@等。用直接读取的方式将不能得到正确的值。
比如上面的代码中,String cookieValue = "sam@satech.com.au"
使用下面的代码直接读取:
Cookie[] cookies = ((HttpServletRequest)((BridgeExternalContext)FacesContext
.getCurrentInstance().getExternalContext()).getRequest()).getCookies();
for(int i=0;i<cookies.length;i++){
if(cookies[i].getName().equals(loginCookieName)) {
this.loginName = cookies[i].getValue();
}
你会发现,储存后在firefox的cookie查看器中能看到正常的值,而读取出来以后,值只剩下一个sam了。这显然不是我们所想要的。
最简单的解决方式,就是使用某种编码方式避开这些不能接受的字符,之后读取出来再进行解码。这里使用Base64编码来解决。其实使用任何编码方式都是可以的。
我的环境中有很多Base64类的继承类可以使用。比较常见的是
import sun.misc.BASE64Decoder; import sun.misc.BASE64Encoder;
其他一些继承类中的加密和解码方法,可以直接作为静态方法调用,比如Icefaces自己也在utility包里面包含了。Base64类,需要的朋友可以尝试使用,原理是一样的。我这里列出使用基本的sun的misc包中的BASE64Encoder & Decoder的方法。
编码
if (log.isDebugEnabled()) {
log.debug("Encoding string: " + this.loginName);
}
byte[] encodedBytes = this.loginName.getBytes(); // convert String into byte[]
BASE64Encoder base64encoder = new BASE64Encoder(); // Create encoder
this.base64LoginName = base64encoder.encode(encodedBytes);
Cookie nameCookie = new Cookie(loginCookieName, this.base64LoginName);
nameCookie.setMaxAge(SECONDS_PER_YEAR);
nameCookie.setComment("This is the cookie to store username");
((BridgeExternalContext)context.getExternalContext()).addCookie(nameCookie); // save cookie to context
解码
Cookie[] cookies = ((HttpServletRequest)((BridgeExternalContext)FacesContext
.getCurrentInstance().getExternalContext()).getRequest()).getCookies();
for(int i=0;i<cookies.length;i++){
if(cookies[i].getName().equals(loginCookieName)) {
this.base64LoginName = cookies[i].getValue();
BASE64Decoder decoder = new BASE64Decoder();
try {
byte[] decodedBytes = decoder.decodeBuffer(this.base64LoginName);
this.loginName = new String(decodedBytes);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
现在这个cookie value就能接受大部分的字符集了。但是,又发现一个问题。大部分的Base64都是以"="结尾。在纯Java环境中读取和储存都没有问题,但是某些时候会需要在Javascript中储存和读取这个值。在Javascript中读取已储存的Base64值的时候,最后面的"="会丢失,导致读取错误。
解决方案有2,第一,使用URLCodec urlCodec再次编码。第二,自己写一个判断函数来加上最后的"=" or "=="
方案一:
public class CookieDecoder {
private static final Log log = LogFactory.getLog(CookieDecoder.class);
/**
* @param cookieValue The value of the cookie to decode
* @return Returns the decoded string
*/
public String decode(String cookieValue) {
if (cookieValue == null || "".equals(cookieValue)) {
return null;
}
if (log.isDebugEnabled()) {
log.debug("Decoding string: " + cookieValue);
}
URLCodec urlCodec = new URLCodec();
String b64Str;
try {
b64Str = urlCodec.decode(cookieValue);
}
catch (DecoderException e) {
log.error("Error decoding string: " + cookieValue);
return null;
}
Base64 base64 = new Base64();
byte[] encodedBytes = b64Str.getBytes();
byte[] decodedBytes = base64.decode(encodedBytes);
String result = new String(decodedBytes);
if (log.isDebugEnabled()) {
log.debug("Decoded string to: " + result);
}
return result;
}
}
JS部分:
Encode:
var encodedValue = this.base64.encode(value); document.cookie = name + "=" + escape(encodedValue) + "; expires=" + this.expires.toGMTString() + "; path=" + this.path;
Decode:
var nameEQ = name + "="; var ca = document.cookie.split(';'); for(var i = 0; i < ca.length; i++) { var c = ca[i]; while (c.charAt(0)==' ') { c = c.substring(1,c.length); } if (c.indexOf(nameEQ) == 0) { var encodedValue = c.substring(nameEQ.length,c.length); return this.base64.decode(unescape(encodedValue)); } } return null;
方案二:padString()来解决最后那个等号。
private static final Log log = LogFactory.getLog(CookieDecoder.class);
/**
* @param cookieValue The value of the cookie to decode
* @return Returns the decoded string
*/
public String decode(String cookieValue) {
if (cookieValue == null || "".equals(cookieValue)) {
return null;
}
if (!cookieValue.endsWith("=")) {
cookieValue = padString(cookieValue);
}
if (log.isDebugEnabled()) {
log.debug("Decoding string: " + cookieValue);
}
Base64 base64 = new Base64();
byte[] encodedBytes = cookieValue.getBytes();
byte[] decodedBytes = base64.decode(encodedBytes);
String result = new String(decodedBytes);
if (log.isDebugEnabled()) {
log.debug("Decoded string to: " + result);
}
return result;
}
private String padString(String value) {
int mod = value.length() % 4;
if (mod <= 0) {
return value;
}
int numEqs = 4 - mod;
if (log.isDebugEnabled()) {
log.debug("Padding value with " + numEqs + " = signs");
}
for (int i = 0; i < numEqs; i++) {
value += "=";
}
return value;
}
JS部分:现在在JS中就可以按照正常方法读取了
var encodedValue = this.base64.encode(value); document.cookie = name + "=" + encodedValue + "; expires=" + this.expires.toGMTString() + "; path=" + this.path;
至此,基本解决问题。


This seems like a nice one. “Java EE 5 Development using GlassFish Application Server” written by David R. Heffelfinger has recently been published by Packt Publishing and is intended to guide developers “through the development and deployment of Java EE 5 compliant applications on GlassFish version 2″.

