【Spring Security实战系列】Spring Security实战(五)

在之前的几篇security教程中,资源和所对应的权限都是在xml中进行配置的,也就在http标签中配置intercept-url,试想要是配置的对象不多,那还好,但是平常实际开发中都往往是非常多的资源和权限对应,而且写在配置文件里面写改起来还得该源码配置文件,这显然是不好的。因此接下来,将用数据库管理资源和权限的对应关系。数据库还是接着之前的,用mysql数据库,因此也不用另外引入额外的jar包。
一 数据库表的设计
数据库要提供给security的数据无非就是,资源(说的通俗点就是范围资源地址)和对应的权限,这里就有两张表,但是因为他们俩是多对多的关系,因此还要设计一张让这两张表关联起来的表,除此之外,还有一张用户表,有因为用户和角色也是多对多的关系,还要额外加一张用户和角色关联的表。这样总共下来就是五张表。
建表和添加数据的sql语句:
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 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 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 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类似键值对的数据,就像配置文件中写的那样:
1

其实当项目启动时,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
在数据中执行这条语句做测试,得到如下结果:

【Spring Security实战系列】Spring Security实战(五)_第1张图片
这样的格式正是security所需要的数据。
三 构建一个数据库的操作的类
虽然上述的数据符合security的需要,但是security将这种数据类型进行了封装,把它封装成Map>这样的类型,其中RequestMatcher接口就是我们数据库中的res_string,其实现类为AntPathRequestMatcher,构建一个这样的对象只要在new的时候传入res_string就可以了,Collection这里对象构建起来就也是类似的,构建一个ConfigAttribute对象只需要在其实现类SecurityConfig创建的时候传入角色的名字就可以。

1、ResourceMapping类代码如下:

package cn.quan.ssm.sec.entity;

import org.springframework.jdbc.object.MappingSqlQuery;

import javax.sql.DataSource;
import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * @auther zhangsq on 2017-9-6.
 */

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

    /**
     * //对结果集进行封装处理
     * @param rs
     * @param i
     * @return
     * @throws SQLException
     */
    protected Object mapRow(ResultSet rs, int i) throws SQLException {
        String url = rs.getString(1);
        String role = rs.getString(2);
        Resource resource = new Resource(url,role);
        return resource;
    }
}

2、Resource类代码如下:

package cn.quan.ssm.sec.entity;

/**
 * @auther zhangsq on 2017-9-6.
 * 资源信息类
 */

public 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 void setUrl(String url) {
        this.url = url;
    }

    public String getRole() {
        return role;
    }

    public void setRole(String role) {
        this.role = role;
    }
}

3、JdbcRequestMapBulider类代码如下:

package cn.quan.ssm.sec;

import cn.quan.ssm.sec.entity.Resource;
import cn.quan.ssm.sec.entity.ResourceMapping;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;

import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;

/**
 * @auther zhangsq on 2017-9-6.
 * 查询资源和角色,并构建RequestMap
 */

public class JdbcRequestMapBulider extends JdbcDaoSupport{

    //查询数据的sql语句,该属性在配置bean的时候传入即可。
    private String resourceQuery = "";

    public String getResourceQuery() {
        return resourceQuery;
    }

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

    //查询资源的方法
    public List findResourceData(){
        ResourceMapping resourceMapping = new ResourceMapping(getDataSource(),resourceQuery);

        return resourceMapping.execute();
    }

    /**
     * 拼接RequestMap
     * buildRequestMap方法用来最后拼接成LinkedHashMap>共security使用。
     * @return
     */
    public LinkedHashMap> buildRequestMap(){
        LinkedHashMap> requestMap =
                new LinkedHashMap>();
        List resourcesList = this.findResourceData();//查询资源信息
        for(Resource resource : resourcesList){
            RequestMatcher requestMatcher = this.getRequestMatcher(resource.getUrl());
            List list = new ArrayList();
            list.add(new SecurityConfig(resource.getRole()));
            requestMap.put(requestMatcher,list);
        }
        return requestMap;
    }

    //通过一个字符串地址构建一个AntPathRequestMatcher对象
    //getRequestMatcher方法就是用来创建RequestMatcher对象的
    protected RequestMatcher getRequestMatcher(String url){
        return new AntPathRequestMatcher(url);
    }
}

说明:
  • resourceQuery是查询数据的sql语句,该属性在配置bean的时候传入即可。
  • 内部创建了一个resource来封装数据。
  • getRequestMatcher方法就是用来创建RequestMatcher对象的
  • buildRequestMap方法用来最后拼接成LinkedHashMap>共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 cn.quan.ssm.sec.dao;

import cn.quan.ssm.sec.JdbcRequestMapBulider;
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.matcher.RequestMatcher;

import javax.servlet.http.HttpServletRequest;
import java.util.*;

/**
 * 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接口。
 * @auther zhangsq on 2017-9-6.
 */

public class MyFilterInvocationSecurityMetadataSource
        implements FilterInvocationSecurityMetadataSource,InitializingBean {

    private final static List NULL_CONFIG_ATTRIBUTE = null;

    //资源权限集合
    private Map> requestMap;

    //查找数据库权限和资源的关系
    private JdbcRequestMapBulider bulider;



    /**
     * 更具访问资源的地址查找所需要的权限
     * @param object
     * @return
     * @throws IllegalArgumentException
     */
    public Collection getAttributes(Object object) throws IllegalArgumentException {
        final HttpServletRequest request = ((FilterInvocation) object) .getRequest();
        Collection atts = NULL_CONFIG_ATTRIBUTE;
        for(Map.Entry> entry : requestMap.entrySet()){
            if(entry.getKey().matches(request)){
                atts = entry.getValue();
                break;
            }

        }
        return atts;
    }

    /**
     * 获取所有的权限
     * @return
     */
    public Collection getAllConfigAttributes() {
        Set attributes = new HashSet();
        for(Map.Entry> entry : requestMap.entrySet()){
            attributes.addAll(entry.getValue());
        }
        System.out.println("总共有这些权限:"+attributes.toString());
        return attributes;
    }

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

    //绑定requestMap
    protected Map> bindRequestMap() {

        return bulider.buildRequestMap();
    }

    public void afterPropertiesSet() throws Exception {
        this.requestMap = this.bindRequestMap();
    }

    public void refreshResuorceMap() {
        this.requestMap = this.bindRequestMap();
    }

    public JdbcRequestMapBulider getBulider() {
        return bulider;
    }

    public void setBulider(JdbcRequestMapBulider bulider) {
        this.bulider = bulider;
    }
}

说明:

  1. requestMap这个属性就是用来存放资源权限的集合
  2. builder为JdbcRequestMapBulider类型,用来查找数据库权限和资源关系
  3. 其他的代码中都有详细的注释
四 配置




    
    
    
    
    
    
    

    
    
    

    

        
        
        
        
       


        

        
        

        
        

        
        

        
        
            
        

        

    

    
    

    
    
    

    
    
        
        
    

    
        
        
        
        
        
        
    

    
    


    
    
    
        
            

        
    

    
    
        
    

1.http中的custom-filter是特别要注意的,就是通过这个标签来增加过滤器的,其中before="FILTER_SECURITY_INTERCEPTOR"表示在SpringSecurity默认的过滤器之前执行。

2.在配置builder时候,resourceQuery就是要查询的sql语句,dataSource为数据源。其他的如authenticationManager在之前的博客配置中就有详细讲解。
3.在配置认证过滤器的时候,accessDecisionManager,authenticationManager,securityMetadataSource这三个属性是必填项,若缺失会报错。其中authenticationManager就是authentication-manager标签,securityMetadataSource是自定义的MyFilterInvocationSecurityMetadataSource,authenticationManager这里还没有定义,因此再创建一个类叫MyAccessDecisionManager,代码如下:

package cn.quan.ssm.sec.dao;

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;

import java.util.Collection;
import java.util.Iterator;

/**
 * @auther zhangsq on 2017-9-6.
 */

public class MyAccessDecisionManager implements AccessDecisionManager {

    /**
     * 该方法决定该权限是否有权限访问该资源,其实object就是一个资源的地址,authentication是当前用户的对应权限,
     * 如果没登陆就为游客,登陆了就是该用户对应的权限
     * @param authentication
     * @param object
     * @param configAttributes
     * @throws AccessDeniedException
     * @throws InsufficientAuthenticationException
     */
    public void decide(Authentication authentication, Object object, Collection configAttributes)
            throws AccessDeniedException, InsufficientAuthenticationException {
        if(null == configAttributes){
            return;
        }
        //所请求的资源拥有的权限(一个资源对多个权限)
        Iterator iterable = configAttributes.iterator();
        while (iterable.hasNext()){
            ConfigAttribute configAttribute = iterable.next();
            //访问所请求的资源所需要的权限
            String needPermission = configAttribute.getAttribute();
            System.out.println("访问"+object.toString()+"需要的权限是:"+needPermission);

            //用户所拥有的权限authentication
            Collection authorities = authentication.getAuthorities();
            for (GrantedAuthority ga : authorities) {
                if (needPermission.equals(ga.getAuthority())) {
                    return;
                }
            }
        }
        //没有权限
        throw new AccessDeniedException("没有权限访问!");
    }

    public boolean supports(ConfigAttribute configAttribute) {
        return true;
    }

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

五 结果
用户对应的角色,和角色能访问的资源
【Spring Security实战系列】Spring Security实战(五)_第2张图片
admin能访问的页面有adminPage.jsp、index.jsp;user能访问的有index.jsp;test能访问的有test.jsp。
分别admin用户、user用户及test用户测试一下,效果与之前的实战中类似。这里就不在截图了。

你可能感兴趣的:(spring,security)