spring security总结

首先导入spring security所需要的jar包
spring-security-core-2.0.5.RELEASE.jar
spring-security-core-tiger-2.0.5.RELEASE.jar
一.配置过滤器
在web.xml中定义如下过滤器
 
<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> 

二.在spring配置文件中添加security的命名空间
<beans:beans xmlns="http://www.springframework.org/schema/security" 
    xmlns:beans="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-2.0.xsd 
    http://www.springframework.org/schema/security 
    http://www.springframework.org/schema/security/spring-security-2.0.4.xsd"> 

三.在spring配置文中中定义需要保护的资源
   
<http auto-config='true'> 
        <intercept-url pattern="/admin.jsp" access="ROLE_ADMIN" /> 
        <intercept-url pattern="/**" access="ROLE_USER" /> 
    </http> 

    注意intercept-url的先后顺序,spring security使用第一个能匹配的intercept-url标签进行权限控制。
四.使用数据库获取用户权限
  <!-- 数据源 -->
   
<beans:bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> 
    <beans:property name="driverClassName" value="com.mysql.jdbc.Driver"></beans:property> 
<beans:property name="url" value="jdbc:mysql://localhost:3306/sp"></beans:property> 
<beans:property name="username" value="root"></beans:property> 
<beans:property name="password" value="root"></beans:property> 
    </beans:bean>

    <!-- 定义用户的权限根据注入的数据源获得 -->
   
<authentication-provider> 
        <jdbc-user-service data-source-ref="dataSource"/> 
    </authentication-provider> 


定义权限管理模块的表结构(mysql数据库)
alter table `t_account_role` drop foreign key `FK1C2BC93384B0A30E`; 
alter table `t_account_role` drop foreign key `FK1C2BC9332D31C656`; 
drop table if exists `t_account_role`; 
drop table if exists `t_account`; 
drop table if exists `t_role`; 
/* 用户表 */ 
CREATE TABLE `t_account` ( 
  `id` int(11) NOT NULL, 
  `username` varchar(255) default NULL, 
  `password` varchar(255) default NULL, 
  `enabled` int default NULL, /* 用户是否禁用 0:禁用 非0:可用*/ 
  PRIMARY KEY  (`id`) 
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 
    /* 角色表 */ 
    CREATE TABLE `t_role` ( 
  `id` int(11) NOT NULL, 
  `name` varchar(255) default NULL, /* 角色名 */ 
  `descn` varchar(255) default NULL, /* 角色在spring配置文件中的名字 如ROLE_ADMIN,ROLE_USER*/ 
  PRIMARY KEY  (`id`) 
) ENGINE=InnoDB DEFAULT CHARSET=utf8; 

/* 用户角色中间表 */ 
CREATE TABLE `t_account_role` ( 
  `a_id` int(11) NOT NULL, 
  `r_id` int(11) NOT NULL, 
  PRIMARY KEY  (`a_id`,`r_id`), 
  KEY `FK1C2BC9332D31C656` (`r_id`), 
  KEY `FK1C2BC93371CCC630` (`a_id`), 
  CONSTRAINT `FK1C2BC93384B0A30E` FOREIGN KEY (`a_id`) REFERENCES `t_account` (`id`), 
  CONSTRAINT `FK1C2BC9332D31C656` FOREIGN KEY (`r_id`) REFERENCES `t_role` (`id`) 
) ENGINE=InnoDB DEFAULT CHARSET=utf8; 

/* 初始化数据 */ 
insert into t_account values(1,'zhangsan','123',1); 
insert into t_account values(2,'lisi','321',1); 

insert into t_role values(1,'系统管理员','ROLE_ADMIN'); 
insert into t_role values(2,'普通用户','ROLE_USER'); 

insert into t_account_role values(1,2); 
    insert into t_account_role values(2,1); 

   
当用户登录时,spring security首先判断用户是否可以登录。用户登录后spring security获得该用户的
所有权限以判断用户是否可以访问资源。

spring配置文件中定义
<authentication-provider>
        <jdbc-user-service data-source-ref="dataSource"
        users-by-username-query="select username,password,enabled from t_account where username=?"
        authorities-by-username-query="select r.descn from t_account_role ar join
         t_account a on ar.a_id=a.id join t_role r on ar.r_id=r.id where a.username=?"/>
    </authentication-provider>
   
    users-by-username-query:根据用户名查找用户
    authorities-by-username-query:根据用户名查找这个用户所有的角色名,将用户访问的URL地址和
    查询结果与<intercept-url pattern="/admin.jsp" access="ROLE_ADMIN" />标签进行匹配。
    匹配成功就允许访问,否则就返回到提示页面。
   
    在<http>标签中添加登录页面等信息
   
<http auto-config='true'> 
      <intercept-url pattern="/login.jsp" access="IS_AUTHENTICATED_ANONYMOUSLY"/> 
        <intercept-url pattern="/admin.jsp" access="ROLE_ADMIN" /> 
        <intercept-url pattern="/**" access="ROLE_USER" /> 
        <form-login login-page="/login.jsp" 
                    authentication-failure-url="/error.jsp" 
                    default-target-url="/index.jsp" /> 
    </http>    
    
    <intercept-url pattern="/login.jsp" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
让没有登录的用户也可以访问登录页面
   
<form-login login-page="/login.jsp" 
                    authentication-failure-url="/error.jsp" 
                    default-target-url="/index.jsp" /> 

    login-page:当用户登录时显示自定义登录页面
    authentication-failure-url:登录失败时跳转到哪个页面
    default-target-url:登录成功后跳转到哪个页面
   
    注意:users-by-username-query指定的查询,必须至少按顺序返回3列,列名必须是username,password,enabled
          authorities-by-username-query指定的查询,必须至少按顺序返回2列,第一列列名必须是username
          第2列必须是权限的名字,与<intercept-url pattern="/admin.jsp" access="ROLE_ADMIN" />中的
          access匹配。
          不能使用select *
    完成的配置文件:
   
<?xml version="1.0" encoding="UTF-8"?> 
<beans:beans xmlns="http://www.springframework.org/schema/security" 
    xmlns:beans="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-2.0.xsd 
    http://www.springframework.org/schema/security 
    http://www.springframework.org/schema/security/spring-security-2.0.4.xsd"> 
    
    <!-- 数据源 --> 
    <beans:bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> 
    <beans:property name="driverClassName" value="com.mysql.jdbc.Driver"></beans:property> 
<beans:property name="url" value="jdbc:mysql://localhost:3306/sp"></beans:property> 
<beans:property name="username" value="root"></beans:property> 
<beans:property name="password" value="root"></beans:property> 
    </beans:bean> 

    <http auto-config='true'> 
    <intercept-url pattern="/login.jsp" access="IS_AUTHENTICATED_ANONYMOUSLY"/> 
        <intercept-url pattern="/admin.jsp" access="ROLE_ADMIN" /> 
        <intercept-url pattern="/**" access="ROLE_USER,ROLE_ADMIN" /> 
        <form-login login-page="/login.jsp" 
                    authentication-failure-url="/error.jsp" 
                    default-target-url="/index.jsp" /> 
    </http> 

    <authentication-provider> 
        <jdbc-user-service data-source-ref="dataSource" 
        users-by-username-query="select username,password,enabled from t_account where username=?" 
        authorities-by-username-query="select a.username,r.descn from t_account_role ar join 
         t_account a on ar.a_id=a.id join t_role r on ar.r_id=r.id where a.username=?"/> 
    </authentication-provider> 

</beans:beans> 


登录页面
<form action="${pageContext.request.contextPath}/j_spring_security_check" method="post"> 
        用户: <input type="text" name="j_username" value="${SPRING_SECURITY_LAST_USERNAME}"/><br /> 
        密码: <input type="password" name="j_password"/><br /> 
        <input type="checkbox" name="_spring_security_remember_me" />两周之内不必登陆<br /> 
        <input type="submit" value="登陆"/><input type="reset" value="重置"/> 
  </form> 

  页面中输入控件的name属性和form的action地址必须符合spring security的规定
 
 
登录失败页面
  ${SPRING_SECURITY_LAST_EXCEPTION.message}    获取spring生成的异常
 
将资源信息放入数据库中
表结构
/* 用户表 */ 
CREATE TABLE `t_account` ( 
  `id` int(11) NOT NULL, 
  `username` varchar(255) default NULL, 
  `password` varchar(255) default NULL, 
  `enabled` int default NULL, /* 用户是否禁用 0:禁用 非0:可用*/ 
  PRIMARY KEY  (`id`) 
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 
    /* 角色表 */ 
    CREATE TABLE `t_role` ( 
  `id` int(11) NOT NULL, 
  `name` varchar(255) default NULL, /* 角色名 */ 
  `descn` varchar(255) default NULL, /* 角色在spring配置文件中的名字 如ROLE_ADMIN,ROLE_USER*/ 
  PRIMARY KEY  (`id`) 
) ENGINE=InnoDB DEFAULT CHARSET=utf8; 

/* 用户角色中间表 */ 
CREATE TABLE `t_account_role` ( 
  `a_id` int(11) NOT NULL, 
  `r_id` int(11) NOT NULL, 
  PRIMARY KEY  (`a_id`,`r_id`), 
  KEY `FK1C2BC9332D31C656` (`r_id`), 
  KEY `FK1C2BC93371CCC630` (`a_id`), 
  CONSTRAINT `FK1C2BC93384B0A30E` FOREIGN KEY (`a_id`) REFERENCES `t_account` (`id`), 
  CONSTRAINT `FK1C2BC9332D31C656` FOREIGN KEY (`r_id`) REFERENCES `t_role` (`id`) 
) ENGINE=InnoDB DEFAULT CHARSET=utf8; 
/* 资源表 */ 
CREATE TABLE `t_module` ( 
  `id` int(11) NOT NULL, 
  `name` varchar(255) default NULL, 
  `address` varchar(255) default NULL, 
  PRIMARY KEY  (`id`) 
) ENGINE=InnoDB DEFAULT CHARSET=utf8; 

/* 资源角色的中间表 */ 
CREATE TABLE `t_module_role` ( 
  `m_id` int(11) NOT NULL, 
  `r_id` int(11) NOT NULL, 
  PRIMARY KEY  (`m_id`,`r_id`), 
  KEY `FKA713071E2D31C656` (`r_id`), 
  KEY `FKA713071ED78C9071` (`m_id`), 
  CONSTRAINT `FKA713071ED78C9071` FOREIGN KEY (`m_id`) REFERENCES `t_module` (`id`), 
  CONSTRAINT `FKA713071E2D31C656` FOREIGN KEY (`r_id`) REFERENCES `t_role` (`id`) 
) ENGINE=InnoDB DEFAULT CHARSET=utf8; 

/* 初始化数据 */ 
insert into t_account values(1,'zhangsan','123',1); 
insert into t_account values(2,'lisi','321',1); 

insert into t_role values(1,'系统管理员','ROLE_ADMIN'); 
insert into t_role values(2,'普通用户','ROLE_USER'); 

insert into t_account_role values(1,2); 
    insert into t_account_role values(2,1); 
    
    insert into t_module values(1,'部门管理','/dept.jsp'); 
    insert into t_module values(2,'人员管理','/emp.jsp'); 
    
    insert into `t_module_role` values(1,1); 
    insert into `t_module_role` values(1,2); 
    insert into `t_module_role` values(2,1); 

   
1.在自定义的过滤器中获取资源的URL地址和角色名以取代spring配置文件中原有的<intercept-url pattern="/login.jsp" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
    过滤器代码:
import java.sql.ResultSet; 
import java.sql.SQLException; 

import java.util.LinkedHashMap; 
import java.util.List; 
import java.util.Map; 

import javax.sql.DataSource; 

import org.springframework.beans.factory.FactoryBean; 

import org.springframework.jdbc.core.support.JdbcDaoSupport; 
import org.springframework.jdbc.object.MappingSqlQuery; 

import org.springframework.security.ConfigAttributeDefinition; 
import org.springframework.security.ConfigAttributeEditor; 
import org.springframework.security.intercept.web.DefaultFilterInvocationDefinitionSource; 
import org.springframework.security.intercept.web.FilterInvocationDefinitionSource; 
import org.springframework.security.intercept.web.RequestKey; 
import org.springframework.security.util.AntUrlPathMatcher; 
import org.springframework.security.util.UrlMatcher; 


public class JdbcFilterInvocationDefinitionSourceFactoryBean 
    extends JdbcDaoSupport implements FactoryBean { 
    private String resourceQuery; 

    public boolean isSingleton() { 
        return true; 
    } 

    public Class getObjectType() { 
        return FilterInvocationDefinitionSource.class; 
    } 

    public Object getObject() { 
        return new DefaultFilterInvocationDefinitionSource(this 
            .getUrlMatcher(), this.buildRequestMap()); 
    } 

    protected Map<String, String> findResources() { 
        ResourceMapping resourceMapping = new ResourceMapping(getDataSource(), 
                resourceQuery); 

        Map<String, String> resourceMap = new LinkedHashMap<String, String>(); 

        for (Resource resource : (List<Resource>) resourceMapping.execute()) { 
            String url = resource.getUrl(); 
            String role = resource.getRole(); 

            if (resourceMap.containsKey(url)) { 
                String value = resourceMap.get(url); 
                resourceMap.put(url, value + "," + role); 
            } else { 
                resourceMap.put(url, role); 
            } 
        } 

        return resourceMap; 
    } 

    protected LinkedHashMap<RequestKey, ConfigAttributeDefinition> buildRequestMap() { 
        LinkedHashMap<RequestKey, ConfigAttributeDefinition> requestMap = null; 
        requestMap = new LinkedHashMap<RequestKey, ConfigAttributeDefinition>(); 

        ConfigAttributeEditor editor = new ConfigAttributeEditor(); 

        Map<String, String> resourceMap = this.findResources(); 

        for (Map.Entry<String, String> entry : resourceMap.entrySet()) { 
            RequestKey key = new RequestKey(entry.getKey(), null); 
            editor.setAsText(entry.getValue()); 
            requestMap.put(key, 
                (ConfigAttributeDefinition) editor.getValue()); 
        } 

        return requestMap; 
    } 

    protected UrlMatcher getUrlMatcher() { 
        return new AntUrlPathMatcher(); 
    } 

    public void setResourceQuery(String resourceQuery) { 
        this.resourceQuery = resourceQuery; 
    } 

    private class Resource { 
        private String url; 
        private String role; 

        public Resource(String url, String role) { 
            this.url = url; 
            this.role = role; 
        } 

        public String getUrl() { 
            return url; 
        } 

        public String getRole() { 
            return role; 
        } 
    } 

    private class ResourceMapping extends MappingSqlQuery { 
        protected ResourceMapping(DataSource dataSource, 
            String resourceQuery) { 
            super(dataSource, resourceQuery); 
            compile(); 
        } 

        protected Object mapRow(ResultSet rs, int rownum) 
            throws SQLException { 
            String url = rs.getString(1); 
            String role = rs.getString(2); 
            Resource resource = new Resource(url, role); 
            
            return resource; 
        } 
    } 
} 


将自定义的过滤器放入到原有的spring security过滤器链中(在spring配置文件中配置)
定义自定义过滤器(sql语句用于查询资源的URL地址 如:/index.jsp 和角色名 如ROLE_USER)
<beans:bean id="filterInvocationDefinitionSource" 
        class="com.lovo.JdbcFilterInvocationDefinitionSourceFactoryBean"> 
        <beans:property name="dataSource" ref="dataSource"/> 
        <beans:property name="resourceQuery" value=" 
            select m.address,r.descn 
from t_module_role mr 
join t_module m on mr.m_id=m.id 
join t_role r on mr.r_id=r.id; 
        "/> 
    </beans:bean> 

  将自定义过滤器放入过滤器链中
 
<beans:bean id="filterSecurityInterceptor" 
        class="org.springframework.security.intercept.web.FilterSecurityInterceptor" autowire="byType"> 
        <custom-filter before="FILTER_SECURITY_INTERCEPTOR"/> 
        <beans:property name="objectDefinitionSource" ref="filterInvocationDefinitionSource" /> 
    </beans:bean> 

    注意:FilterSecurityInterceptor过滤器会向request中写入一个标记,用于标记是否已经控制了当前请求,以避免对同一请求多次处理,导致第2个FilterSecurityInterceptor不会再次执行。
    在<http>中不需要再定义<intercept-url>,如下:
   
<http auto-config='true'> 
        <form-login login-page="/login.jsp" 
                    authentication-failure-url="/error.jsp" 
                    default-target-url="/index.jsp" /> 
                    
    </http> 

    自定义的过滤器就从配置文件中读取sql,查询结果就是角色和资源,用户登录时就在session中保存了用户的角色。
    注意:intercept-url的先后顺序,spring security使用第一个能匹配的intercept-url标签进行权限控制。
    现在intercept-url来源于数据库,所以在sql查询时注意角色和资源的顺序。
    建议在角色和资源的中间表中添加1个字段用于标识顺序,(按从严到宽的顺序)
    表结构修改如下:
   
/* 资源角色的中间表 */ 
CREATE TABLE `t_module_role` ( 
  `m_id` int(11) NOT NULL, 
  `r_id` int(11) NOT NULL, 
  `priority` int(11) default NULL, /* 用于标识角色和资源的匹配顺序 从严到宽 */ 
  PRIMARY KEY  (`m_id`,`r_id`), 
  KEY `FKA713071E2D31C656` (`r_id`), 
  KEY `FKA713071ED78C9071` (`m_id`), 
  CONSTRAINT `FKA713071ED78C9071` FOREIGN KEY (`m_id`) REFERENCES `t_module` (`id`), 
  CONSTRAINT `FKA713071E2D31C656` FOREIGN KEY (`r_id`) REFERENCES `t_role` (`id`) 
) ENGINE=InnoDB DEFAULT CHARSET=utf8; 


数据如下:
insert into t_account values(1,'zhangsan','123',1); 
insert into t_account values(2,'lisi','321',1); 

insert into t_role values(1,'系统管理员','ROLE_ADMIN'); 
insert into t_role values(2,'普通用户','ROLE_USER'); 

insert into t_account_role values(1,2); 
    insert into t_account_role values(2,1); 
    
    insert into t_module values(1,'部门管理','/dept.jsp'); 
    insert into t_module values(2,'人员管理','/emp.jsp'); 
    
    insert into `t_module_role` values(1,1,3); 
    insert into `t_module_role` values(1,2,2); 
    insert into `t_module_role` values(2,1,1); 
    

    自定义过滤器修改如下:
   
<beans:bean id="filterInvocationDefinitionSource" 
        class="com.lovo.JdbcFilterInvocationDefinitionSourceFactoryBean"> 
        <beans:property name="dataSource" ref="dataSource"/> 
        <beans:property name="resourceQuery" value=" 
            select m.address,r.descn 
from t_module_role mr 
join t_module m on mr.m_id=m.id 
join t_role r on mr.r_id=r.id 
order by mr.priority 
        "/> 
    </beans:bean> 

   
    如果持久层使用的是hibernate,那么只需要给自定义过滤器注入1个hibernateTemplate.
    在自定义过滤器中就不需要再使用mapRow的方式。而是直接获取url和角色名即可。
    例如:
   
public class ModuleFilter implements FactoryBean { 
private String resourceQuery; 

    public boolean isSingleton() { 
        return true; 
    } 

    public Class getObjectType() { 
        return FilterInvocationDefinitionSource.class; 
    } 

    public Object getObject() { 
        return new DefaultFilterInvocationDefinitionSource(this 
            .getUrlMatcher(), this.buildRequestMap()); 
    } 

    protected Map<String, String> findResources() { 
        ResourceMapping resourceMapping = new ResourceMapping(); 

        Map<String, String> resourceMap = new LinkedHashMap<String, String>(); 

        for (Resource resource : (List<Resource>) resourceMapping.execute()) { 
            String url = resource.getUrl(); 
            String role = resource.getRole(); 

            if (resourceMap.containsKey(url)) { 
                String value = resourceMap.get(url); 
                resourceMap.put(url, value + "," + role); 
            } else { 
                resourceMap.put(url, role); 
            } 
        } 

        return resourceMap; 
    } 

    protected LinkedHashMap<RequestKey, ConfigAttributeDefinition> buildRequestMap() { 
        LinkedHashMap<RequestKey, ConfigAttributeDefinition> requestMap = null; 
        requestMap = new LinkedHashMap<RequestKey, ConfigAttributeDefinition>(); 

        ConfigAttributeEditor editor = new ConfigAttributeEditor(); 

        Map<String, String> resourceMap = this.findResources(); 

        for (Map.Entry<String, String> entry : resourceMap.entrySet()) { 
            RequestKey key = new RequestKey(entry.getKey(), null); 
            editor.setAsText(entry.getValue()); 
            requestMap.put(key, 
                (ConfigAttributeDefinition) editor.getValue()); 
        } 

        return requestMap; 
    } 

    protected UrlMatcher getUrlMatcher() { 
        return new AntUrlPathMatcher(); 
    } 

    public void setResourceQuery(String resourceQuery) { 
        this.resourceQuery = resourceQuery; 
    } 

    private class Resource { 
        private String url; 
        private String role; 

        public Resource(String url, String role) { 
            this.url = url; 
            this.role = role; 
        } 

        public String getUrl() { 
            return url; 
        } 

        public String getRole() { 
            return role; 
        } 
    } 

    private class ResourceMapping{ 
        public List<Resource> execute(){ 
        List<Resource> rlist = new ArrayList<Resource>(); 
        List<Role> list = hibernateTemplate.find(resourceQuery); 
        for(int i=0;i<list.size();i++){ 
        Role role = list.get(i); 
        Set<Module> set = role.getModuleSet(); 
        Iterator<Module> it = set.iterator(); 
        while(it.hasNext()){ 
        Module m = it.next(); 
        Resource re = new Resource(m.getUrl(),role.getDescn()); 
        rlist.add(re); 
        } 
        } 
return rlist; 
        } 
    } 

    public void setHibernateTemplate(HibernateTemplate hibernateTemplate) { 
this.hibernateTemplate = hibernateTemplate; 
} 

private HibernateTemplate hibernateTemplate; 
} 

而从灵活性的角度考虑,把hql写在配置文件中
<beans:bean id="moduleFilter" class="com.lovo.ModuleFilter"> 
         <beans:property name="resourceQuery" 
         value="from com.lovo.po.Role order by ind"> 
         </beans:property> 
         <beans:property name="hibernateTemplate" ref="hibernateTemplate"> 
         </beans:property> 
    </beans:bean> 

   
    问题:系统只会在初始化的时候从数据库中加载信息。无法识别数据库中信息的改变。
    解决:每个jsp页面上重新内存
    代码:每个jsp页面include如下代码:
<%@page import="org.springframework.context.ApplicationContext"%> 
<%@page import="org.springframework.web.context.support.WebApplicationContextUtils"%> 
<%@page import="org.springframework.beans.factory.FactoryBean"%> 
<%@page import="org.springframework.security.intercept.web.FilterSecurityInterceptor"%> 
<%@page import="org.springframework.security.intercept.web.FilterInvocationDefinitionSource"%> 
<% 
    ApplicationContext ctx =  WebApplicationContextUtils.getWebApplicationContext(application); 
    FactoryBean factoryBean = (FactoryBean) ctx.getBean("&自定义过滤器的id"); 
    FilterInvocationDefinitionSource fids = (FilterInvocationDefinitionSource) factoryBean.getObject(); 
    FilterSecurityInterceptor filter = (FilterSecurityInterceptor) ctx.getBean("filterSecurityInterceptor"); 
    filter.setObjectDefinitionSource(fids); 
%> 



控制用户信息
用户密码MD5加密:
<authentication-provider> 
    <password-encoder hash="md5"/> 
        <jdbc-user-service data-source-ref="dataSource" 
        users-by-username-query="select username,password,enabled from t_account where username=?" 
        authorities-by-username-query="select a.username,r.descn from t_account_role ar join 
         t_account a on ar.a_id=a.id join t_role r on ar.r_id=r.id where a.username=?"/> 
  </authentication-provider> 

  盐值加密
  
<authentication-provider> 
    <password-encoder hash="md5"> 
    <salt-source user-property="username"/> 
    </password-encoder> 
        <jdbc-user-service data-source-ref="dataSource" 
        users-by-username-query="select username,password,enabled from t_account where username=?" 
        authorities-by-username-query="select a.username,r.descn from t_account_role ar join 
         t_account a on ar.a_id=a.id join t_role r on ar.r_id=r.id where a.username=?"/> 
    </authentication-provider> 

   
    用户信息缓存,使用spring内置的ehCache实现
    
<beans:bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"></beans:bean> 
<beans:bean id="userEhCache" class="org.springframework.cache.ehcache.EhCacheFactoryBean"> 
<beans:property name="cacheManager" ref="cacheManager"></beans:property> 
<beans:property name="cacheName" value="userCache"></beans:property> 
</beans:bean> 
<beans:bean id="userCache" class="org.springframework.security.providers.dao.cache.EhCacheBasedUserCache"> 
<beans:property name="cache" ref="userEhCache"></beans:property> 
</beans:bean> 


在src目录下新建ehcache.xml
<ehcache> 
    <diskStore path="java.io.tmpdir"/> 

    <defaultCache 
        maxElementsInMemory="1000" 
        eternal="false" 
        timeToIdleSeconds="120" 
        timeToLiveSeconds="120" 
        overflowToDisk="true" 
    /> 

    <cache 
        name="userCache" 
        maxElementsInMemory="100" 
        eternal="false" 
        timeToIdleSeconds="600" 
        timeToLiveSeconds="3600" 
        overflowToDisk="true" 
    /> 
</ehcache> 


在程序中获取用户信息
UserDetails ud = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); 
String name = ud.getUsername(); 
String pwd = ud.getPassword(); 
GrantedAuthority[] ga = ud.getAuthorities(); 
System.out.println(name + "," + pwd); 
for(GrantedAuthority g : ga){ 
System.out.println(g.getAuthority()); 
} 


自定义访问拒绝页面
<http auto-config='true' access-denied-page="/error.jsp"> 


访问用户以带参形式访问
在资源URL地址后加*
如   
insert into t_module values(1,'部门管理','/dept.jsp*'); 
    insert into t_module values(2,'人员管理','/emp.jsp*'); 

   
自定义用户接口实现
由于将sql写在配置文件中只适用于小型系统,而且不灵活,在大型系统或实体关系复杂时需要自定义用户实现的接口。
自定义用户实现需要实现2个接口。
UserDetails:实体类需要实现的接口。
UserDetailsService:实体管理类需要实现的接口。
配置:
<!-- 实体管理类 --> 
<beans:bean id="userManager" class="com.lovo.UserManager"></beans:bean> 
<authentication-provider user-service-ref="userManager"> 
    <!--  
        <jdbc-user-service data-source-ref="dataSource" 
        users-by-username-query="select username,password,enabled from t_account where username=?" 
        authorities-by-username-query="select a.username,r.descn from t_account_role ar join 
         t_account a on ar.a_id=a.id join t_role r on ar.r_id=r.id where a.username=?"/> 
   --> 
    </authentication-provider> 

    原有的通过固定sql语句的获取方式可以抛弃,改为使用user-service-ref属性注入的bean来实现对用户及用户所
    拥有的资源的查找。
   
用户注销
注销功能由过滤器org.springframework.security.ui.logout.LogoutFilter负责完成。
<a href="${pageContext.request.contextPath}/j_spring_security_logout">注销</a>
被注销的用户就是当前session中保存的用户,注销后页面自动重定向到default-target-url所指定的页面

spring security过滤器体系
spring security的一系列功能都是由一串过滤器来完成的。在spring配置文件中配置的<http>标签实际上就是
起到默认的过滤器进行声明和配置的作用。

管理会话
当项目中要求不能使用同一个账号同时登陆,按以下步骤实施
1.web.xml添加1个监听器
<listener> 
  <listener-class>org.springframework.security.ui.session.HttpSessionEventPublisher</listener-class> 
  </listener> 

  2.spring配置文件中的<http>标签添加子标签<concurrent-session-control/>
 <http auto-config='true' access-denied-page="/noview.jsp"> 
        <form-login login-page="/login.jsp" authentication-failure-url="/error.jsp" default-target-url="/index.jsp"/> 
<concurrent-session-control/> 
</http> 
<concurrent-session-control/>
会产生1个org.springframework.security.concurrent.ConcurrentSessionFilter
并放在过滤器链的最前面。
默认情况下,使用同一账号后登陆的用户会踢出先登陆的用户。
如果想禁止第2个用户登录,则设置
<concurrent-session-control exception-if-maximum-exceeded="true"/>



对方法级的权限控制
1.添加依赖包 cglib-nodep-2.1_3.jar aspectjweaver.jar aspectjrt.jar
2.利用<global-method-security>设置需要保护的方法及可以调用的权限
<global-method-security> 
<protect-pointcut access="ROLE_ADMIN,ROLE_USER" expression="execution(* com.lovo.bo.AccountBo.get*(..))"/> 
<protect-pointcut access="ROLE_ADMIN" expression="execution(* com.lovo.bo.AccountBo.create*(..))"/> 
</global-method-security> 


利用注解同样可以实现方法级的保护
需要spring-security-core-tiger.jar包
启用注解保护
<global-method-security secured-annotations="enabled"/> 


@Secured({"ROLE_ADMIN","ROLE_USER"}) 
public void getOneAccount(); 

拥护ROLE_ADMIN或ROLE_USER权限的用户可以调用该方法

SecurityContext安全上下文
SecurityContext securityContext = SecurityContextHolder.getContext();
SecurityContext中保存着实现了Authentication 接口的对象,如果用户尚未通过
认证,那么SecurityContext.getAuthenticaiton()方法就会返回null。
  注意,如果使用了匿名用户,SecurityContext.getAuthenticaiton()返回的不是null.
  只有在未启用过滤器链的情况下,SecurityContext.getAuthenticaiton()才返回空。
 
  验证管理器
  验证管理器用来识别用户的身份。使用命名空间会自动注册一个验证管理器的bean.
  是org.springframework.security.providers.ProviderManager类的一个对象。
  如果要在其他的bean中要引用这个验证管理器,则给这个验证管理器取一个别名。
 
<authentication-manager alias="authenticationManager"/> 

  如果要采用其他的类来完成验证管理器的功能,可以使用以下标签
  
 <beans:bean id="abc" class="org.springframework.security.providers.dao.DaoAuthenticationProvider"> 
    <custom-authentication-provider/> 
    </beans:bean> 

   
  访问决策管理器
  当使用命名空间配置时,默认的AccessDecisionManager实例会自动注册。
  默认的策略是使用一个AffirmativeBased作为AccessDecisionManager(访问决策管理器),投票者是RoleVoter和
AuthenticatedVote
<global-method-security secured-annotations="enabled" access-decision-manager-ref="accessDecisionManager"> 
</global-method-security>

利用访问决策管理器对方法进行保护

<beans:bean id="filterSecurityInterceptor" class="org.springframework.security.intercept.web.FilterSecurityInterceptor" autowire="byType"> 
        <custom-filter before="FILTER_SECURITY_INTERCEPTOR"/> 
        <beans:property name="objectDefinitionSource" ref="moduleFilter" /> 
        <beans:property name="accessDecisionManager" ref="accessDecisionManager"></beans:property> 
    </beans:bean> 

  向过滤器注入访问决策管理器
 
 
  不管是MethodSecurityInterceptor还是FilterSecurityInterceptor都使用 authenticationManager和accessDecisionManager属性用于验证用户,并且都是通过使用 objectDefinitionSource属性来定义受保护的资源。不同的是过滤器安全拦截器将URL资源与权限关联,而方法安全拦截器将业务方法与权限关联。
  objectDefinitionSource属性需要注入的就是org.springframework.security.intercept.ObjectDefinitionSource这个接口的实现类。
  该接口中有一个方法是:
  ConfigAttributeDefinition getAttributes(Object object) throws IllegalArgumentException;
  参数实际类型是org.springframework.security.intercept.web.FilterInvocation
  这个方法用于获取保护资源对应的权限信息,返回一个ConfigAttributeDefinition对象。
  ConfigAttributeDefinition对象内部维护1个列表,安全拦截器就通过调用getAttributes方法来获取ConfigAttributeDefinition对象,并将该对象和当前用户拥有的Authentication对象传递给accessDecisionManager(访问决策管理器)
  访问决策管理器在将其传递给具体实现类维护的投票者,这些投票者从ConfigAttributeDefinition对象中获取这个存放了访问保护资源需要的权限信息的列表,然后遍历这个列表并与 Authentication对象中GrantedAuthority[]数据中的用户权限信息进行匹配,如果匹配成功,投票者就会投赞成票,否则就投反对票,最后访问决策管理器来统计这些投票决定用户是否能访问该资源。
 
  FilterInvocationDefinitionSource接口和MethodDefinitionSource接口继承自ObjectDefinitionSource接口,并提供了2个默认实现类用以从配置文件读取权限信息。
  是DefaultFilterInvocationDefinitionSource和DelegatingMethodDefinitionSource两个类,如果需要从其他数据来源读取则需要实现FilterInvocationDefinitionSource接口和MethodDefinitionSource接口。
 
   自定义的过滤器必须注入objectDefinitionSource,accessDecisionManager,authenticationManager3个属性。
 
  FilterInvocationDefinitionSource接口getAttributes实现思路
      1.获取客户端访问的url地址。
      2.将该url地址和数据库中存储的url地址匹配,找到所有有权访问该地址的权限(角色)名字。
      3.把所有的权限名利用ConfigAttributeEditor类封装成ConfigAttributeDefinition对象

简单例子:


public class MyFilter extends HibernateDaoSupport implements FilterInvocationDefinitionSource,FactoryBean{ 
//保存权限信息(能够访问当前资源的角色名) 
private List<String> roleNameList = new ArrayList<String>(); 

public ConfigAttributeDefinition getAttributes(Object object) throws IllegalArgumentException { 
FilterInvocation fi = (FilterInvocation)object; 
String requeatUrl = fi.getRequestUrl();  //获取客户端访问的url地址 
requeatUrl = requeatUrl.toLowerCase();   //将url地址全部转换成小写 
if(requeatUrl.indexOf("?") != -1){ //过滤请求参数 
requeatUrl = requeatUrl.substring(0,requeatUrl.indexOf("?")); 
} 
//在数据库中查找能够访问此url地址的角色 
List<Module> list = this.getHibernateTemplate().find("from com.lovo.po.Module where url like ?",requeatUrl+"%"); 
if(list.size() <= 0){ 
return null; 
} 
String s = ""; 
Module module = (Module)list.get(0); 
Set<Role> set = module.getRoleSet(); 
roleNameList.clear(); 
for(Role role : set){ 
roleNameList.add(role.getDescn()); 
s += role.getDescn() + ","; 
} 
s = s.substring(0,(s.length() - 1));  //将所有的角色名(ROLE_开头)拼接为一个字符串。 

ConfigAttributeEditor editer = new ConfigAttributeEditor(); 

editer.setAsText(s); 
return (ConfigAttributeDefinition) editer.getValue();  //将包含所有角色名(ROLE_开头)的字符串转换为ConfigAttributeDefinition对象。 

} 

public Collection getConfigAttributeDefinitions() { 
        return Collections.unmodifiableCollection(this.roleNameList); 
} 

public boolean supports(Class clazz) { 
return FilterInvocation.class.isAssignableFrom(clazz); 
} 

public Object getObject() throws Exception { 
return this; 
} 


public Class getObjectType() { 
return FilterInvocationDefinitionSource.class; 
} 


public boolean isSingleton() { 
// TODO Auto-generated method stub 
return true; 
} 

} 

方法保护的原理是通过aop来实现的。
<aop:config> 
<aop:pointcut expression="execution(* com.lovo.bo.face.*.*(..))" id="me"/> 
<aop:advisor advice-ref="methodSecurityInterceptor" pointcut-ref="me"/>    
</aop:config> 

通知是1个实现了MethodSecurityInterceptor接口的类
methodSecurityInterceptor是spring中的一个类
<beans:bean id="methodSecurityInterceptor" class="org.springframework.security.intercept.method.aopalliance.MethodSecurityInterceptor"> 
    <beans:property name="objectDefinitionSource" ref="methodFilter" /> 
        <beans:property name="accessDecisionManager" ref="accessDecisionManager"></beans:property> 
        <beans:property name="authenticationManager" ref="authenticationManager"></beans:property> 
</beans:bean> 

accessDecisionManager属性注入访问决策管理器,authenticationManager属性注入验证管理器。
objectDefinitionSource属性注入1个实现了MethodDefinitionSource接口的类。
<beans:bean id="methodFilter" class="com.lovo.method.MethodFilter"> 
<beans:property name="hibernateTemplate" ref="hibernateTemplate"></beans:property> 
</beans:bean> 

  这个类的作用是根据用户调用的方法,找到相应的角色,
public class MethodFilter extends HibernateDaoSupport implements MethodDefinitionSource{ 

private List<String> roleList = new ArrayList<String>(); 
public ConfigAttributeDefinition getAttributes(Method method, Class targetClass) { 
return null; 
} 

public ConfigAttributeDefinition getAttributes(Object object) 
throws IllegalArgumentException { 

ConfigAttributeEditor editor = new ConfigAttributeEditor(); 
String s = ""; 
ReflectiveMethodInvocation rmi = (ReflectiveMethodInvocation)object; 

String methodName = rmi.getThis().getClass().getName() + "." + rmi.getMethod().getName(); 
System.out.println(methodName); 

List<Module> list = this.getHibernateTemplate().find("from com.lovo.po.Module where url like ?",methodName); 
if(list.size() == 0){ 
return null; 
} 
roleList.clear(); 
for(int i=0;i<list.size();i++){ 
Module module = list.get(i); 
Set<Role> roleSet = module.getRoleSet(); 
Iterator<Role> it = roleSet.iterator(); 
while(it.hasNext()){ 
Role role = it.next(); 
s += role.getDescn() + ","; 
roleList.add(role.getDescn()); 
} 
} 
s = s.substring(0,(s.length() - 1)); 
editor.setAsText(s); 
editor.getValue(); 
return (ConfigAttributeDefinition) editor.getValue(); 
} 

public Collection getConfigAttributeDefinitions() { 

return roleList; 
} 

public boolean supports(Class clazz) { 
return true; 
} 

} 

相应的方法名保存在数据库中。

你可能感兴趣的:(Spring Security)