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不匹配,造成访问拒绝!

 

 

posted @ 2013-09-23 22:06  世间安得两全法  阅读(14995)  评论(0编辑  收藏  举报