浅谈Acegi配置Acegi Security System 是一种功能强大并易于使用的替代性方案,使您不必再为 Java 企业应用
浅谈Acegi配置
Acegi Security System 是一种功能强大并易于使用的替代性方案,使您不必再为 Java 企业应用程序编写大量的安全代码。虽然它专门针对使用 Spring 框架编写的应用程序,但是任何类型的 Java 应用程序都没有理由不去使用 Acegi。
本文的主要目的是希望能够说明如何在基于Spring构架的Web应用中使用Acegi,而不是详细介绍其中的每个接口、每个类。注意,即使对已经存在的Spring应用,通过下面介绍的步骤,也可以马上享受到Acegi提供的认证和授权。?
?
Acegi Security System 使用安全过滤器来提供企业应用程序的身份验证和授权服务。该框架提供了不同类型的过滤器,可以根据应用程序的需求进行配置。您将在本文后面了解到?安全过滤器的不同类型?;现在,只需注意可以为如下任务配置 Acegi 安全过滤器:
- 在访问一个安全资源之前提示用户登录。
- 通过检查安全标记(如密码),对用户进行身份验证。
- 检查经过身份验证的用户是否具有访问某个安全资源的特权。
- 将成功进行身份验证和授权的用户重定向到所请求的安全资源。
- 对不具备访问安全资源特权的用户显示 Access Denied 页面。
- 在服务器上记录成功进行身份验证的用户,并在用户的客户机上设置安全 cookie。使用该 cookie 执行下一次身份验证,而无需要求用户登录。
- 将身份验证信息存储在服务器端的会话对象中,从而安全地进行对资源的后续请求。
- 在服务器端对象中构建并保存安全信息的缓存,从而优化性能。
- 当用户退出时,删除为用户安全会话而保存的服务器端对象。
- 与大量后端数据存储服务(如目录服务或关系数据库)进行通信,这些服务用于存储用户的安全信息和 ECM 的访问控制策略。
正如这个列表显示的那样,Acegi 的安全过滤器允许您执行保护企业应用程序所需的几乎任何事情。
?
?
[基础工作]?
在你的Web应用的lib中添加Acegi下载包中的acegi-security.jar
?
[web.xml]
在web.xml配置
Xml代码??
<filter>??????????<filter-name>Acegi?Filter?Chain?Proxy</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>???
<filter-mapping>限定了FilterToBeanProxy?的URL匹配模式?,
Xml代码??<filter-mapping>??????????<filter-name>Acegi?Filter?Chain?Proxy</filter-name>??????????<url-pattern>/*</url-pattern>??????</filter-mapping>???
<listener>的HttpSessionEventPublisher?用于发布HttpSessionApplicationEvents?和HttpSessionDestroyedEvent?事件给spring的applicationcontext?。
?
Xml代码??<listener>??????????<listener-class>??????????????org.acegisecurity.ui.session.HttpSessionEventPublisher??????????</listener-class>??????</listener>???
[applicationContext-acegi-security.xml]
?
?applicationContext-acegi-security.xml文件配置
?
FilterChainProxy?会按顺序来调用这些filter,使这些filter能享用Spring ioc的功能?, CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON定义了url比较前先转为小写, PATTERN_TYPE_APACHE_ANT定义了使用Apache ant的匹配模式
Xml代码??<bean?id="filterChainProxy"?class="org.acegisecurity.util.FilterChainProxy">???????<property?name="filterInvocationDefinitionSource">??????????<value>??????????CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON??????????PATTERN_TYPE_APACHE_ANT?????????????/**=httpSessionContextIntegrationFilter,?logoutFilter,?authenticationProcessingFilter,????????????????basicProcessingFilter,rememberMeProcessingFilter,anonymousProcessingFilter,??????????????switchUserProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor??????????</value>???????</property>?????</bean>???
定义数据源为调用tomcat容器数据源 登入验证时需要获取数据源连接数据库
Xml代码??<bean?id="dataSource"?class="org.springframework.jndi.JndiObjectFactoryBean">??????<property?name="jndiName"><value>java:/comp/env/jdbc/test</value></property>??</bean>???
认证管理 ,从数据库中读取用户信息验证身份
Xml代码??<bean?id="authenticationManager"?class="org.acegisecurity.providers.ProviderManager">?????<property?name="providers">????????<list>???????????<ref?local="daoAuthenticationProvider"/>????????</list>?????</property>??</bean>???
?
?
?
daoAuthenticationProvider?
进行简单的基于数据库的身份验证。DaoAuthenticationProvider?获取数据库中的账号密码并进行匹配,若成功则在通过用户身份的同时返回一个包含授权信息的Authentication对象?,否则身份验证失败,抛出一个AuthenticatiionException?。
Xml代码???<bean?id="daoAuthenticationProvider"?class="org.acegisecurity.providers.dao.DaoAuthenticationProvider">????????<property?name="userDetailsService"><ref?local="jdbcDaoImpl"/></property>????????<property?name="passwordEncoder"><ref?local="passwordEncoder"/></property>????????<property?name="userCache"><ref?local="userCache"/></property>???</bean>???
jdbcDaoImpl
用于在数据中获取用户信息
Xml代码??<bean?id="jdbcDaoImpl"?class="com.milesup.acegi.userdetails.jdbc.JdbcDaoImpl">????????<property?name="dataSource"><ref?bean="dataSource"/></property>????????<property?name="rolePrefix"><value>ROLE_</value></property>?????</bean>???
passwordEncoder
? 使用加密器对用户输入的明文进行加密。Acegi提供了三种加密器:
Xml代码??1?:???PlaintextPasswordEncoder—默认,不加密,返回明文.??2?:???ShaPasswordEncoder—哈希算法(SHA)加密??3?:???Md5PasswordEncoder—消息摘要(MD5)加密???使用加密器对用户输入的明文进行加密 为MD5加密方式
Xml代码??<bean?id="passwordEncoder"?class="org.acegisecurity.providers.encoding.Md5PasswordEncoder"/>???
缓存用户和资源相对应的权限信息。每当请求一个受保护资源时,daoAuthenticationProvider?就会被调用以获取用户授权信息?。如果每次都从数据库获取的话,那代价很高,对于不常改变的用户和资源信息来说,最好是把相关授权信息缓存起来。
userCache提供了两种实现:?NullUserCache?和EhCacheBasedUserCache?,?NullUserCache?实际上就是不进行任何缓存,EhCacheBasedUserCache?是使用Ehcache来实现缓功能。
Xml代码??<!--?缓存管理?-->?????<bean?id="cacheManager"?class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"/>??????<!--?缓存用户和资源相对应的权限信息?-->?????<bean?id="userCacheBackend"?class="org.springframework.cache.ehcache.EhCacheFactoryBean">????????<property?name="cacheManager">???????????<ref?local="cacheManager"/>????????</property>????????<property?name="cacheName">???????????<value>userCache</value>????????</property>?????</bean>??????????<bean?id="userCache"?class="org.acegisecurity.providers.dao.cache.EhCacheBasedUserCache">????????<property?name="cache"><ref?local="userCacheBackend"/></property>?????</bean>???
该过滤器用来处理在系统认证授权过程中抛出的异常
Xml代码??<bean?id="exceptionTranslationFilter"?class="org.acegisecurity.ui.ExceptionTranslationFilter">???????<property?name="authenticationEntryPoint"><ref?local="authenticationProcessingFilterEntryPoint"/></property>????</bean>???
?
一个没有进行身份验证的用户试图访问受保护的资源验证是否授权? Exception Translation Filter(ETF)
Xml代码??<bean?id="authenticationProcessingFilterEntryPoint"?class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint">????????<property?name="loginFormUrl"><value>/login.jsp</value></property>????????<property?name="forceHttps"><value>false</value></property>?????</bean>???
登入验证
成功进入main.jsp页面? ,失败跳转到/login.jsp?login_error=1? ,登入url为 /j_acegi_security_check, Authentication Processing Filter(APF)
??????? authenticationFailureUrl?定义登陆失败时转向的页面
?????????defaultTargetUrl?定义登陆成功时转向的页面
?????????filterProcessesUrl?定义登陆请求的页面
?????????rememberMeServices?用于在验证成功后添加cookie信息
Xml代码??<bean?id="authenticationProcessingFilter"?class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter">????????<property?name="authenticationManager"><ref?bean="authenticationManager"/></property>????????<property?name="authenticationFailureUrl"><value>/login.jsp?login_error=1</value></property>????????<property?name="defaultTargetUrl"><value>/main.jsp</value></property>????????<property?name="filterProcessesUrl"><value>/j_acegi_security_check</value></property>???????<property?name="rememberMeServices"><ref?local="rememberMeServices"/></property>???</bean>???
?
Xml代码??用于处理HTTP头的认证信息,如从<span?style="color:?#0000ff;"><strong>Spring远程协议</strong>??</span>??(如Hessian和Burlap)或<span?style="color:?#0000ff;"><strong>普通的浏览器如IE,Navigator的HTTP头</strong>??</span>??中获取用户??信息,将他们转交给通过<span?style="text-decoration:?underline;">authenticationManager</span>??属性装配的认证管理器。如果认证成功,会<span?style="text-decoration:?underline;">将一个Authentication对象放到会话中</span>????,否则,如果认证失败,会将控制<span?style="text-decoration:?underline;">转交给认证入口点</span>??(通过authenticationEntryPoint属性装配)??<!--?用于处理HTTP头的认证信息?-->?????<bean?id="basicProcessingFilter"?class="org.acegisecurity.ui.basicauth.BasicProcessingFilter">????????<property?name="authenticationManager"><ref?local="authenticationManager"/></property>????????<property?name="authenticationEntryPoint"><ref?local="basicProcessingFilterEntryPoint"/></property>?????</bean>??????????<!--?通过向浏览器发送一个HTTP401(未授权)消息,提示用户登录?-->?????<bean?id="basicProcessingFilterEntryPoint"?class="org.acegisecurity.ui.basicauth.BasicProcessingFilterEntryPoint">????????<property?name="realmName"><value>Milesup?Realm</value></property>?????</bean>???注销 退出验证 跳转到/login.jsp
Xml代码??<bean?id="logoutFilter"?class="org.acegisecurity.ui.logout.LogoutFilter">????????<constructor-arg?value="/login.jsp"/><constructor-arg>???????????<list>????????????????<bean?class="org.acegisecurity.ui.logout.SecurityContextLogoutHandler"/>???????????</list>????????</constructor-arg>?????</bean>???
经过投票?机制来决定是否可以访问某一资源(URL?或方法?)。allowIfAllAbstainDecisions为false时如果有一个或以上的decisionVoters投票通过,则授权通过。可选的决策机制有ConsensusBased和UnanimousBased
?
roleVoter?
必须是以rolePrefix设定的value开头的权限才能进行投票,如 ROLE_
Xml代码??<bean?id="roleVoter"?class="org.acegisecurity.vote.RoleVoter">????????<property?name="rolePrefix">???????????<value>ROLE_</value>????????</property>?????</bean>??????<!--?组件管理授权过程??决策管理器-->?????<bean?id="httpRequestAccessDecisionManager"?class="org.acegisecurity.vote.AffirmativeBased">????????<property?name="allowIfAllAbstainDecisions"><value>false</value></property>????????<property?name="decisionVoters">???????????<list>??????????????<ref?bean="roleVoter"/>???????????</list>????????</property>?????</bean>????
过滤器安全拦截器? 是否认证,是否有权限访问受保护的资源
在执行转向url前检查objectDefinitionSource?中设定的用户权限信息。首先,objectDefinitionSource?中定义了访问URL需要的属性信息(这里的属性信息仅仅是标志,告诉accessDecisionManager?要用哪些voter来投票)。然后,authenticationManager?掉用自己的provider来对用户的认证信息进行校验。最后,有投票者根据用户持有认证和访问url需要的属性,调用自己的voter来投票,决定是否允许访问。
Xml代码??<bean?id="filterInvocationInterceptor"?class="org.acegisecurity.intercept.web.FilterSecurityInterceptor">????????<property?name="authenticationManager"><ref?bean="authenticationManager"/></property>????????<property?name="accessDecisionManager"><ref?local="httpRequestAccessDecisionManager"/></property>????????<property?name="objectDefinitionSource">???????????<value>??????????????PATTERN_TYPE_APACHE_ANT??????????????/user.jsp=ROLE_ADMIN??????????????/admin=ROLE_ADMIN???????????</value>????????</property>?????</bean>????
[login.jsp]
?
Html代码??<form?action="/j_acegi_security_check"?method="post"?>??????<table>????????????????????<tr>??????????????????????<td?width="140"?height="22"?align="right">用户名</td>??????????????????????<td?width="47%"?align="left"><input?value="${lastUserName}"?name="j_username"?type="text"?class="inputstyle"?id="j_username"></td>??????????????????????<td?align="left"?nowrap><div?class="loginmeon">?</div></td>????????????????????</tr>????????????????????<tr>??????????????????????<td?width="140"?height="22"?align="right">密 码</td>??????????????????????<td?width="47%"?align="left"><input?name="j_password"?type="password"?class="inputstyle"?id="j_password"?size="21"></td>????????????????????</tr>???<tr>??????????????????????<td?height="60"?colspan="3"?align="center"?valign="middle">???????????????????<input?type="submit"?name="imageField2"?value="登入"></td>????????????????????</tr>??????????????????</table>??????????????????</td>????????????????</tr>?????????????</table>???????????</td>????????</tr>??????</table>???</form>??????[JdbcDaoImpl.java]
?
Java代码??public?class?JdbcDaoImpl?extends?org.acegisecurity.userdetails.jdbc.JdbcDaoImpl?{????????private?String?anonymousRoleName?=?"ROLE_ANONYMOUS";????????private?Log?logger?=?LogFactory.getLog(JdbcDaoImpl.class);????????private?PreparedStatement?userPstmt;????????private?PreparedStatement?rolePstmt;????????public?UserDetails?loadUserByUsername(String?userName)??????????????throws?UsernameNotFoundException,?DataAccessException?{??????????UserDetails?user?=?findUserByName(userName);????????????if?(user?==?null)?{??????????????throw?new?UsernameNotFoundException("User?not?found");??????????}????????????return?user;??????}????????private?UserDetails?findUserByName(String?userName)?{??????????Connection?connection?=?null;??????????ResultSet?rsUser?=?null;??????????ResultSet?rsRole?=?null;??????????UserDetails?user?=?null;??????????String?logonName?=?null;??????????String?password?=?null;??????????String?roleName?=?null;??????????int?status?=?-1;??????????boolean?enabled?=?false;??????????Vector?roles?=?null;??????????GrantedAuthority[]?rolesArray?=?null;????????????try?{??????????????connection?=?getDataSource().getConnection();??????????????userPstmt?=?connection??????????????????????.prepareStatement("select?*?from?users?where?user_NAME=?");??????????????userPstmt.setString(1,?userName);??????????????rsUser?=?userPstmt.executeQuery();??????????????if?(rsUser.next())?{??????????????????logonName?=?rsUser.getString("USER_NAME");??????????????????password?=?rsUser.getString("PASSWORD");??????????????????status?=?rsUser.getInt("STATUS");??????????????????if?(status?==?1)??????????????????????enabled?=?true;??????????????}?else?{??????????????????return?null;??????????????}??????????????rolePstmt?=?connection??????????????????????.prepareStatement("SELECT?ROLE.NAME?Role?FROM?ROLE,??users_ROLES,??users?WHERE?ROLE.ID=?user_ROLES.FK_ROLES??and?users.user_NAME=?");??????????????rolePstmt.setString(1,?userName);??????????????rsRole?=?rolePstmt.executeQuery();??????????????roles?=?new?Vector();??????????????while?(rsRole.next())?{??????????????????roleName?=?getRolePrefix()?+?rsRole.getString("Role");??????????????????roles.add(new?GrantedAuthorityImpl(roleName));??????????????}??????????????rolesArray?=?new?GrantedAuthority[roles.size()?+?1];??????????????int?index?=?0;??????????????for?(index?=?0;?index?<?roles.size();?index++)??????????????????rolesArray[index]?=?(GrantedAuthority)?roles.get(index);??????????????rolesArray[index]?=?new?GrantedAuthorityImpl(anonymousRoleName);??????????????user?=?new?User(logonName,?password,?enabled,?true,?true,?true,??????????????????????rolesArray);??????????}?catch?(SQLException?e)?{??????????????logger.fatal("",?e);??????????}?finally?{??????????????try?{??????????????????rsRole.close();??????????????????rsUser.close();??????????????????userPstmt.close();??????????????????rolePstmt.close();??????????????????connection.close();??????????????}?catch?(SQLException?sqlx)?{??????????????????logger.fatal("",?sqlx);??????????????}?catch?(NullPointerException?x)?{??????????????????logger.fatal("",?x);??????????????}??????????}??????????return?user;??????}????}???