原贴地址: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 阅读(436)
评论(0) 编辑 收藏 所属分类:
Spring