创建好用户,用户就可以登陆了。
在Roller的系统中,用户登陆其实是直接指向登陆成功的首页:
连接为: /roller-ui/login-redirect.rol
这个配置在struts.xml中
<action name="login-redirect">
<result>/roller-ui/login-redirect.jsp</result>
</action>
在acegi中配置:
<bean id="filterInvocationInterceptor" class="org.acegisecurity.intercept.web.FilterSecurityInterceptor">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="accessDecisionManager" ref="accessDecisionManager"/>
<property name="objectDefinitionSource">
<value>
PATTERN_TYPE_APACHE_ANT
/roller-ui/login-redirect**=admin,editor
/roller-ui/profile**=admin,editor
/roller-ui/createWeblog**=admin,editor
/roller-ui/menu**=admin,editor
/roller-ui/authoring/**=admin,editor
/roller-ui/admin/**=admin
/rewrite-status*=admin
</value>
<!-- Add this to above list for LDAP/SSO configuration -->
<!-- /roller-ui/user.do*=register -->
</property>
</bean>
连接到 /roller-ui/login-redirect** 需要 admin或者editor权限,
如果用户没有登陆,就跳转到用户登陆页面中。
这个由配置中的authenticationManager来进行处理
如果用户登陆了,需要验证权限,由配置中的accessDecisionManager来进行处理。
这个登陆完全是采用acegi的方式进行。
/roller/roller-ui/login.rol 进行登陆;
登陆是 : org.apache.roller.weblogger.ui.struts2.core.Login进行处理
这个servlet只是转向到tiles的.Login配置,具体页面为: jsps/core/Login.jsp
登陆的要求发送给:<c:url value="/roller_j_security_check"/>
在submit之前,先把用户名,保留在cookie中,key为username,过期时间:但前日期+30天
-------------------------------
这里要提到原来的一个未说明的 Listener了: RollerSession
RollerSession: 实现了: HttpSessionListener
HttpSessionActivationListener
(serializable)
这个就是在创建一个Session的时候,系统创建了一个RollerSession的实例放在session中
----------------------------
连接: roller_j_security_check 配置给了 acegi的登陆:
配置文件security.xml
<bean id="authenticationProcessingFilter" class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="authenticationFailureUrl" value="/roller-ui/login.rol?error=true"/>
<property name="defaultTargetUrl" value="/"/>
<property name="filterProcessesUrl" value="/roller_j_security_check
"/>
<property name="rememberMeServices" ref="rememberMeServices"/>
</bean>
这个配置给了
filterProcessesUrl
具体的工作,由authenticationManager进行处理。
在Roller中,authenticationManager的配置为:
<bean id="authenticationManager
" class="org.acegisecurity.providers.ProviderManager">
<property name="providers">
<list>
<ref local="daoAuthenticationProvider"/>
<!-- Uncomment this for LDAP/SSO configuration <ref local="ldapAuthProvider"/> -->
<!-- Uncomment this for CAS/SSO configuration <ref local="casAuthenticationProvider"/> -->
<ref local="anonymousAuthenticationProvider"/>
<!-- rememberMeAuthenticationProvider added programmatically -->
</list>
</property>
</bean>
这个由:daoAuthenticationProvider来进行具体的实现:
daoAuthenticationProvider的配置:
<bean id="daoAuthenticationProvider
" class="org.acegisecurity.providers.dao.DaoAuthenticationProvider">
<property name="userDetailsService" ref="jdbcAuthenticationDao"/>
<property name="userCache" ref="userCache"/>
</bean>
<!-- Read users from Roller API -->
<bean id="jdbcAuthenticationDao" class="org.apache.roller.weblogger.ui.core.security.RollerUserDetailsService"/>
<bean id="userCache" class="org.acegisecurity.providers.dao.cache.EhCacheBasedUserCache">
<property name="cache">
<bean class="org.springframework.cache.ehcache.EhCacheFactoryBean">
<property name="cacheManager">
<bean class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"/>
</property>
<property name="cacheName" value="userCache"/>
</bean>
</property>
</bean>
RollerUserDetailsService实现了接口: org.acegisecurity.userdetails.UserDetailsService
实现了方法: loadUserByUsername
通过webloggerFactory.getWeblooger().getUserManager().getUserByUserName, 获取一个User对象;
同时获取GrantedAuthority,
最后生成一个 org.acegisecurity.userdetails.User对象返回
-------------------------
来看一下Roller中使用的acegi的验证:
首先在web.xml中,定义采用acegi:
<filter>
<filter-name>securityFilter</filter-name>
<filter-class>org.acegisecurity.util.FilterToBeanProxy
</filter-class>
<init-param>
<param-name>targetClass</param-name>
<param-value>org.acegisecurity.util.FilterChainProxy</param-value>
</init-param>
</filter>
<!-- Acegi Security filters - controls secure access to different parts of Roller -->
<!-- 对于所有的url都进行权限验证 -->
<filter-mapping>
<filter-name>securityFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
</filter-mapping>
FilterToBeanProxy是一个特殊的Servlet过滤器,它本身完成的工作并不多,而是将自己的工作委托给Spring应用程序上下文中的一个Bean来完成。被委托的Bean几乎和其他的Servlet过滤器一样,实现javax.servlet.Filter接口。但是它是在Spring的配置文件而不是在web.xml中配置的。
FilterToBeanProxy的参数target:表示将过滤工作委托给哪个Bean来完成。
当这个FilterToBeanProxy被初始化时,它会在Spring的上下文中寻找这个Bean,并且把自己的过滤工作委托给这个Bean完成。(这意味着这个Spring上下文必须使用Spring的ContextLoaderListener或者是contextLoaderServlet进行加载)
security.xml中的配置:
<!-- ======================== FILTER CHAIN ======================= -->
<bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy">
<property name="filterInvocationDefinitionSource">
<value>
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/**=httpSessionContextIntegrationFilter,authenticationProcessingFilter,rememberMeProcessingFilter,channelProcessingFilter,remoteUserFilter,anonymousProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor
</value>
<!-- Replace "authenticationProcessingFilter,rememberMeProcessingFilter" with "casProcessingFilter" if you want to use Roller with CAS -->
</property>
</bean>
首先配置了acegi的FilterChainProxy,这个定义了在Http请求采用多少种方式的Filter进行处理;
FilterChainProxy:是javax.servlet.Filter的一个实现,它可以被配置来同时把几个过滤器链接在一起,
它有一个参数:filterInvocationDefinitionSource:String类型参数,CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON定义了url比较前先转为小写,
它被解析FilterChainProxy将用来把过滤器接到一起的一种模式。
接下来一行,在声明URL模式时,采用Apache Ant样式路径,
最后,一个或多个”URL-到-过滤器-链“映像被提供。在这里, /**模式(在Ant中,这意味着所有URL都将匹配)被映像给后面的这些过滤器。
对于用户登陆,使用的是:authenticationProcessingFilter,这样就回到了本文的开头的配置说明。
在authenticationProcessingFilter的配置中:
filterProcessesUrl:处理来源URL
defaultTargetUrl: 处理成功的去向
authenticationFailureUrl:验证失败URl
在Acegi框架规定必须使用j_username和j_password作为用户名和密码的HTTP参数名。这两个参数名是固定的,Acegi不允许对此进行配置,不过这种约定俗成而非事事配置的风格正渐渐成为流行的设计模式。
---------------
整体流程在复述一次:
- Http请求过来,通过Acegi的Filter:org.acegisecurity.util.FilterToBeanProxy,
- 这个其实是一个代理,通过配置参数中的 org.acegisecurity.util.FilterChainProxy 进行实际 filter链处理(简化web.xml中的配置,同时可以利用到Spring的IoC优势)
- 在这个处理链中负责处理登陆的是:authenticationProcessingFilter
- 在authenticationProcessingFilter中,是通过authenticationManager来进行具体操作的,
- authenticationManager中配置了多个ProviderManager,目前应该是daoAuthenticationProvider来进行的:
daoAuthenticationProvider进行简单的基于数据库的身份验证。DaoAuthenticationProvider获取数据库中的账号密码并进行匹配,若成功则在通过用户身份的同时 返回一个包含授权信息的Authentication对象,否则身份验证失败,抛出一个AuthenticatiionException。
对于daoAuthenticationProvider的配置,需要了解3个参数:userDetailsService : 用于在数据中获取用户信息。 acegi提供了用户及授权的表结构,但是您也可以自己来实现。 通过usersByUsernameQuery这个SQL得到你的(用户ID,密码,状态信息); 通过authoritiesByUsernameQuery这个SQL得到你的(用户ID,授权信息) userCache : 缓存用户和资源相对应的权限信息。每当请求一个受保护资源时,daoAuthenticationProvider就会被调用以获取用户授权信息。 如果每次都从数据库获取的话,那代价很高,对于不常改变的用户和资源信息来说,最好是把相关授权信息缓存起来 userCache提供了两种实现: NullUserCache 和 EhCacheBasedUserCache, NullUserCache实际上就是不进行任何缓存, EhCacheBasedUserCache是使用Ehcache来实现缓功能。 passwordEncoder : 使用加密器对用户输入的明文进行加密。Acegi提供了三种加密器: PlaintextPasswordEncoder—默认,不加密,返回明文. ShaPasswordEncoder—哈希算法(SHA)加密 Md5PasswordEncoder—消息摘要(MD5)加密
- daoAuthenticationProvider中配置了jdbcAuthenticationDao进行获取用户对象,
- jdbcAuthenticationDao的具体实现是:org.apache.roller.weblogger.ui.core.security.RollerUserDetailsService
------------------------------------------------------------------------------------------------------------------------------
Acegi的认证管理器(authenticationManager)有:
Acegi提供了不同的AuthenticationProvider的实现,如:
DaoAuthenticationProvider 从数据库中读取用户信息验证身份
AnonymousAuthenticationProvider 匿名用户身份认证
RememberMeAuthenticationProvider 已存cookie中的用户信息身份认证
AuthByAdapterProvider 使用容器的适配器验证身份
CasAuthenticationProvider 根据Yale中心认证服务验证身份, 用于实现单点登陆
JaasAuthenticationProvider 从JASS登陆配置中获取用户信息验证身份
RemoteAuthenticationProvider 根据远程服务验证用户身份 中国网管联盟www.bitscn.com
RunAsImplAuthenticationProvider 对身份已被管理器替换的用户进行验证
X509AuthenticationProvider 从X509认证中获取用户信息验证身份
TestingAuthenticationProvider 单元测试时使用
------------------------------------------------------------------------------------------------------------------------------
在FilterChainProxy中,配置的一些过滤器:
ChannelProcessingFilter:通道处理过滤器,可选
该过滤器负责检查当前请求的Channel,并判断是否已满足安全需 要。如果不满足,则由非安全的通道传输(Http)重定向于安全的通道传输(Https),以确保服务器与浏览器之间传输的数据加密。
AuthenticationProcessingFilter: 认证处理过滤器
该过滤器会判断该请求是否是一个认证请求(通常是 "/j_acegi_security_check")。如果是,则它会从请求中获取用户名和密码,并转交给AuthenticationManager 来认证用户身份。如果不是,则会直接转到下一个Filter。
HttpSessionContextIntegrationFilter : HttpSession安 全上下文信息集成过滤器
该过滤器负责将Authentication对象保存在HttpSession中,使其在下一个请求到来时仍可被获取。故 Authentication能跨越多个请求。
FilterSecurityInterceptor : 过滤器安全拦截器
该过滤器会首先调用 AuthenticationManager判断用户是否已登陆认证,如还没认证成功,则重定向到登陆界面。认证成功,则并从 Authentication中获取用户的权限。然后从objectDefinitionSource属性获取各种URL资源所对应的权限。最后调用 AccessDecisionManager来判断用户所拥有的权限与当前受保华的URL资源所对应的权限是否相匹配。如果匹配失败,则返回403错误 (禁止访问)给用户。匹配成功则用户可以访问受保护的URL资源。
# 保护方法调用
当去保护方法调用时,Acegi使用Spring AOP,将"切面"应用于所代理的对象,以确保用户只有在拥有恰当授权时才可以调用受保护的方法。
MethodSecurityInterceptor : 方法调用安全拦截器
同样也是在调用方法之前,先调用AuthenticationManager 判断用户身份是否已验证,然后从objectDefinitionSource中获取方法所对应的权限,再调用 AccessDecisionManager来匹配用户权限和方法对应的权限。如果用户没有足够权限调用当前方法,则抛出 AccessDeniedException使方法不能被调用。
------------------------------------------------------------------------------------------------------------------------------如果已有表和Acegi的表名字不一样,如何验证