[Spring ---- Security] V. custom filters

Resources and corresponding permissions are configured in the xml before a few security tutorial, configure it at http label Intercept-url , imagine if the small object configuration, and that good, but usually the actual are often correspond to the development of a lot of resources and authority, and written in a configuration file written inside it had to change the source code configuration file, which is obviously not good. Hence Next, the correspondence between the database management resources and privileges. Database or before Next, mysql database, and therefore without additional introduction of additional jar package.

First, the database table design

Database data security to be provided to nothing more than, resources (the popular point is to say the address range of resources) and the corresponding rights, where there are two tables, but because they are two of many relationships, and therefore have to devise a let the two tables associated with table together, in addition, there is a user table , have since users and roles is many to many relationships, but also add an extra user and role associated table . This is down a total of five tables. The following is a model corresponding to FIG:

 

Built sql statement tables and add data:

DROP TABLE IF EXISTS `resc`;
CREATE TABLE `resc` (
  `id` int(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', '/page/admin.jsp', '管理员页面');
INSERT INTO `resc` VALUES (2, '', 'URL', '/page/user.jsp', '用户页面');
INSERT INTO `resc` VALUES (3, null, 'URL', '/page/test.jsp', '测试页面');
 
-- ----------------------------
-- Table structure for resc_role
-- ----------------------------
DROP TABLE IF EXISTS `resc_role`;
CREATE TABLE `resc_role` (
  `resc_id` int(20) NOT NULL DEFAULT '0',
  `role_id` int(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` int(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` int(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', '123', 1, '管理员');
INSERT INTO `user` VALUES (2, 'user', '123', 1, '用户');
INSERT INTO `user` VALUES (3, 'test', '123', 1, '测试');
 
-- ----------------------------
-- Table structure for user_role
-- ----------------------------
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role` (
  `user_id` int(20) NOT NULL DEFAULT '0',
  `role_id` int(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 table contains the user login information, role role table contains authorization information, resc resource table included the need to protect resources.

Second, to achieve read resource information from the database

Spring Security data needed nothing more than a similar pattern and access to key data, like the configuration file as written:

<intercept-url pattern="/login.jsp" access="IS_AUTHENTICATED_ANONYMOUSLY" />1
<intercept-url pattern="/admin.jsp" access="ROLE_ADMIN" />
<intercept-url pattern="/**" access="ROLE_USER" />

In fact, when the project started, Spring Security is done at system initialization, converts the information in the XML above for a specific data format, and other components of the frame can use these data in a specific format, for verifying the operation after control . Now we store this information in a database, so the query data from the database to find a way, so necessary security data, just as you can sql statement:

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

The statement is executed in the data to do the test results are as follows:

 


This format is the data security needs.

Build a database operation three classes

Although the above data to meet the needs of security, but the security of this type of data is encapsulated, it packaged into Map <RequestMatcher, Collection <ConfigAttribute >> of the type in which the interface is RequestMatcher our database res_string, its implementation class as AntPathRequestMatcher, a building such objects as long as the new incoming res_string time on it, Collection <ConfigAttribute> here the object is built up on a similar build a ConfigAttribute objects only need to pass the time its role in the implementation class created SecurityConfig name on it. code show as below:

package com.sunny.auth;

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.util.AntPathRequestMatcher;
import org.springframework.security.web.util.RequestMatcher;

public class JdbcRequestMapBulider extends JdbcDaoSupport{
    //查询资源和权限关系的sql语句
    private String resourceQuery = "";
    
    //查询资源
    @SuppressWarnings("unchecked")
    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 String getResourceQuery() {
        return resourceQuery;
    }
    public void setResourceQuery(String resourceQuery) {
        this.resourceQuery = resourceQuery;
    }
    
    /**
     * 内部类,用于封装访问地址和权限
     * @ClassName: Resource 
     * @Description: TODO(这里用一句话描述这个类的作用) 
     * @author Sunny
     * @date 2018年7月4日 下午2:42:59 
     *
     */
    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;
        }
    }
    
    @SuppressWarnings("rawtypes")
    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;
        }
    }
}

Description:

  • resourceQuery is sql statement to query data, the property passed to the bean configuration time.
  • Internal to create a resource package data.
  • getRequestMatcher method is used to create RequestMatcher object
  • buildRequestMap method is used to splice the last LinkedHashMap <RequestMatcher, Collection <ConfigAttribute >> for security use.

Fourth, replace the original entry point function

In this previously, come to know about the process running under the security, security control functions to achieve is actually achieved through a series of interceptors, when the user login, will be AuthenticationProcessingFilter intercepted calls AuthenticationManager implementation class, Meanwhile AuthenticationManager calls ProviderManager to obtain user authentication information, call different among different Provider service because that information may be on the database, it can be in LDAP (lightweight directory access protocol) server, which can be fine xml configuration file , the database is in this example; if the verification passes the user's permission information into the global cache spring SecurityContextHolder used in order to prepare the back access resources. When accessing resources, visit url, will pass AbstractSecurityInterceptor interceptor to intercept, which calls FilterInvocationSecurityMetadataSource way to get all the required permissions intercepted url, which FilterInvocationSecurityMetadataSource common implementation class is DefaultFilterInvocationSecurityMetadataSource , this class has a very critical thing is Requestmap , that is, the data we obtained above, calling authorization ManagerThe AccessDecisionManager , the License Manager will be cached by the global spring SecurityContextHolder privilege user access to information, but also get intercepted and full access was blocked url url you want, and in accordance with the distribution policy, if sufficient authority, then return, enough authority to call the error and insufficient permissions page.

According to the source debug tracking results, in fact, resource permissions relations on DefaultFilterInvocationSecurityMetadataSource of requestMap , in this requestMap is our JdbcRequestMapBulider.buildRequestMap () data type methods needed, so we thought of a custom class that inherits FilterInvocationSecurityMetadataSource interfaces, isolated data into the data requestMap go. Develop class MyFilterInvocationSecurityMetadataSource inherit FilterInvocationSecurityMetadataSource and InitializingBean interface.

package com.sunny.auth;

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;

public class MyFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource, InitializingBean{
    private final static List<ConfigAttribute> NULL_CONFIG_ATTRIBUTE = null;
    // 资源权限集合
    private Map<RequestMatcher, Collection<ConfigAttribute>> requestMap;
    
    //查找数据库权限和资源关系
    private JdbcRequestMapBulider builder;
    
    /*
     * 更具访问资源的地址查找所需要的权限
     */
    @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;
    }
 
    /*
     * 获取所有的权限
     */
    @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;
    }
    
    @Override
    public boolean supports(Class<?> clazz) {
        return FilterInvocation.class.isAssignableFrom(clazz);
    }
    //绑定requestMap
    protected Map<RequestMatcher, Collection<ConfigAttribute>> bindRequestMap() {
        return builder.buildRequestMap();
    }
 
    @Override
    public void afterPropertiesSet() throws Exception {
        this.requestMap = this.bindRequestMap();
    }
 
    public void refreshResuorceMap() {
        this.requestMap = this.bindRequestMap();
    }
 
    /******************* GET & SET *******************************/
    public JdbcRequestMapBulider getBuilder() {
        return builder;
    }
    public void setBuilder(JdbcRequestMapBulider builder) {
        this.builder = builder;
    }
}

Description:

  • requestMap This attribute is used to store a collection of resource rights
  • builder is JdbcRequestMapBulider type, used to find the database permissions and resource relationship
  • Other code has detailed notes

5, configuration files

spring-dataSource.xml remain unchanged

<?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">
    <!-- 数据源 -->
    <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/springsecurity?useUnicode=true&amp;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:beans>

spring-context.xml modified as follows

<?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="/page/login.jsp" security="none"></http>
    <http auto-config="false">
        <form-login login-page="/page/login.jsp" default-target-url="/page/admin.jsp" authentication-failure-url="/page/login.jsp?error=true" />
        <logout invalidate-session="true" logout-success-url="/page/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="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 id="accessDecisionManager" class="com.sunny.auth.MyAccessDecisionManager">
    </beans:bean>
    
     <!--自定义的切入点-->
    <beans:bean id="securityMetadataSource" class="com.sunny.auth.MyFilterInvocationSecurityMetadataSource">
        <beans:property name="builder" ref="builder"></beans:property>
    </beans:bean>
    
    <beans:bean id="builder" class="com.sunny.auth.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>
    
     <!--认证管理-->
    <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:beans>

1.http the custom-filter is particularly to be noted, that the tag is increased by a filter, wherein before = "FILTER_SECURITY_INTERCEPTOR" indicates SpringSecurity performed before the default filter.

2. In the configuration builder time, resourceQuery is sql statement to query, dataSource data source.

3. In the filter configuration authentication when, accessDecisionManager , the authenticationManager , securityMetadataSource these three attributes is required, if the absence of error. Wherein authenticationManager is authentication-manager labels, securityMetadataSource is custom MyFilterInvocationSecurityMetadataSource , authenticationManager there has not been defined, then create a so called class MyAccessDecisionManager , code is as follows:

package com.sunny.auth;

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;

public class MyAccessDecisionManager implements AccessDecisionManager{
    /* 
     * 该方法决定该权限是否有权限访问该资源,其实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) {
        return true;
    }
 
    @Override
    public boolean supports(Class<?> clazz) {
        return true;
    }
}

pom.xml increase

            <!-- j2ee -->
            <dependency>
                <groupId>javax.servlet</groupId>
                <artifactId>javax.servlet-api</artifactId>
                <version>3.1.0</version>
            </dependency>        

The default page is still web.xml

Five results

Page admin can have access admin.jsp, user.jsp;

user can have access user.jsp;

test access has test.jsp.

 

If the permissions will be prompted to "no access!"

 

 

Guess you like

Origin blog.csdn.net/ningjiebing/article/details/89411070