Spring下的权限框架 spring security总结 

[code] 

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; 

} 


} 

[/code]