OMG,到底在寻找什么..................
(构造一个完美的J2EE系统所需要的完整知识体系)
posts - 198,  comments - 37,  trackbacks - 0
原贴地址:http://xiecc.itpub.net/post/1476/47438

发现我给自己设了个陷阱,对于
Acegi Security System 的解析并不是光写个安全认证的流程就能说清楚的。虽然看起来 Acegi 的文档确实挺累,但是当我动笔写时却发现要写得比这个文档更好还是挺难的。毕竟我们不能让本来很难的事情一下子就变得很简单了,我也是昨天重要看了一遍了 Acegi Security System 的源代码,才发现我自己有明白了好多不明白的东西。但是我仍然希望我写的东西能够帮助那些正在学习和研究 Acegi Security System 的人们,所以我又开始动笔了,呵呵!

虽然我不想介绍太多与安全认证流程无关的东西,但是有些东西的讲解却是必不可少的,因为没有这些基本的概念和类,后面的东西就没法说清了。不过对于具体的类、类图和它们之间的关联,我还是推荐去看 Professional Java Development with the Spring Framework 里关于 Acegi 的那一章,对于想读 Acegi 的源代码和了解 Acegi 内部设计的人来说,这一章真是太有用了。

本来不想贴这幅类图的,毕竟有盗版的嫌疑,但是发现有些东西不贴出来又说不清楚。整个认证功能的核心是 Authentication 接口,我们只把 Authentication 想象成一个包含用户基本信息的类就行了,它里面放了用户名、密码、这个用户的具体权限有哪些(当然具体的东西是由它的子类 UsernamePasswordAuthenticationToken 实现的,其它类的存放的信息稍有不同,毕竟验证的方式还是多种多样的,我这里描述的所有东西 Acegi 最常用的实现方式,而不考虑其它的东西,否则只会分散注意力,看了之后一头雾水)。 Authentication 里还包含了一个 GrantedAuthority 接口,今天暂且不讨论 Authorization 的问题了,毕竟它与验证的流程是不相关的,而具体的细节又极复杂。

我们通过 AuthenticationManager 这个接口来验证这个 Authtication 对象的合法性,真正的验证过程看上去很悬,其实最后的实现无非是去数据库搜索一下是否存在这个用户,密码是否匹配(说的仍然是最常用的实现方式,呵呵),只是它设计的时候对象的关联比较巧妙,类图看熟了就会觉得没什么,真正查数据库的那个类是 DaoAuthenticationProvider 。这个接口真正巧妙的地方是它执行后返回的结果是一个 Authentication ,而不是用一个布尔值来表示验证成功或失败。 Why? 记得当年在 JavaEye 上有个讨论 Exception 的贴子, robbin 认为用户安全认证应该用 Checked Exception 来控制流程,更多的人认为密码错误是正常的事件流,返回布尔值更为恰当,这里不讨论这两种观点的对错,毕竟每个人站的角度不同,具体的情况也不同。

但是如果要实现认证的透明性,我们要用到的却是 unchecked exception ,这个 Exception 叫做 AuthenticationException (如果是 authorization 会抛出 AccessDeniedException, 不过道理类似),这真是奇妙的事,因为 Exeception 是可以传递的,如果在本类里面处理不了这个 Exception ,我们就会将这个 Exception 抛给调用这个类的类,如此循环,直到有一个类可以处理它为止(对于 Web 来说应该是在页面上提示登录出错信息)。没想到 Exception 的这个种特性用在安全认证里如此的合适,权限不够?用户密码不对?我才不管呢,只要抛出个异常,最后会有人把它接住处理掉的。当然这里的另一个条件是 Unchecked ,只有 unchecked 才不会导致接口的污染,才能达到完全的透明性。

有了前面的基础接口,我们要提出下一个问题了,这个 Authentication 对象应该存放在哪里?几乎每个做过 Web 应用的人告诉我: HttpSession Acegi 也不例外,虽然还有其它的存放地点(要跟具体的 Web 容器结合,会导致不兼容性,一般不提倡用)。但是我们马上会问下一个问题:我们怎么得到 Authentication 对象?当然我们可以去 HttpSession 里去取,但是很多时候我们在验证的是与 Web 层无关的(比如要用户调用 Service 层的权限或 Domain Object 的权限)。我们必须用与 Web 无关的 API 来获取 Authentication 。这让我们想起了什么?对,是 Webwork Webwork Action 是完全与 Web API 无关的,它的 Request 里的参数自动 populate 成了 Action 的属性,但是我们仍然可以通过 ActionContext 来获取这些信息。它是怎么做到的?是 Threadlocal ,因为每个 Web Request 都会使 Web 容器生成一个新的线程来处理它的这个特性使我们可以将这些 Web 的数据一股脑塞给 Threadlocal 。这个存放 Authentication 的对象叫做 SecurityContext ,而把 SecurityContext 放入 Threadloal 或取出的则是 SecurityContextHolder ,下面就是它的类图:

讲完这些基础设施,我们就可以看具体的认证流程啦,真正的认证是一串的 Filter (对 Servlet 容器熟悉的人应该都不要解释了)。只不过 Acegi 在这些 Filter 上稍微玩的点花招,因为一般的 Filter 是不能定义在 Spring ApplicationContext 里的,所以这用了一个代理的 Filter 对象 FilterToBeanProxy Filter 操作 Delegate 给定义在 ApplicationContext 里的 Filter 。这个似乎跟主题无关,不过如果以后真有类似的需求的话,这倒是蛮管用的一招。当然还有 FilterChainProxy ,它把一串的 Filter 全部定义在一个 bean 里,使配置简化了好多,呵呵。

我们来看看 Filter 的一头一尾。头是 httpSessionContextIntegrationFilter ,其实它的功能前面已经讨论过了,在执行前把 HttpSession 里的 Authentication 取出来放到 SessionContextHolder Threadlocal )里,在执行完毕后,把 Authentication 塞回到 HttpSession 。真正的实现代码有一堆,不过核心的代码无非就这么几行:

Object contextFromSessionObject = httpSession.getAttribute(ACEGI_SECURITY_CONTEXT_KEY);

SecurityContextHolder.setContext((SecurityContext) contextFromSessionObject);

chain.doFilter(request, response);

httpSession.setAttribute(ACEGI_SECURITY_CONTEXT_KEY, SecurityContextHolder.getContext());

Filter 的尾是 securityEnforcementFilter ,它的工作就是进真正的用户认证的流程控制了,具体的认证工作它会 delegate FilterSecurityInterceptor ,但是不管怎么认证,结果无非是认证成功或失败, securityEnforcementFilter 只要管抓住异常再转到特定的页面就行了。下面就是这个类的信心代码:

try {

filterSecurityInterceptor.invoke(fi);

}

} catch (AuthenticationException authentication) {

sendStartAuthentication(fi, authentication);

} catch (AccessDeniedException accessDenied) {

sendAccessDeniedError(fi, accessDenied);

}

我们再来看看用户登录是怎么干的吧。 Acegi 的用户登录很有意思,为了不让用户写任何这方面的代码,它也直接把这个功能放到 Filter 里了,这个 Filter 叫做 authenticationProcessingFilter 。这个 Filter 的要求是页面上的 form Action 名字,登录名、密码的字段名都是定死的。一个简单的页面就这些啦:

<form action="<c:url value=' j_acegi_security_check '/>" method="POST">

<input type='text' name='j_username'>

<input type='password' name='j_password'>

<input name="submit" type="submit">

</form>

记住 action 必须叫 j_acegi_security_check ,用户名必须叫 j_username ,密码必须叫 j_password 。呵呵,代码就不写了,无非就是判断只要 Action 名字对,就把用户名、密码取出来认证一把,最后把认证成功的 Authetication 对象填到 SecurityContextHolder 里再导到相应页面,认证失败就导到出错页面。

呵呵,好了,认证的核心过程其实就这些了,虽然还有其它的好多的 Filter 和关联,但是当我们把核心的内容分析清楚之后,其它的都不难了。( Authorization 是例外,有些部分我还没看明白)。

posted on 2006-10-13 15:11 OMG 阅读(428) 评论(0)  编辑  收藏 所属分类: Spring

只有注册用户登录后才能发表评论。


网站导航:
 

<2006年10月>
24252627282930
1234567
891011121314
15161718192021
22232425262728
2930311234

常用链接

留言簿(1)

随笔分类

随笔档案

IT风云人物

文档

朋友

相册

经典网站

搜索

  •  

最新评论

阅读排行榜

评论排行榜