posts - 13,comments - 19,trackbacks - 0
          安全永遠(yuǎn)是WEB應(yīng)用系統(tǒng)必須面對的頭等大事, 也是最頭疼的事, 其實(shí)安全系統(tǒng)就只包括兩個(gè)問題: 認(rèn)證和授權(quán).    以前做些網(wǎng)站系統(tǒng), 安全檢測邏輯都在放在須要安全控制的代碼前面, 這樣做有很多不好的地方, 重復(fù)多次的編碼就不用說了, 代碼移植性, 重用性都得不到體現(xiàn), 安全檢測邏輯要永遠(yuǎn)和業(yè)務(wù)邏輯放在一起.
              那么, 能不能夠在進(jìn)入方法前就調(diào)用一些安全檢測? 其實(shí)Spring AOP就是這個(gè)思想, 那么又如何實(shí)現(xiàn)安全檢測呢? Spring Acegi Security 框架就是做這個(gè)事情.
               本文主要是討論下在已有的SSH系統(tǒng)中, 如何使用Acegi作為安全框架實(shí)現(xiàn)基于角色的權(quán)限控制(Role Based Access Control RBAC) , 本文主要是以Java 5注解的形式來配置安全框架, 大大減化配置和操作.
              本文的主要參考資料: <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 官方文檔
              說明: 本文介紹的是RBAC, 在官方文檔的基礎(chǔ)上有所擴(kuò)展或改動(dòng), 以更適合WEB應(yīng)用系統(tǒng). 其實(shí)我覺得大多數(shù)的網(wǎng)站基于角色已經(jīng)足夠了, 一般都沒必要基于權(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ù)庫:
               數(shù)據(jù)表: 用戶信息表User: id, enable, user_name, user_pass, email_box
                           角色信息表RoleInfo: id, role_name, role_title, descp
                           用戶與角色關(guān)聯(lián)表(用戶與角色是多對多關(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è)沒有角色.
          二. 修改配置文件:
              其實(shí)對Acegi框架的應(yīng)用難點(diǎn)就在配置文件, 所以要特別注意了:
              在 src 建立Acegi的配置文件: acegi-security.xml 當(dāng)然這個(gè)文件的名稱是可以任意的.
              acegi-security.xml 說白了就是配置: 安全攔截器, 認(rèn)證管理器, 決策管理器.
              其內(nèi)容如下:
          <?xml version="1.0" encoding="UTF-8"?>
          <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
          <!-- ========================= 認(rèn)證管理器 ========================= -->
          <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)就通過 -->
          <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="/" />
          <constructor-arg>
             <list>
              <ref bean="rememberMeServices" />
              <bean class="org.acegisecurity.ui.logout.SecurityContextLogoutHandler" />
             </list>
          </constructor-arg>
          <!-- 登錄退出的URL -->
          <property name="filterProcessesUrl" value="/j_logout.j" />
          </bean>
          <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>
              在上面的配置文件中, 紅色部分要特別注意, 其余的內(nèi)容都差不多了.
          <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>

              注意: 這個(gè)過濾器一定要在MVC轉(zhuǎn)發(fā)過濾器的前面!!!!
          四. 在 applicationContext.xml 中增加 Acegi安全控制攔截器Spring的自動(dòng)代理功能實(shí)現(xiàn)AOP代理
          <!-- 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注釋實(shí)現(xiàn)安全控制
          @Secured({"ROLE_USER"})
          void sendSimpleMail(Long userId);

          @Secured({"ROLE_ADMIN"})
          void sendAttachmentMail() throws Exception;

              其實(shí)就是在需要安全控制的方法前加上: @Secured({"角色名"}), 非常的簡單
          七. 整個(gè)工作完成
              Acegi框架完全是一種可插拔式的, 完全可以在原有的系統(tǒng)中加個(gè)一個(gè)配置文件, 和在每個(gè)方法前加上: @Secured({"角色名"}) 就可完成.
               上面的 AcegiUserDeitailsService.java 中的有 UserDao, UserRoleDao, 我想一看就知道它們是干什么的了, 這完全取決于個(gè)人的實(shí)現(xiàn), 與Acegi無關(guān), 它僅僅只要返回一個(gè) return new org.acegisecurity.userdetails.User(userName, user.getUserPass(),
                    true, true, true, true, this.grantedAuthArray) 就可以了.
          posted on 2008-12-11 18:51 南山隱士 閱讀(689) 評論(0)  編輯  收藏

          只有注冊用戶登錄后才能發(fā)表評論。


          網(wǎng)站導(dǎo)航:
           
          主站蜘蛛池模板: 永春县| 定南县| 隆回县| 锦州市| 武功县| 雷波县| 土默特左旗| 绥棱县| 灌阳县| 丰镇市| 永修县| 新丰县| 孝昌县| 高尔夫| 西城区| 绍兴市| 无锡市| 嘉义市| 芷江| 瓮安县| 靖边县| 新龙县| 北碚区| 白城市| 苗栗市| 昌邑市| 伊金霍洛旗| 云龙县| 尉氏县| 沙雅县| 沈丘县| 新建县| 昭觉县| 沽源县| 上蔡县| 桃江县| 榆林市| 南木林县| 农安县| 灌阳县| 陆河县|