在以前的几篇security教程中,资源和所对应的权限都是在xml中进行配置的,也就在http标签中配置intercept-url,试想要是配置的对象很少,那还好,可是日常实际开发中都每每是很是多的资源和权限对应,并且写在配置文件里面写改起来还得该源码配置文件,这显然是很差的。所以接下来,将用数据库管理资源和权限的对应关系。数据库仍是接着以前的,用mysql数据库,所以也不用另外引入额外的jar包。java
数据库要提供给security的数据无非就是,资源(说的通俗点就是范围资源地址)和对应的权限,这里就有两张表,可是由于他们俩是多对多的关系,所以还要设计一张让这两张表关联起来的表,除此以外,还有一张用户表,有由于用户和角色也是多对多的关系,还要额外加一张用户和角色关联的表。这样总共下来就是五张表。下面就是对应的模型图:mysql
建表和添加数据的sql语句:web
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
Spring Security须要的数据无非就是pattern和access相似键值对的数据,就像配置文件中写的那样:sql
<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所须要的数据。服务器
虽然上述的数据符合security的须要,可是security将这种数据类型进行了封装,把它封装成 Map
/**
* @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的时候传入便可。session
内部建立了一个resource来封装数据。
getRequestMatcher方法就是用来建立RequestMatcher对象的
buildRequestMap方法用来最后拼接成LinkedHashMap
在将这部以前,先得了解大概下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接口。
/**
* @classname MyFilterInvocationSecurityMetadataSource
* @author ZMC
* @time 2017-1-10
*/
public class MyFilterInvocationSecurityMetadataSource implements
FilterInvocationSecurityMetadataSource, 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)
* 更具访问资源的地址查找所须要的权限
*/
@Override
public 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()
* 获取全部的权限
*/
@Override
public 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)
*/
@Override
public boolean supports(Class<?> clazz) {
return FilterInvocation.class.isAssignableFrom(clazz);
}
//绑定requestMap
protected Map<RequestMatcher, Collection<ConfigAttribute>> bindRequestMap() {
return builder.buildRequestMap();
}
/*
* (non-Javadoc)
*
* @see
* org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
*/
@Override
public 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>复制代码
说明
/**
* @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是当前用户的
* 对应权限,若是没登录就为游客,登录了就是该用户对应的权限
*/
@Override
public 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(" 没有权限访问! ");
}
@Override
public boolean supports(ConfigAttribute attribute) {
// TODO Auto-generated method stub
return true;
}
@Override
public boolean supports(Class<?> clazz) {
// TODO Auto-generated method stub
return true;
}
}复制代码
用户对应的角色,和角色能访问的资源
admin能访问的页面有adminPage.jsp、index.jsp;user能访问的有index.jsp;test能访问的有test.jsp。
先测试admin用户:
user用户测试:
test用户测试:
微信公众号关注:ByteZ,获取更多学习资料