# 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数据库) ## 将资源信息放入数据库中 # 表结构 # /* 用户表 */ # 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); # # 当用户登录时,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生成的异常 # # 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); # %> # 或者加入 // 刷新SpringSecurity权限信息 private void flushSpringSecurity() { try { FactoryBean factoryBean = (FactoryBean)SpringUtil.getBean("&userSecurityDefinitionSource"); FilterInvocationDefinitionSource filterInvocationDefinitionSource = (FilterInvocationDefinitionSource) factoryBean.getObject(); FilterSecurityInterceptor filterSecurityInterceptor = (FilterSecurityInterceptor) SpringUtil.getBean("filterSecurityInterceptor"); filterSecurityInterceptor.setObjectDefinitionSource(filterInvocationDefinitionSource); } catch (Exception e) { e.printStackTrace(); } }