安全永遠(yuǎn)是WEB應(yīng)用系統(tǒng)必須面對(duì)的頭等大事, 也是最頭疼的事, 其實(shí)安全系統(tǒng)就只包括兩個(gè)問(wèn)題: 認(rèn)證和授權(quán) 那么, 能不能夠在進(jìn)入方法前就調(diào)用一些安全檢測(cè)? 其實(shí)Spring AOP就是這個(gè)思想, 那么又如何實(shí)現(xiàn)安全檢測(cè)呢? Spring Acegi Security 框架就是做這個(gè)事情. 本文主要是討論下在已有的SSH系統(tǒng)中, 如何使用Acegi作為安全框架實(shí)現(xiàn)基于角色的權(quán)限控制(Role Based Access Control RBAC) 本文的主要參考資料: <Spring 2.0 核心技術(shù)與最佳實(shí)踐> 第10章 (Spring Acegi 安全框架) <精通Spring 2.X -- 企業(yè)應(yīng)用開發(fā)詳解> 第17章 (使用Acegi 實(shí)施應(yīng)用系統(tǒng)安全) acegi-security-1.0.6 官方文檔 說(shuō)明: 本文介紹的是RBAC, 在官方文檔的基礎(chǔ)上有所擴(kuò)展或改動(dòng), 以更適合WEB應(yīng)用系統(tǒng). 其實(shí)我覺(jué)得大多數(shù)的網(wǎng)站基于角色已經(jīng)足夠了, 一般都沒(méi)必要基于權(quán)限. 文章開始: 一. 下載所要的軟件或JAR包: 我的相關(guān)配置是: Java 5, Tomcat 5.5.26, Struts 2.0.11, Spring 2.5.1, Hibernate 3.2, Acegi 1.0.6 二. 建立相關(guān)的數(shù)據(jù)庫(kù): 數(shù)據(jù)表: 用戶信息表User: id, enable, user_name, user_pass, email_box 角色信息表RoleInfo: id, role_name, role_title, descp 用戶與角色關(guān)聯(lián)表(用戶與角色是多對(duì)多關(guān)系)UserRole: user_id, user_name, role_id, role_name 并在這三個(gè)表中插入相關(guān)的數(shù)據(jù), 我是定義了兩種角色(role_name): ROLE_USER, ROLE_ADMIN 和三個(gè)用戶, 一個(gè)用戶角色為: ROLE_USER, ROLE_ADMIN 另一個(gè)用戶角色為: ROLE_USER 第三個(gè)沒(méi)有角色. 二. 修改配置文件: 其實(shí)對(duì)Acegi框架的應(yīng)用難點(diǎn)就在配置文件, 所以要特別注意了: 在 src 建立Acegi的配置文件: acegi-security.xml acegi-security.xml 其內(nèi)容如下: <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www./schema/beans xsi:schemaLocation="http://www./schema/beans <!-- ================= <bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager"> <property name="providers"> <list> <ref bean="daoAuthenticationProvider" /> <ref bean="rememberMeAuthenticationProvider" /> </list> </property> </bean> <!-- 基于DAO驗(yàn)證的AuthenticationProvider --> <bean id="daoAuthenticationProvider" class="org.acegisecurity.providers.dao.DaoAuthenticationProvider"> <property name="userDetailsService" ref="userDetailsService" /> </bean> <bean id="userDetailsService" class="org.ymcn.security.AcegiUserDeitailsService"> <property name="userDao" ref="userDao" /> <property name="userRoleDao" ref="userRoleDao" /> </bean> <bean id="rememberMeAuthenticationProvider" class="org.acegisecurity.providers.rememberme.RememberMeAuthenticationProvider"> <property name="key" value="obullxl@163.com </bean> <bean id="rememberMeServices" class="org.acegisecurity.ui.rememberme.TokenBasedRememberMeServices"> <property name="userDetailsService" ref="userDetailsService" /> <property name="parameter" value="j_remember_me <property name="key" value="obullxl@163.com <property name="tokenValiditySeconds" value="31536000" /> </bean> <!-- ================= <bean id="accessDecisionManager" class="org.acegisecurity.vote.AffirmativeBased"> <property name="decisionVoters"> <list> <ref bean="roleVoter" /> </list> </property> <!-- 是否全部棄權(quán)就通過(guò) --> <property name="allowIfAllAbstainDecisions" value="false" /> </bean> <bean id="roleVoter" class="org.acegisecurity.vote.RoleVoter"> <property name="rolePrefix" value="ROLE_" /> </bean> <!-- ================= <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,rememberMeFilter,exceptionFilter,securityInterceptor </value> </property> </bean> <bean id="httpSessionContextIntegrationFilter" class="org.acegisecurity.context.HttpSessionContextIntegrationFilter" /> <bean id="logoutFilter" class="org.acegisecurity.ui.logout.LogoutFilter"> <!-- 登錄退出后的URL --> <constructor-arg value="/" /> <list> <ref bean="rememberMeServices" /> <bean class="org.acegisecurity.ui.logout.SecurityContextLogoutHandler" /> </list> </constructor-arg> <!-- 登錄退出的URL --> <property name="filterProcessesUrl" value="/j_logout.j" /> <bean id="authenticationProcessingFilter" class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter"> <property name="authenticationManager" ref="authenticationManager" /> <!-- 登錄失敗后的URL --> <property name="authenticationFailureUrl" value="/login.jsp?msg=%E6%97%A0%E6%95%88%E7%9A%84%E7%94%A8%E6%88%B7%E5%90%8D%E6%88%96%E5%8F%A3%E4%BB%A4" /> <!-- 登錄成功后的URL --> <property name="defaultTargetUrl" value="/user/cmd.jsp" /> <!-- 登錄的URL --> <property name="filterProcessesUrl" value="/j_login.j" /> <property name="rememberMeServices" ref="rememberMeServices" /> </bean> <bean id="rememberMeFilter" class="org.acegisecurity.ui.rememberme.RememberMeProcessingFilter"> <property name="authenticationManager" ref="authenticationManager" /> <property name="rememberMeServices" ref="rememberMeServices" /> </bean> <bean id="exceptionFilter" class="org.acegisecurity.ui.ExceptionTranslationFilter"> <!-- 出現(xiàn)AuthenticationException時(shí)的登錄入口 --> <property name="authenticationEntryPoint"> <bean class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint"> <property name="loginFormUrl" value="/login.jsp" /> <property name="forceHttps" value="false" /> </bean> </property> <!-- 出現(xiàn)AccessDeniedException時(shí)的Handler --> <property name="accessDeniedHandler"> <bean class="org.acegisecurity.ui.AccessDeniedHandlerImpl"> <property name="errorPage" value="/denied.jsp" /> </bean> </property> </bean> <bean id="securityInterceptor" class="org.acegisecurity.intercept.web.FilterSecurityInterceptor"> <property name="authenticationManager" ref="authenticationManager" /> <property name="accessDecisionManager" ref="accessDecisionManager" /> <property name="objectDefinitionSource"> <value> CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON PATTERN_TYPE_APACHE_ANT /admin/**=ROLE_ADMIN /user/**=ROLE_USER /cart/previeworder*=ROLE_USER </value> </property> </bean> </beans> 在上面的配置文件中, 紅色 <bean id="userDetailsService" class="org.ymcn.security.AcegiUserDeitailsService <property name="userDao" ref="userDao" /> <property name="userRoleDao" ref="userRoleDao" /> </bean> 在整個(gè)應(yīng)用的安全控制中, 我們唯一要編寫代碼的類就是: org.ymcn.security.AcegiUserDeitailsService 就連登錄和登出的代碼也不要了. ![]() 三. 修改 web.xml <filter> <filter-name>acegiFilterChain</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> <filter-name>acegiFilterChain</filter-name> <url-pattern>*.j</url-pattern> </filter-mapping> 注意: 四. 在 applicationContext.xml <!-- Acegi安全控制攔截器 --> <bean id="serviceSecurityInterceptor" class="org.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor"> <property name="validateConfigAttributes" value="true" /> <property name="authenticationManager" ref="authenticationManager" /> <property name="accessDecisionManager" ref="accessDecisionManager" /> <property name="objectDefinitionSource"> <bean class="org.acegisecurity.intercept.method.MethodDefinitionAttributes"> <property name="attributes"> <bean class="org.acegisecurity.annotation.SecurityAnnotationAttributes" /> </property> </bean> </property> </bean> <!-- 利用Spring的自動(dòng)代理功能實(shí)現(xiàn)AOP代理 --> <bean id="autoProxyCreator" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"> <property name="interceptorNames"> <list> <value>transactionInterceptor</value> <value>serviceSecurityInterceptor</value> </list> </property> <property name="beanNames"> <list> <value>userService</value> <value>mailService</value> </list> </property> </bean> 五. 編寫在利用Acegi框架唯一要我們編寫的類 AcegiUserDeitailsService.java package org.ymcn.security; import java.util.List; import org.acegisecurity.GrantedAuthority; import org.acegisecurity.GrantedAuthorityImpl; import org.acegisecurity.userdetails.UserDetails; import org.acegisecurity.userdetails.UserDetailsService; import org.acegisecurity.userdetails.UsernameNotFoundException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.dao.DataAccessException; import org.ymcn.dao.UserDao; import org.ymcn.dao.UserRoleDao; import org.ymcn.model.User; import org.ymcn.model.UserRole; public class AcegiUserDeitailsService implements UserDetailsService { private final Log LOG = LogFactory.getLog(AcegiUserDeitailsService.class); /* 依賴注入 */ private UserDao userDao; private UserRoleDao userRoleDao; public void setUserDao(UserDao userDao) { this.userDao = userDao; } public void setUserRoleDao(UserRoleDao userRoleDao) { this.userRoleDao = userRoleDao; } /* 用戶所有的權(quán)限 */ //private final List<GrantedAuthority> grantedAuthList = new ArrayList<GrantedAuthority>(6); private GrantedAuthority[] grantedAuthArray; public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException, DataAccessException { if(LOG.isDebugEnabled()) { LOG.debug("Loading UserDetails of userName: " + userName); } /* 取得用戶 */ User user = userDao.getUserByName(userName); if(user == null) { LOG.warn("UserDetails load failed: No such UserRole with userName: " + userName); throw new UsernameNotFoundException("User name is not found."); } /* 取得所有用戶權(quán)限 */ List<UserRole> userRoleList = userRoleDao.getUserRoleByUserName(userName); if(userRoleList == null || userRoleList.size() == 0) { LOG.warn("UserRole load failed: No such UserRole with userName: " + userName); throw new UsernameNotFoundException("UserRole is not found."); } /* 取得用戶的所有角色 */ int size = userRoleList.size(); grantedAuthArray = new GrantedAuthority[size]; int j = 0; for(int i = 0; i < size; i++) { UserRole userRole = userRoleList.get(i); if(userRole != null) { this.grantedAuthArray[j++] = new GrantedAuthorityImpl(userRole.getRoleName().toUpperCase()); } } LOG.info("UserName: " + userName + " loaded successfully."); return new org.acegisecurity.userdetails.User(userName, user.getUserPass(), true, true, true, true, this.grantedAuthArray); } } 六. 在業(yè)務(wù)邏輯代碼中利用Java 5注釋 @Secured({"ROLE_USER"}) void sendSimpleMail(Long userId); @Secured({"ROLE_ADMIN"}) void sendAttachmentMail() throws Exception; 其實(shí)就是在需要安全控制的方法前加上: @Secured({"角色名"}) 七. 整個(gè)工作完成 Acegi框架完全是一種可插拔式的, 完全可以在原有的系統(tǒng)中加個(gè)一個(gè)配置文件 上面的 AcegiUserDeitailsService.java true ![]() |
|