方案
Acegi登陆使用中文帐号名登陆时会抛出异常,下面我贴出的JSP页面上抛出的错误信息(只贴出有分析价值的一部分):
root cause
java.lang.IllegalArgumentException: 张三
org.apache.tomcat.util.http.ServerCookie.maybeQuote(ServerCookie.java:276)
org.apache.tomcat.util.http.ServerCookie.appendCookieValue(ServerCookie.java:209)
org.apache.coyote.tomcat5.CoyoteResponse.addCookie(CoyoteResponse.java:950)
org.apache.coyote.tomcat5.CoyoteResponseFacade.addCookie(CoyoteResponseFacade.java:291)
javax.servlet.http.HttpServletResponseWrapper.addCookie(HttpServletResponseWrapper.java:102)
分析异常的原因,应该是底层的ServerCookie试图增加一个值为张三的cookie导致的异常,(cookie的name肯定不能为中文,这个“张三”应该为value值,但value应该可以是中文的(我用的GBK编码,查看ServerCookie.java好象是用了isToken方法来验证这个cookie的value值)),另外这个cookie是acegi框架中如何传递给ServerCookie的很让人困惑。
我按cookie单词查找了acegi(1.0.5版本)的sourcecode,没有发现acegi中有增加中文cookie的地方,以下是跟cookie相关的类:
SavedRequest.java,SavedRequestAwareWrapper.java,RememberMeServices.java,TokenBasedRememberMeServices.java,我的acegi配置文件中没有配置rememberme相关的过滤器,所以cookie应该和rememberme类没关系,下面是我配置的acegi的过滤器:
/**=httpSessionContextIntegrationFilter,casProcessingFilter,logoutFilter,concurrentSessionFilter,authenticationProcessingFilter,securityContextHolderAwareRequestFilter,anonymousProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor
我在org.acegisecurity.util.FilterChainProxy类的VirtualFilterChain子类中删除session,cookie,
发现java.lang.IllegalArgumentException: 张三 的异常仍然存在,于是我怀疑到是否认证之后,org.apache.tomcat.util.http.ServerCookie是从request.getUserPrincipal().getName()中获取登陆名试图加入到cookie中的,request.getUserPrincipal().getName()返回的是张三,即中文登陆帐号,此值的赋值方式也非常奇怪,在org.acegisecurity.wrapper.SecurityContextHolderAwareRequestFilter.java中,有一句:
request = (HttpServletRequest) constructor.newInstance(new Object[] {request, portResolver});
在此句之前使用request.getUserPrincipal().getName()会抛出异常,因为getUserPrincipal()是空的,在执行了constructor.newInstance之后再运行request.getUserPrincipal().getName(),发现此句能获得登陆帐号“张三”,不知道这个newInstance是什么原理,怎么会自动地填充了principal信息(看来我的servletAPI原理了解的比较有限)?
一个简单的让acegi支持中文帐号登陆的方式就是在org.acegisecurity.ui.webapp.AuthenticationProcessingFilter中将从页面获得的登陆id进行替换,替换为中文对应的英文id,这种方式经测试是可行的,而且绕过了上面提到的cookie的问题,
具体实现就是首先获得页面的中文登陆帐号,然后从数据库的登陆帐号表中找到这个中文帐号(可以使用登陆帐号表的用户名称作为中文帐号),然后找到中文帐号对应的英文帐号,例如根据“张三”找到”zhangsan”,经过测试后,这种方式完全可行!参考下面AuthenticationProcessingFilter更改后的代码:
String username = obtainUsername(request);
String password = obtainPassword(request);
//使用替代方案,如果不支持中文登陆帐号,可建立一个中文帐号-英文帐号对照表,根据中文帐号替换为英文帐号,按英文帐号登录
String userid = "";
try
{
if(username!=null&&username.trim().length()>0)
{
userid = ServiceLocator.getDBSupportService().findSingleValueBySql("select user_id from eas_login_user where user_name=''"+username+"''", null).toString();
}
}
catch(Exception ex)
{
System.out.println("查询用户错误!");
}
if(userid==null)userid="";
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
username=userid; //将中文登陆帐号替换为英文帐号
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
AuthenticationProcessingFilter是获取页面登录用户帐号和口令的入口点,所以在这里替换后,后面的生成Authentication和Principal信息都按照替换后的英文登陆帐号处理。
这种替换帐号的方式固然可行,不过cookie的问题还有很有继续研究的必要,这有助于我们从更深的层次了解servlet容器的原理。