创建好用户,用户就可以登陆了。
在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不允许对此进行配置,不过这种约定俗成而非事事配置的风格正渐渐成为流行的设计模式。
---------------
整体流程在复述一次:
userDetailsService : 用于在数据中获取用户信息。 acegi提供了用户及授权的表结构,但是您也可以自己来实现。 通过usersByUsernameQuery这个SQL得到你的(用户ID,密码,状态信息); 通过authoritiesByUsernameQuery这个SQL得到你的(用户ID,授权信息) userCache : 缓存用户和资源相对应的权限信息。每当请求一个受保护资源时,daoAuthenticationProvider就会被调用以获取用户授权信息。 如果每次都从数据库获取的话,那代价很高,对于不常改变的用户和资源信息来说,最好是把相关授权信息缓存起来 userCache提供了两种实现: NullUserCache 和 EhCacheBasedUserCache, NullUserCache实际上就是不进行任何缓存, EhCacheBasedUserCache是使用Ehcache来实现缓功能。 passwordEncoder : 使用加密器对用户输入的明文进行加密。Acegi提供了三种加密器: PlaintextPasswordEncoder—默认,不加密,返回明文. ShaPasswordEncoder—哈希算法(SHA)加密 Md5PasswordEncoder—消息摘要(MD5)加密
------------------------------------------------------------------------------------------------------------------------------
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的表名字不一样,如何验证