Spring Security教程(五)
来源:互联网 发布:老人摔倒报警器 知乎 编辑:程序博客网 时间:2024/05/29 16:37
在之前的几篇security教程中,资源和所对应的权限都是在xml中进行配置的,也就在http标签中配置intercept-url,试想要是配置的对象不多,那还好,但是平常实际开发中都往往是非常多的资源和权限对应,而且写在配置文件里面写改起来还得该源码配置文件,这显然是不好的。因此接下来,将用数据库管理资源和权限的对应关系。数据库还是接着之前的,用mysql数据库,因此也不用另外引入额外的jar包。
一 数据库表的设计
DROP TABLE IF EXISTS `resc`;CREATE TABLE `resc` ( `id` bigint(20) NOT NULL DEFAULT '0', `name` varchar(50) DEFAULT NULL, `res_type` varchar(50) DEFAULT NULL, `res_string` varchar(200) DEFAULT NULL, `descn` varchar(200) DEFAULT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;-- ------------------------------ Records of resc-- ----------------------------INSERT INTO `resc` VALUES ('1', '', 'URL', '/adminPage.jsp', '管理员页面');INSERT INTO `resc` VALUES ('2', '', 'URL', '/index.jsp', '');INSERT INTO `resc` VALUES ('3', null, 'URL', '/test.jsp', '测试页面');-- ------------------------------ Table structure for resc_role-- ----------------------------DROP TABLE IF EXISTS `resc_role`;CREATE TABLE `resc_role` ( `resc_id` bigint(20) NOT NULL DEFAULT '0', `role_id` bigint(20) NOT NULL DEFAULT '0', PRIMARY KEY (`resc_id`,`role_id`), KEY `fk_resc_role_role` (`role_id`), CONSTRAINT `fk_resc_role_role` FOREIGN KEY (`role_id`) REFERENCES `role` (`id`), CONSTRAINT `fk_resc_role_resc` FOREIGN KEY (`resc_id`) REFERENCES `resc` (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;-- ------------------------------ Records of resc_role-- ----------------------------INSERT INTO `resc_role` VALUES ('1', '1');INSERT INTO `resc_role` VALUES ('2', '1');INSERT INTO `resc_role` VALUES ('2', '2');INSERT INTO `resc_role` VALUES ('3', '3');-- ------------------------------ Table structure for role-- ----------------------------DROP TABLE IF EXISTS `role`;CREATE TABLE `role` ( `id` bigint(20) NOT NULL DEFAULT '0', `name` varchar(50) DEFAULT NULL, `descn` varchar(200) DEFAULT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;-- ------------------------------ Records of role-- ----------------------------INSERT INTO `role` VALUES ('1', 'ROLE_ADMIN', '管理员角色');INSERT INTO `role` VALUES ('2', 'ROLE_USER', '用户角色');INSERT INTO `role` VALUES ('3', 'ROLE_TEST', '测试角色');-- ------------------------------ Table structure for t_c3p0-- ----------------------------DROP TABLE IF EXISTS `t_c3p0`;CREATE TABLE `t_c3p0` ( `a` char(1) DEFAULT NULL) ENGINE=InnoDB DEFAULT CHARSET=utf8;-- ------------------------------ Records of t_c3p0-- ------------------------------ ------------------------------ Table structure for user-- ----------------------------DROP TABLE IF EXISTS `user`;CREATE TABLE `user` ( `id` bigint(20) NOT NULL DEFAULT '0', `username` varchar(50) DEFAULT NULL, `password` varchar(50) DEFAULT NULL, `status` int(11) DEFAULT NULL, `descn` varchar(200) DEFAULT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;-- ------------------------------ Records of user-- ----------------------------INSERT INTO `user` VALUES ('1', 'admin', 'admin', '1', '管理员');INSERT INTO `user` VALUES ('2', 'user', 'user', '1', '用户');INSERT INTO `user` VALUES ('3', 'test', 'test', '1', '测试');-- ------------------------------ Table structure for user_role-- ----------------------------DROP TABLE IF EXISTS `user_role`;CREATE TABLE `user_role` ( `user_id` bigint(20) NOT NULL DEFAULT '0', `role_id` bigint(20) NOT NULL DEFAULT '0', PRIMARY KEY (`user_id`,`role_id`), KEY `fk_user_role_role` (`role_id`), CONSTRAINT `fk_user_role_role` FOREIGN KEY (`role_id`) REFERENCES `role` (`id`), CONSTRAINT `fk_user_role_user` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;-- ------------------------------ Records of user_role-- ----------------------------INSERT INTO `user_role` VALUES ('1', '1');INSERT INTO `user_role` VALUES ('1', '2');INSERT INTO `user_role` VALUES ('2', '2');INSERT INTO `user_role` VALUES ('3', '3');user表中包含用户登陆信息,role角色表中包含授权信息,resc资源表中包含需要保护的资源。
二 实现从数据库中读取资源信息
Spring Security需要的数据无非就是pattern和access类似键值对的数据,就像配置文件中写的那样:
<intercept-url pattern="/login.jsp" access="IS_AUTHENTICATED_ANONYMOUSLY" />1<intercept-url pattern="/admin.jsp" access="ROLE_ADMIN" /><intercept-url pattern="/**" access="ROLE_USER" />
其实当项目启动时,Spring Security所做的就是在系统初始化时,将以上XML中的信息转换为特定的数据格式,而框架中其他组件可以利用这些特定格式的数据,用于控制之后的验证操作。现在我们将这些信息存储在数据库中,因此就要想办法从数据库中查询这些数据,所以根据security数据的需要,只需要如下sql语句就可以:
select re.res_string,r.name from role r,resc re,resc_role rr where r.id=rr.role_id and re.id=rr.resc_id在数据中执行这条语句做测试,得到如下结果:
这样的格式正是security所需要的数据。
三 构建一个数据库的操作的类
package com.zmc.demo;import java.sql.ResultSet;import java.sql.SQLException;import java.util.ArrayList;import java.util.Collection;import java.util.LinkedHashMap;import java.util.List;import javax.sql.DataSource;import org.springframework.jdbc.core.support.JdbcDaoSupport;import org.springframework.jdbc.object.MappingSqlQuery;import org.springframework.security.access.ConfigAttribute;import org.springframework.security.access.SecurityConfig;import org.springframework.security.web.access.intercept.DefaultFilterInvocationSecurityMetadataSource;import org.springframework.security.web.util.AntPathRequestMatcher;import org.springframework.security.web.util.RequestMatcher;/** * @classname JdbcRequestMapBulider * @author ZMC * @time 2017-1-10 * 查询资源和角色,并构建RequestMap */public class JdbcRequestMapBulider extends JdbcDaoSupport{//查询资源和权限关系的sql语句 private String resourceQuery = ""; public String getResourceQuery() {return resourceQuery;} //查询资源 public List<Resource> findResources() { ResourceMapping resourceMapping = new ResourceMapping(getDataSource(), resourceQuery); return resourceMapping.execute(); } //拼接RequestMap public LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> buildRequestMap() { LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestMap = new LinkedHashMap<>(); List<Resource> resourceList = this.findResources(); for (Resource resource : resourceList) {RequestMatcher requestMatcher = this.getRequestMatcher(resource.getUrl());List<ConfigAttribute> list = new ArrayList<ConfigAttribute>();list.add(new SecurityConfig(resource.getRole()));requestMap.put(requestMatcher, list);} return requestMap; } //通过一个字符串地址构建一个AntPathRequestMatcher对象 protected RequestMatcher getRequestMatcher(String url) { return new AntPathRequestMatcher(url); } public void setResourceQuery(String resourceQuery) { this.resourceQuery = resourceQuery; } /** * @classname Resource * @author ZMC * @time 2017-1-10 * 资源内部类 */ 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; } } }说明:
resourceQuery是查询数据的sql语句,该属性在配置bean的时候传入即可。
内部创建了一个resource来封装数据。
getRequestMatcher方法就是用来创建RequestMatcher对象的
buildRequestMap方法用来最后拼接成LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>>共security使用。
四 替换原有功能的切入点
在将这部之前,先得了解大概下security的运行过程,security实现控制的功能其实就是通过一系列的拦截器来实现的,当用户登陆的时候,会被AuthenticationProcessingFilter拦截,调用AuthenticationManager的实现类,同时AuthenticationManager会调用ProviderManager来获取用户验证信息,其中不同的Provider调用的服务不同,因为这些信息可以是在数据库上,可以是在LDAP服务器上,可以是xml配置文件上等,这个例子中就是为数据库;如果验证通过后会将用户的权限信息放到spring的全局缓存SecurityContextHolder中,以备后面访问资源时使用。当访问资源,访问url时,会通过AbstractSecurityInterceptor拦截器拦截,其中会调用FilterInvocationSecurityMetadataSource的方法来获取被拦截url所需的全部权限,其中FilterInvocationSecurityMetadataSource的常用的实现类为DefaultFilterInvocationSecurityMetadataSource,这个类中有个很关键的东西就是requestMap,也就是我们上面所得到的数据,在调用授权管理器AccessDecisionManager,这个授权管理器会通过spring的全局缓存SecurityContextHolder获取用户的权限信息,还会获取被拦截的url和被拦截url所需的全部权限,然后根据所配的策略,如果权限足够,则返回,权限不够则报错并调用权限不足页面。
根据源码debug跟踪得出,其实资源权限关系就放在DefaultFilterInvocationSecurityMetadataSource的requestMap,中的,这个requestMap就是我们JdbcRequestMapBulider.buildRequestMap()方法所需要的数据类型,因此,顺气自然就想到了我们自定义一个类继承FilterInvocationSecurityMetadataSource接口,将数据查出的数据放到requestMap中去。制定类MyFilterInvocationSecurityMetadataSource继承FilterInvocationSecurityMetadataSource和InitializingBean接口。
package com.zmc.demo;import java.util.Collection;import java.util.HashSet;import java.util.List;import java.util.Map;import java.util.Set;import javax.servlet.http.HttpServletRequest;import org.springframework.beans.factory.InitializingBean;import org.springframework.security.access.ConfigAttribute;import org.springframework.security.web.FilterInvocation;import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;import org.springframework.security.web.util.RequestMatcher;/** * @classname MyFilterInvocationSecurityMetadataSource * @author ZMC * @time 2017-1-10 */public class MyFilterInvocationSecurityMetadataSource implementsFilterInvocationSecurityMetadataSource, InitializingBean {private final static List<ConfigAttribute> NULL_CONFIG_ATTRIBUTE = null;// 资源权限集合private Map<RequestMatcher, Collection<ConfigAttribute>> requestMap;//查找数据库权限和资源关系private JdbcRequestMapBulider builder;/* * (non-Javadoc) * @see * org.springframework.security.access.SecurityMetadataSource#getAttributes * (java.lang.Object) * 更具访问资源的地址查找所需要的权限 */@Overridepublic Collection<ConfigAttribute> getAttributes(Object object)throws IllegalArgumentException {final HttpServletRequest request = ((FilterInvocation) object).getRequest();Collection<ConfigAttribute> attrs = NULL_CONFIG_ATTRIBUTE;for (Map.Entry<RequestMatcher, Collection<ConfigAttribute>> entry : requestMap.entrySet()) {if (entry.getKey().matches(request)) {attrs = entry.getValue();break;}}return attrs;}/* * (non-Javadoc) * * @see org.springframework.security.access.SecurityMetadataSource# * getAllConfigAttributes() * 获取所有的权限 */@Overridepublic Collection<ConfigAttribute> getAllConfigAttributes() {Set<ConfigAttribute> allAttributes = new HashSet<ConfigAttribute>();for (Map.Entry<RequestMatcher, Collection<ConfigAttribute>> entry : requestMap.entrySet()) {allAttributes.addAll(entry.getValue());}System.out.println("总共有这些权限:"+allAttributes.toString());return allAttributes;}/* * (non-Javadoc) * * @see * org.springframework.security.access.SecurityMetadataSource#supports(java * .lang.Class) */@Overridepublic boolean supports(Class<?> clazz) {return FilterInvocation.class.isAssignableFrom(clazz);}//绑定requestMapprotected Map<RequestMatcher, Collection<ConfigAttribute>> bindRequestMap() {return builder.buildRequestMap();}/* * (non-Javadoc) * * @see * org.springframework.beans.factory.InitializingBean#afterPropertiesSet() */@Overridepublic void afterPropertiesSet() throws Exception {this.requestMap = this.bindRequestMap();}public void refreshResuorceMap() {this.requestMap = this.bindRequestMap();}//get方法public JdbcRequestMapBulider getBuilder() {return builder;}//set方法public void setBuilder(JdbcRequestMapBulider builder) {this.builder = builder;}}说明:
requestMap这个属性就是用来存放资源权限的集合
builder为JdbcRequestMapBulider类型,用来查找数据库权限和资源关系
其他的代码中都有详细的注释
四 配置
<?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-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd"><http pattern="/login.jsp" security="none"></http><http auto-config="false"><form-login login-page="/login.jsp" default-target-url="/index.jsp"authentication-failure-url="/login.jsp?error=true" /><logout invalidate-session="true" logout-success-url="/login.jsp"logout-url="/j_spring_security_logout" /><!-- 通过配置custom-filter来增加过滤器,before="FILTER_SECURITY_INTERCEPTOR"表示在SpringSecurity默认的过滤器之前执行。 --><custom-filter ref="filterSecurityInterceptor" before="FILTER_SECURITY_INTERCEPTOR" /></http><!-- 数据源 --><beans:bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"destroy-method="close"><!-- 此为c3p0在spring中直接配置datasource c3p0是一个开源的JDBC连接池 --><beans:property name="driverClass" value="com.mysql.jdbc.Driver" /><beans:property name="jdbcUrl"value="jdbc:mysql://localhost:3306/springsecuritydemo?useUnicode=true&characterEncoding=UTF-8" /><beans:property name="user" value="root" /><beans:property name="password" value="" /><beans:property name="maxPoolSize" value="50"></beans:property><beans:property name="minPoolSize" value="10"></beans:property><beans:property name="initialPoolSize" value="10"></beans:property><beans:property name="maxIdleTime" value="25000"></beans:property><beans:property name="acquireIncrement" value="1"></beans:property><beans:property name="acquireRetryAttempts" value="30"></beans:property><beans:property name="acquireRetryDelay" value="1000"></beans:property><beans:property name="testConnectionOnCheckin" value="true"></beans:property><beans:property name="idleConnectionTestPeriod" value="18000"></beans:property><beans:property name="checkoutTimeout" value="5000"></beans:property><beans:property name="automaticTestTable" value="t_c3p0"></beans:property></beans:bean><beans:bean id="builder" class="com.zmc.demo.JdbcRequestMapBulider"> <beans:property name="dataSource" ref="dataSource" /> <beans:property name="resourceQuery"value="select re.res_string,r.name from role r,resc re,resc_role rr where r.id=rr.role_id and re.id=rr.resc_id" /> </beans:bean><!-- 认证过滤器 --><beans:bean id="filterSecurityInterceptor"class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor"><!-- 用户拥有的权限 --><beans:property name="accessDecisionManager" ref="accessDecisionManager" /><!-- 用户是否拥有所请求资源的权限 --><beans:property name="authenticationManager" ref="authenticationManager" /><!-- 资源与权限对应关系 --><beans:property name="securityMetadataSource" ref="securityMetadataSource" /></beans:bean><!-- 授权管理器 --><beans:bean class="com.zmc.demo.MyAccessDecisionManager" id="accessDecisionManager"></beans:bean><!--认证管理--><authentication-manager alias="authenticationManager"><authentication-provider><jdbc-user-service data-source-ref="dataSource" id="usersService"users-by-username-query="select username,password,status as enabled from user where username = ?"authorities-by-username-query="select user.username,role.name from user,role,user_role where user.id=user_role.user_id and user_role.role_id=role.id and user.username=?" /></authentication-provider></authentication-manager><!--自定义的切入点--><beans:bean id="securityMetadataSource"class="com.zmc.demo.MyFilterInvocationSecurityMetadataSource"> <beans:property name="builder" ref="builder"></beans:property></beans:bean></beans:beans>
1.http中的custom-filter是特别要注意的,就是通过这个标签来增加过滤器的,其中before="FILTER_SECURITY_INTERCEPTOR"表示在SpringSecurity默认的过滤器之前执行。
package com.zmc.demo;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.authentication.InsufficientAuthenticationException;import org.springframework.security.core.Authentication;import org.springframework.security.core.GrantedAuthority;/** * @classname MyAccessDecisionManager * @author ZMC * @time 2017-1-10 * */public class MyAccessDecisionManager implements AccessDecisionManager {/* (non-Javadoc) * @see org.springframework.security.access.AccessDecisionManager#decide(org.springframework.security.core.Authentication, java.lang.Object, java.util.Collection) * 该方法决定该权限是否有权限访问该资源,其实object就是一个资源的地址,authentication是当前用户的 * 对应权限,如果没登陆就为游客,登陆了就是该用户对应的权限 */@Overridepublic void decide(Authentication authentication, Object object,Collection<ConfigAttribute> configAttributes)throws AccessDeniedException, InsufficientAuthenticationException {if(configAttributes == null) { return; } //所请求的资源拥有的权限(一个资源对多个权限) Iterator<ConfigAttribute> iterator = configAttributes.iterator(); while(iterator.hasNext()) { ConfigAttribute configAttribute = iterator.next(); //访问所请求资源所需要的权限 String needPermission = configAttribute.getAttribute(); System.out.println("访问"+object.toString()+"需要的权限是:" + needPermission); //用户所拥有的权限authentication Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities(); for(GrantedAuthority ga : authorities) { if(needPermission.equals(ga.getAuthority())) { return; } } } //没有权限 throw new AccessDeniedException(" 没有权限访问! "); }@Overridepublic boolean supports(ConfigAttribute attribute) {// TODO Auto-generated method stubreturn true;}@Overridepublic boolean supports(Class<?> clazz) {// TODO Auto-generated method stubreturn true;}}
五 结果
admin能访问的页面有adminPage.jsp、index.jsp;user能访问的有index.jsp;test能访问的有test.jsp。
- Spring Security教程(五)
- spring security详解教程
- spring security 教程入门
- spring security教程
- Spring Security教程
- Spring Security教程目录
- Spring Security教程(一)
- Spring Security教程(二)
- Spring Security教程(三)
- Spring Security教程(四)
- Spring Security教程(六)
- Spring Security教程(七)
- Spring Security教程
- spring security教程
- spring security (五)Filter
- Spring Security 相关教程集合
- Spring Security 系列教程(2)
- 【Spring Security实战系列】Spring Security实战(五)
- 机器学习笔记——梯度下降:
- js分页页码显示逻辑实现的两种方法
- android和ios当前流行架构对比学习
- Android app图标在手机桌面显示过小
- 前端学习网站
- Spring Security教程(五)
- JPA学习笔记1——JPA基础
- JAVA设计模式(二)-创建型
- 【ife】任务三十三:听指令的小方块(一)
- JAVA中读取网络中的图片资源导入到EXCEL中
- Apache Kylin实践
- 微信小程序-自定义组件
- Java中 intValue,parseInt,Valueof 这三个关键字的区别
- 雨默默的