spring security之用戶 權限 url存儲在數據庫


本文項目參照http://www.blogjava.net/SpartaYew/archive/2011/05/19/SpingSecurity3.html的第三種方法實現,並簡寫博客中一部分內容。

本文項目采用spring security3.1 + Mybatis3.2 + oracle10

Spring Security3提供了靈活的擴展方法。具體應該擴展哪些類呢? 或者到底Spring Security3工作的流程如何,你不妨參看下面一篇文章,就會獲得
一些啟示,網址為:http://www.blogjava.net/youxia/archive/2008/12/07/244883.html , 哈哈,謝謝分享。

    還有一個地址很有價值, http://wenku.baidu.com/view/4ec7e324ccbff121dd368364.html ,我就參考着上面的介紹擴展了4個類。

    不過我得提一下,原文的作者為了考驗你的耐性和自信心,故意在代碼里面賣了幾點小小的關子,因此若是完全按照作者的原文代碼裝配起來的權限系統,是不會那么順利地工作的,天下似乎真是沒有不花費力氣的午餐!在裝配完成后,我也是經過九九八十一難的折磨,在用戶、角色、權限、資源的
“天下黃河九曲十八彎”里面盤旋迂回,終於到達了成功的彼岸。至此才對Spring Security有了更深層次的理解,更加佩服作者的良苦用心。 哈哈。

     並擴展了User類以增加其相關的各類其他信息(如Email,職務,所在單位id等)。


相關的代碼如下(包含5個關鍵類)(修改過作者原先代碼):

package com.springmvc.security;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

import org.springframework.security.access.SecurityMetadataSource;
import org.springframework.security.access.intercept.AbstractSecurityInterceptor;
import org.springframework.security.access.intercept.InterceptorStatusToken;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;

public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor
        implements Filter {

    private FilterInvocationSecurityMetadataSource securityMetadataSource;

    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {

        FilterInvocation fi = new FilterInvocation(request, response, chain);
        invoke(fi);

    }

    public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() {
        return this.securityMetadataSource;
    }

    public Class<? extends Object> getSecureObjectClass() {
        return FilterInvocation.class;
    }

    public void invoke(FilterInvocation fi) throws IOException,
            ServletException {

        InterceptorStatusToken token = super.beforeInvocation(fi);

        try {
            fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
        } finally {
            super.afterInvocation(token, null);
        }

    }

    @Override
    public SecurityMetadataSource obtainSecurityMetadataSource() {
        return this.securityMetadataSource;
    }

    public void setSecurityMetadataSource(
            FilterInvocationSecurityMetadataSource securityMetadataSource) {
        this.securityMetadataSource = securityMetadataSource;
    }

    public void destroy() {

    }

    public void init(FilterConfig filterconfig) throws ServletException {

    }

}
package com.springmvc.security;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.annotation.Resource;

import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.stereotype.Service;

import com.springmvc.dao.TestDao;

public class MyInvocationSecurityMetadataSourceService implements
        FilterInvocationSecurityMetadataSource {

//    @Resource(name = "testDao")
    private TestDao testDao;

    private static Map<String, Collection<ConfigAttribute>> resourceMap = null;

    public MyInvocationSecurityMetadataSourceService(TestDao testDao) {
        //使用注解方式的話,只能在構造函數執行完成后才能獲得實例
        this.testDao = testDao;
        loadResourceDefine();
    }

     // 在Web服務器啟動時,提取系統中的所有權限
    private void loadResourceDefine() {
        List<String> query = this.testDao.getAllAuthorityName();

        /*
         * 應當是資源為key, 權限為value。 資源通常為url, 權限就是那些以ROLE_為前綴的角色。 一個資源可以由多個權限來訪問。
         * sparta
         */
        resourceMap = new HashMap<String, Collection<ConfigAttribute>>();

        for (String auth : query) {
            ConfigAttribute ca = new SecurityConfig(auth);

            List<String> query1 = this.testDao.getResource(auth);

            for (String res : query1) {
                String url = res;

                /*
                 * 判斷資源文件和權限的對應關系,如果已經存在相關的資源url,則要通過該url為key提取出權限集合,將權限增加到權限集合中。
                 * sparta
                 */
                if (resourceMap.containsKey(url)) {
                    Collection<ConfigAttribute> value = resourceMap.get(url);
                    value.add(ca);
                    resourceMap.put(url, value);
                } else {
                    Collection<ConfigAttribute> atts = new ArrayList<ConfigAttribute>();
                    atts.add(ca);
                    resourceMap.put(url, atts);
                }
            }

        }

    }

    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {

        return null;
    }

    // 根據URL,找到相關的權限配置。
    @Override
    public Collection<ConfigAttribute> getAttributes(Object object)
            throws IllegalArgumentException {

        // object 是一個URL,被用戶請求的url。
        String url = ((FilterInvocation) object).getRequestUrl();
        System.out.println("url" + url);
        int firstQuestionMarkIndex = url.indexOf("?");

        if (firstQuestionMarkIndex != -1) {
            url = url.substring(0, firstQuestionMarkIndex);
        }

        Iterator<String> ite = resourceMap.keySet().iterator();

        while (ite.hasNext()) {
            String resURL = ite.next();

            if (url.equals(resURL)) {

                return resourceMap.get(resURL);
            }
        }

        return null;
    }

    @Override
    public boolean supports(Class<?> arg0) {

        return true;
    }

}
package com.springmvc.security;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import javax.annotation.Resource;

import org.springframework.dao.DataAccessException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

import com.springmvc.dao.TestDao;

public class MyUserDetailsService implements UserDetailsService {
    
    @Resource(name="testDao")
    private TestDao testDao;
    
     @Override
     public UserDetails loadUserByUsername(String username)
       throws UsernameNotFoundException, DataAccessException {
      System.out.println("username" + username);
      List<GrantedAuthority> auths = new ArrayList<GrantedAuthority>();
      List<String> authorityName = this.testDao.getAuthorityName(username);
      for(String roleName : authorityName) {
          System.out.println(roleName);
          SimpleGrantedAuthority authority = new SimpleGrantedAuthority(roleName);
          auths.add(authority);
      }
      String pwd = this.testDao.getPWD(username);
      return new User(username,pwd,true,true,true,true,auths);
    
     }
     
}
package com.springmvc.security;

import java.util.Collection;
import java.util.Iterator;

import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;

/**
 * AccessdecisionManager在Spring security中是很重要的。
 * 
 * 在驗證部分簡略提過了,所有的Authentication實現需要保存在一個GrantedAuthority對象數組中。 這就是賦予給主體的權限。
 * GrantedAuthority對象通過AuthenticationManager 保存到
 * Authentication對象里,然后從AccessDecisionManager讀出來,進行授權判斷。
 * 
 * Spring Security提供了一些攔截器,來控制對安全對象的訪問權限,例如方法調用或web請求。
 * 一個是否允許執行調用的預調用決定,是由AccessDecisionManager實現的。 這個 AccessDecisionManager
 * 被AbstractSecurityInterceptor調用, 它用來作最終訪問控制的決定。
 * 這個AccessDecisionManager接口包含三個方法:
 * 
 * void decide(Authentication authentication, Object secureObject,
 * List<ConfigAttributeDefinition> config) throws AccessDeniedException; boolean
 * supports(ConfigAttribute attribute); boolean supports(Class clazz);
 * 
 * 從第一個方法可以看出來,AccessDecisionManager使用方法參數傳遞所有信息,這好像在認證評估時進行決定。
 * 特別是,在真實的安全方法期望調用的時候,傳遞安全Object啟用那些參數。 比如,讓我們假設安全對象是一個MethodInvocation。
 * 很容易為任何Customer參數查詢MethodInvocation,
 * 然后在AccessDecisionManager里實現一些有序的安全邏輯,來確認主體是否允許在那個客戶上操作。
 * 如果訪問被拒絕,實現將拋出一個AccessDeniedException異常。
 * 
 * 這個 supports(ConfigAttribute) 方法在啟動的時候被
 * AbstractSecurityInterceptor調用,來決定AccessDecisionManager
 * 是否可以執行傳遞ConfigAttribute。 supports(Class)方法被安全攔截器實現調用,
 * 包含安全攔截器將顯示的AccessDecisionManager支持安全對象的類型。
 */
public class MyAccessDecisionManager implements AccessDecisionManager {

    public void decide(Authentication authentication, Object object,
            Collection<ConfigAttribute> configAttributes)
            throws AccessDeniedException, InsufficientAuthenticationException {

        if (configAttributes == null) {
            return;
        }

        Iterator<ConfigAttribute> ite = configAttributes.iterator();

        while (ite.hasNext()) {

            ConfigAttribute ca = ite.next();
            String needRole = ((SecurityConfig) ca).getAttribute();

            System.out.println("authentication size" + authentication.getAuthorities().size());
            // ga 為用戶所被賦予的權限。 needRole 為訪問相應的資源應該具有的權限。
            for (GrantedAuthority ga : authentication.getAuthorities()) {

                if (needRole.trim().equals(ga.getAuthority().trim())) {

                    return;
                }

            }

        }

        throw new AccessDeniedException("");

    }

    public boolean supports(ConfigAttribute attribute) {

        return true;

    }

    public boolean supports(Class<?> clazz) {
        return true;

    }

}

數據庫的SQL及預置數據(簡化了數據):

create table SYS_AUTHORITIES
(
  AUTHORITY_ID   VARCHAR2(32) not null,
  AUTHORITY_NAME VARCHAR2(40),
  AUTHORITY_DESC VARCHAR2(100),
  ENABLED        NUMBER(1),
  ISSYS          NUMBER(1),
  MODULE         VARCHAR2(4)
)

create table SYS_AUTHORITIES_RESOURCES
(
  ID           NUMBER(13) not null,
  AUTHORITY_ID VARCHAR2(32),
  RESOURCE_ID  VARCHAR2(32),
  ENABLED      NUMBER(1)
)

create table SYS_RESOURCES
(
  RESOURCE_ID     VARCHAR2(32) not null,
  RESOURCE_NAME   VARCHAR2(100),
  RESOURCE_DESC   VARCHAR2(100),
  RESOURCE_TYPE   VARCHAR2(40),
  RESOURCE_STRING VARCHAR2(200),
  PRIORITY        NUMBER(1),
  ENABLED         NUMBER(1),
  ISSYS           NUMBER(1),
  MODULE          VARCHAR2(4)
)

create table SYS_ROLES
(
  ROLE_ID   VARCHAR2(32) not null,
  ROLE_NAME VARCHAR2(40),
  ROLE_DESC VARCHAR2(100),
  ENABLED   NUMBER(1),
  ISSYS     NUMBER(1),
  MODULE    VARCHAR2(4)
)

create table SYS_ROLES_AUTHORITIES
(
  ID           NUMBER(13) not null,
  ROLE_ID      VARCHAR2(32),
  AUTHORITY_ID VARCHAR2(32),
  ENABLED      NUMBER(1)
)

create table SYS_USERS
(
  USER_ID       VARCHAR2(32) not null,
  USER_ACCOUNT  VARCHAR2(30),
  USER_NAME     VARCHAR2(40),
  USER_PASSWORD VARCHAR2(100),
  USER_DESC     VARCHAR2(100),
  ENABLED       NUMBER(1),
  ISSYS         NUMBER(1),
  USER_DEPT     VARCHAR2(20),
  USER_DUTY     VARCHAR2(10),
  SUB_SYSTEM    VARCHAR2(30)
)

create table SYS_USERS_ROLES
(
  ID      NUMBER(13) not null,
  USER_ID VARCHAR2(32),
  ROLE_ID VARCHAR2(32),
  ENABLED NUMBER(1)
)

insert into SYS_AUTHORITIES (AUTHORITY_ID, AUTHORITY_NAME, AUTHORITY_DESC, ENABLED, ISSYS, MODULE)
values ('auth_admin', 'auth_admin', null, null, null, null);
insert into SYS_AUTHORITIES (AUTHORITY_ID, AUTHORITY_NAME, AUTHORITY_DESC, ENABLED, ISSYS, MODULE)
values ('auth_lxb', 'auth_lxb', null, null, null, null);
insert into SYS_AUTHORITIES (AUTHORITY_ID, AUTHORITY_NAME, AUTHORITY_DESC, ENABLED, ISSYS, MODULE)
values ('auth_user', 'auth_user', null, null, null, null);

insert into SYS_AUTHORITIES_RESOURCES (ID, AUTHORITY_ID, RESOURCE_ID, ENABLED)
values (1, 'auth_admin', 'admin', 1);
insert into SYS_AUTHORITIES_RESOURCES (ID, AUTHORITY_ID, RESOURCE_ID, ENABLED)
values (2, 'auth_lxb', 'lxb', null);
insert into SYS_AUTHORITIES_RESOURCES (ID, AUTHORITY_ID, RESOURCE_ID, ENABLED)
values (3, 'auth_user', 'user', null);

insert into SYS_RESOURCES (RESOURCE_ID, RESOURCE_NAME, RESOURCE_DESC, RESOURCE_TYPE, RESOURCE_STRING, PRIORITY, ENABLED, ISSYS, MODULE)
values ('admin', 'admin', null, null, '/admin/admin.jsp', null, null, null, null);
insert into SYS_RESOURCES (RESOURCE_ID, RESOURCE_NAME, RESOURCE_DESC, RESOURCE_TYPE, RESOURCE_STRING, PRIORITY, ENABLED, ISSYS, MODULE)
values ('lxb', 'lxb', null, null, '/lxb.lxb.jsp', null, null, null, null);
insert into SYS_RESOURCES (RESOURCE_ID, RESOURCE_NAME, RESOURCE_DESC, RESOURCE_TYPE, RESOURCE_STRING, PRIORITY, ENABLED, ISSYS, MODULE)
values ('user', 'user', null, null, '/user/user.jsp', null, null, null, null);

insert into SYS_ROLES (ROLE_ID, ROLE_NAME, ROLE_DESC, ENABLED, ISSYS, MODULE)
values ('role_admin', 'role_admin', null, null, null, null);
insert into SYS_ROLES (ROLE_ID, ROLE_NAME, ROLE_DESC, ENABLED, ISSYS, MODULE)
values ('role_lxb', 'role_lxb', null, null, null, null);
insert into SYS_ROLES (ROLE_ID, ROLE_NAME, ROLE_DESC, ENABLED, ISSYS, MODULE)
values ('role_user', 'role_user', null, null, null, null);

insert into SYS_ROLES_AUTHORITIES (ID, ROLE_ID, AUTHORITY_ID, ENABLED)
values (1, 'role_admin', 'auth_admin', 1);
insert into SYS_ROLES_AUTHORITIES (ID, ROLE_ID, AUTHORITY_ID, ENABLED)
values (2, 'role_lxb', 'auth_lxb', 1);
insert into SYS_ROLES_AUTHORITIES (ID, ROLE_ID, AUTHORITY_ID, ENABLED)
values (3, 'role_user', 'auth_user', 1);

insert into SYS_USERS (USER_ID, USER_ACCOUNT, USER_NAME, USER_PASSWORD, USER_DESC, ENABLED, ISSYS, USER_DEPT, USER_DUTY, SUB_SYSTEM)
values ('admin', 'admin', 'admin', 'admin', null, null, null, null, null, null);
insert into SYS_USERS (USER_ID, USER_ACCOUNT, USER_NAME, USER_PASSWORD, USER_DESC, ENABLED, ISSYS, USER_DEPT, USER_DUTY, SUB_SYSTEM)
values ('lxb', 'lxb', 'lxb', 'lxb', null, null, null, null, null, null);
insert into SYS_USERS (USER_ID, USER_ACCOUNT, USER_NAME, USER_PASSWORD, USER_DESC, ENABLED, ISSYS, USER_DEPT, USER_DUTY, SUB_SYSTEM)
values ('user', 'user', 'user', 'user', null, null, null, null, null, null);

insert into SYS_USERS_ROLES (ID, USER_ID, ROLE_ID, ENABLED)
values (1, 'admin', 'role_admin', 1);
insert into SYS_USERS_ROLES (ID, USER_ID, ROLE_ID, ENABLED)
values (2, 'lxb', 'role_lxb', 1);
insert into SYS_USERS_ROLES (ID, USER_ID, ROLE_ID, ENABLED)
values (3, 'user', 'role_user', 1);

web.xml配置

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xmlns="http://java.sun.com/xml/ns/javaee" 
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" 
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" 
id="WebApp_ID" 
version="3.0">
  <display-name>springsecurity</display-name>
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>default.html</welcome-file>
    <welcome-file>default.htm</welcome-file>
    <welcome-file>default.jsp</welcome-file>
  </welcome-file-list>
  
  <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            classpath:applicationContext.xml
            classpath:applicationContext-security.xml
        </param-value>
    </context-param>
  <!-- 定義spring security代理Filter -->
   <filter>
           <filter-name>springSecurityFilterChain</filter-name>
           <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
   </filter>
   <!-- 攔截所有的請求 -->
   <filter-mapping>
           <filter-name>springSecurityFilterChain</filter-name>
           <url-pattern>/*</url-pattern>
   </filter-mapping>
   
    <!--
      - Loads the root application context of this web app at startup.
    -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
</web-app>

applicationContext.xml配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
 xmlns:util="http://www.springframework.org/schema/util"
 xmlns:jee="http://www.springframework.org/schema/jee" 
 xmlns:aop="http://www.springframework.org/schema/aop"
 xmlns:tx="http://www.springframework.org/schema/tx" 
 xmlns:context="http://www.springframework.org/schema/context"
 xsi:schemaLocation="http://www.springframework.org/schema/beans
 http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
   http://www.springframework.org/schema/aop 
   http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
   http://www.springframework.org/schema/tx
   http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
   http://www.springframework.org/schema/jee
   http://www.springframework.org/schema/jee/spring-jee-3.0.xsd
   http://www.springframework.org/schema/context
   http://www.springframework.org/schema/context/spring-context-3.0.xsd
   http://www.springframework.org/schema/util 
   http://www.springframework.org/schema/util/spring-util-3.0.xsd"
   default-lazy-init="true" default-autowire="byName">
 
    <context:component-scan base-package="com.springmvc"/>
   <context:annotation-config/>
     
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
           <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/> 
           <property name="url" value="jdbc:oracle:thin:@127.0.0.1:1521:ORCL"/> 
           <property name="username" value="scott"/> 
           <property name="password" value="scott"/> 
     </bean>
     
     <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
         <property name="dataSource" ref="dataSource"/>
         <property name="configLocation" value="classpath:myBatis.xml"/>
         <property name="mapperLocations">
             <list>
                 <value>classpath:mapper/*.xml</value>
             </list>
         </property>
         <property name="transactionFactory">
             <bean class="org.mybatis.spring.transaction.SpringManagedTransactionFactory"/>
         </property>
     </bean>
     
     <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
         <constructor-arg index="0" ref="sqlSessionFactory"/>
     </bean>
     
     <bean id="sqlSessionForBatch" class="org.mybatis.spring.SqlSessionTemplate">
         <constructor-arg index="0" ref="sqlSessionFactory"/>
         <constructor-arg index="1" value="BATCH"/>
     </bean>
     
     <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
          <property name="dataSource" ref="dataSource"/>
     </bean>
     
     <bean id="transactionProxy" abstract="true"
            class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
            <property name="transactionManager" ref="transactionManager" />

            <property name="transactionAttributes">
                <props>
                    <prop key="insert*">
                        PROPAGATION_REQUIRED,-SQLException
                    </prop>
                    <prop key="update*">
                        PROPAGATION_REQUIRED,-SQLException
                    </prop>
                    <prop key="delete*">
                        PROPAGATION_REQUIRED,-SQLException
                    </prop>
                    <prop key="clone*">
                        PROPAGATION_REQUIRED,-SQLException
                    </prop>
                    <prop key="execute*">
                        PROPAGATION_REQUIRED,-SQLException
                    </prop>
                    <prop key="*">PROPAGATION_REQUIRED,readOnly</prop>
                </props>
            </property>
        </bean>
</beans>

applicationContext-security.xml配置

<?xml version="1.0" encoding="UTF-8"?>
<b:beans xmlns="http://www.springframework.org/schema/security"
 xmlns:b="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-3.0.xsd
    http://www.springframework.org/schema/security 
    http://www.springframework.org/schema/security/spring-security-3.1.xsd">

<!-- 不要過濾圖片等靜態資源,其中**代表可以跨越目錄,*不可以跨越目錄。 -->
<!-- <http pattern="/**/*.jpg" security="none" />
<http pattern="/**/*.png" security="none" />
<http pattern="/**/*.gif" security="none" />
<http pattern="/**/*.css" security="none" />
<http pattern="/**/*.js" security="none" /> -->
<http pattern="/login.jsp" security="none" />
<!-- <http pattern="index.html" security="none"/> -->
<http pattern="/jsp/forgotpassword.jsp" security="none" />

 <http auto-config="true" access-denied-page="/accessDenied.jsp">
  
  
  <form-login login-page="/login.jsp"  authentication-failure-url="/login.jsp?error=true"  />

  <!-- "記住我"功能,采用持久化策略(將用戶的登錄信息存放在數據庫表中) -->
  <remember-me data-source-ref="dataSource" />
  
  <!-- 檢測失效的sessionId,超時時定位到另外一個URL -->
  <session-management invalid-session-url="/sessionTimeout.jsp" />
  
  <custom-filter ref="myFilter" before="FILTER_SECURITY_INTERCEPTOR"/> 
  
 </http>

 <!-- 一個自定義的filter,必須包含authenticationManager,
  accessDecisionManager,securityMetadataSource三個屬性。  -->
 <b:bean id="myFilter" 
  class="com.springmvc.security.MyFilterSecurityInterceptor">
  <b:property name="authenticationManager" 
   ref="authenticationManager"/>
  <b:property name="accessDecisionManager" 
   ref="myAccessDecisionManager"/>
  <b:property name="securityMetadataSource" 
   ref="mySecurityMetadataSource"/>
 </b:bean>
 
  <!-- 注意能夠為authentication-manager 設置alias別名  -->
 <authentication-manager alias="authenticationManager">
  <authentication-provider user-service-ref="userDetailsManager">
  <!--  <password-encoder ref="passwordEncoder">
    <salt-source user-property="username" />
   </password-encoder> -->
  </authentication-provider>
 </authentication-manager>
 

 <!--   事件監聽:實現了 ApplicationListener監聽接口,包括AuthenticationCredentialsNotFoundEvent 事件,
  AuthorizationFailureEvent事件,AuthorizedEvent事件, PublicInvocationEvent事件 -->
 <b:bean  class="org.springframework.security.authentication.event.LoggerListener" />

 
 <!-- 用戶詳細信息管理:數據源、用戶緩存(通過數據庫管理用戶、角色、權限、資源)。 -->
 <b:bean id="userDetailsManager" class="com.springmvc.security.MyUserDetailsService"/>  
 
 <!-- 訪問決策器,決定某個用戶具有的角色,是否有足夠的權限去訪問某個資源。 -->
 <b:bean id="myAccessDecisionManager"
  class="com.springmvc.security.MyAccessDecisionManager">
 </b:bean>  


 <!-- 資源源數據定義,將所有的資源和權限對應關系建立起來,即定義某一資源可以被哪些角色去訪問。 -->
 <b:bean id="mySecurityMetadataSource"
  class="com.springmvc.security.MyInvocationSecurityMetadataSourceService">
      <b:constructor-arg ref="testDao"/>
 </b:bean> 
</b:beans>

主要內容大概是這些,剩下的參考上傳的項目,添加缺省的內容,

啟動服務,打開瀏覽器,輸入http://localhost:8080/ThirdSpringSecurity/user/user.jsp,由於有權限限制,會跳轉到登錄頁面,輸入user賬號和密碼(沒有采用密文)登錄后就可以到user.jsp頁面,如果將url改為http://localhost:8080/ThirdSpringSecurity/admin/admin.jsp,會跳轉到權限不足的界面。

 

,驗證及授權的過程如下:
    1、當Web服務器啟動時,通過Web.xml中對於Spring Security的配置,加載過濾器鏈,那么在加載MyFilterSecurityInterceptor類時,會注入MyInvocationSecurityMetadataSourceService、MyUserDetailsService、MyAccessDecisionManager類。

    2、該MyInvocationSecurityMetadataSourceService類在執行時會提取數據庫中所有的用戶權限,形成權限列表;
並循環該權限列表,通過每個權限再從數據庫中提取出該權限所對應的資源列表,並將資源(URL)作為key,權限列表作為value,形成Map結構的數據。

    3、當用戶登錄時,AuthenticationManager進行響應,通過用戶輸入的用戶名和密碼,然后再根據用戶定義的密碼算法和鹽值等進行計算並和數據庫比對,
當正確時通過驗證。此時MyUserDetailsService進行響應,根據用戶名從數據庫中提取該用戶的權限列表,組合成UserDetails供Spring Security使用。

    4、當用戶點擊某個功能時,觸發MyAccessDecisionManager類,該類通過decide方法對用戶的資源訪問進行攔截。
用戶點擊某個功能時,實際上是請求某個URL或Action, 無論.jsp也好,.action或.do也好,在請求時無一例外的表現為URL。
還記得第2步時那個Map結構的數據嗎? 若用戶點擊了"login.action"這個URL之后,那么這個URL就跟那個Map結構的數據中的key對比,若兩者相同,
則根據該url提取出Map結構的數據中的value來,這說明:若要請求這個URL,必須具有跟這個URL相對應的權限值。這個權限有可能是一個單獨的權限,
也有可能是一個權限列表,也就是說,一個URL有可能被多種權限訪問。

    那好,我們在MyAccessDecisionManager類的decide這個方法里,將通過URL取得的權限列表進行循環,然后跟第3步中登錄的用戶所具有的權限進行比對,若相同,則表明該用戶具有訪問該資源的權利。 不大明白吧?  簡單地說, 在數據庫中我們定義了訪問“LOGIN”這個URL必須是具有ROLE_ADMIN權限的人來訪問,那么,登錄用戶恰恰具有該ROLE_ADMIN權限,兩者的比對過程中,就能夠返回TRUE,可以允許該用戶進行訪問。就這么簡單!

    不過在第2步的時候,一定要注意,MyInvocationSecurityMetadataSoruceService類的loadResourceDefine()方法中,形成以URL為key,權限列表為value的Map時,
要注意key和Value的對應性,避免Value的不正確對應形成重復,這樣會導致沒有權限的人也能訪問到不該訪問到的資源。
還有getAttributes()方法,要有 url.indexOf("?")這樣的判斷,要通過判斷對URL特別是Action問號之前的部分進行匹配,防止用戶請求的帶參數的URL與你數據庫中定義的URL不匹配,造成訪問拒絕!

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM