`

Spring Security 3.0数据库动态实现权限控制

阅读更多
第一次接触Spring Security,在网上找了很多资料,有简单示例的,就是把用户名及权限信息配置在XML里面,但这种不太适合一般的项目!有些资料是翻译源代码讲解各个过滤器的作用以及其工作原理!由于项目时间紧所以求成心切的我看这些资料的时候总是很匆忙的跳步来看,到最后反而稀里糊涂,脑子里更乱,真心的希望有个狄仁杰中的元芳能在身边,这样我还能听取下他的意见:"元芳,你怎么看!"这两天静下来把之前看的那些详细看了看,最后感觉这玩意儿也不是那么那么让人抓狂!

开发环境 SpringIDE 3.0、Spring Security 3.0.2、myibatis、Maven、MySql
最前面的spring配置以及Maven管理架包、myibatis的配置就不说了
首先:
1、需要将所需要的JAR包在pom中配置

2、在web.xml中添加spring security的过滤链
	<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>

3、添加applicationContext-security.xml到根目录下
内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<b:beans xmlns="http://www.springframework.org/schema/security"
 xmlns:b="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/security 
    http://www.springframework.org/schema/security/spring-security-3.0.xsd">

	<!-- <global-method-security pre-post-annotations="enabled" /> -->
	
	<!-- access-denied-page配置访问失败页面 -->
	<http auto-config="true" access-denied-page="/accessDenied.htm">
		<!-- 不要过滤图片等静态资源,其中**代表可以跨越目录,*不可以跨越目录。 -->
		  <intercept-url pattern="/**/*.jpg" filters="none" />
		  <intercept-url pattern="/**/*.png" filters="none" />
		  <intercept-url pattern="/**/*.gif" filters="none" />
		  <intercept-url pattern="/**/*.css" filters="none" />
		  <intercept-url pattern="/**/*.js" filters="none" />
		  <intercept-url pattern="/login.htm*" filters="none"/>
		  <!-- <intercept-url pattern="/index.htm" access="ROLE_USER,ROLE_ADMIN"/>
 		  <intercept-url pattern="/header.htm" access="ROLE_USER,ROLE_ADMIN"/>
		  <intercept-url pattern="/left.htm" access="ROLE_USER,ROLE_ADMIN"/> -->
		  <intercept-url pattern="/**" access="ROLE_USER"/>
		<!-- 配置登录页面 -->
		<form-login login-page="/login.htm"
			default-target-url="/" authentication-failure-url="/login.htm?error=true"
			login-processing-url="/springSecurityLogin"/>
		<!--"记住我"功能,采用持久化策略(将用户的登录信息存放在数据库表中) -->
		<!-- <remember-me/> -->
		<!-- 用户退出的跳转页面 -->
		<logout logout-success-url="/login.htm" invalidate-session="true" 
			logout-url="/logout" />
		<!-- 会话管理,设置最多登录异常,error-if-maximum-exceeded = false为第二次登录就会使前一个登录失效 -->
		 <session-management invalid-session-url="/error/errorPage.jsp">
			<concurrency-control max-sessions="1" error-if-maximum-exceeded="false" />
		</session-management>
		
		<!--添加自定义的过滤器 放在FILTER_SECURITY_INTERCEPTOR之前有效  -->
		<custom-filter ref="customFilterSecurityInterceptor" before="FILTER_SECURITY_INTERCEPTOR" />
	</http>
	
	<!-- 配置认证管理器 -->
	<authentication-manager alias="authenticationManager">
		<authentication-provider user-service-ref="customUserDetailsService">
		    <password-encoder hash="md5"/>
			<!-- <jdbc-user-service data-source-ref="dataSource"/> -->
		</authentication-provider>
	</authentication-manager>
</b:beans>

上面是XML文件,需要说明的是:intercept-url节点需至少有一个拦截的,如果全部过滤图片或者是样式这些,spring框架启动的时候,就不会拦截任何URL,因为你没有配置相关的拦截的intercept-url,所以它就以为任何人都可以访问整个资源!


其中的节点用途都有说明,就不再啰嗦
4、创建CustomUserDetailsService、CustomAccessDecisionManager、CustomFilterSecurityInterceptor、CustomInvocationSecurityMetadataSource
这4个类我定义的接口,有相应的实现类,在实现动态权限控制中这4个类都会用到


4个接口代码如下:


I、CustomUserDetailsService接口类代码如下:
package com.doone.wisdom.security.service;

import org.springframework.security.core.userdetails.UserDetailsService;

/** 
 * TODO(这里用一句话描述这个类的职责).
 * @ClassName: CustomUserDetailsService
 * @author Johnny_L_Q
 */
public interface CustomUserDetailsService extends UserDetailsService{

    
}



II、CustomAccessDecisionManager接口类的代码如下:
package com.doone.wisdom.security.service;

import org.springframework.security.access.AccessDecisionManager;

/** 
 * TODO(这里用一句话描述这个类的职责).
 * @ClassName: CustomAccessDecisionManager
 * @author Johnny_L_Q
 */
public interface CustomAccessDecisionManager extends AccessDecisionManager{

    
}


III、CustomFilterSecurityInterceptor接口类的代码如下:
package com.doone.wisdom.security.service;

import javax.servlet.Filter;

/** 
 * TODO(这里用一句话描述这个类的职责).
 * @ClassName: CustomFilterSecurityInterceptor
 * @author Johnny_L_Q
 */
public interface CustomFilterSecurityInterceptor extends Filter{

}


IV、CustomInvocationSecurityMetadataSource接口类的代码如下:
package com.doone.wisdom.security.service;

import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;

/** 
 * TODO(这里用一句话描述这个类的职责).
 * @ClassName: CustomInvocationSecurityMetadataSource
 * @author Johnny_L_Q
 */
public interface CustomInvocationSecurityMetadataSource extends FilterInvocationSecurityMetadataSource{

}



以下相应接口类的实现类代码:


a、CustomUserDetailsServiceImpl 实现 CustomUserDetailsService接口,CustomUserDetailsService接口类继承自 org.springframework.security.core.userdetails.UserDetailsService,这个类主要是处理用户登录信息,在用户输入用户名和密码后,spring security会带着用户名调用类里面的loadUserByUsername(usrename)方法,在这里个人理解是,它没有用密码作为验证条件是通过用户名查出用户信息,然后把数据库中查出的用户密码和刚刚用户输入的存储在session中的密码做比较,然后判断该用户是否合法!
package com.doone.wisdom.security.impl;

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

import javax.annotation.Resource;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.GrantedAuthorityImpl;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import com.doone.wisdom.dao.security.UserDao;
import com.doone.wisdom.entity.security.RoleEntity;
import com.doone.wisdom.entity.security.UserEntity;
import com.doone.wisdom.security.service.CustomUserDetailsService;

/** 
 * TODO(这里用一句话描述这个类的职责).
 * @ClassName: CustomUserDetailsServiceImpl
 * @author Johnny_L_Q
 */
@Service("customUserDetailsService")
public class CustomUserDetailsServiceImpl implements CustomUserDetailsService {

    /**
     * @Fields serialVersionUID : TODO(用一句话描述这个变量表示什么).
     */
    private static final long serialVersionUID = 1L;
    
    @Resource
    private UserDao userDao;
    

    /*
     * 根据用户名判断是否存在
     * <p>Title: loadUserByUsername</p>
     * <p>Description: </p>
     * @param arg0
     * @return
     * @throws UsernameNotFoundException
     * @see org.springframework.security.core.userdetails.UserDetailsService#loadUserByUsername(java.lang.String)
     */
    @Override
    public UserDetails loadUserByUsername(String username)
            throws UsernameNotFoundException {
        UserEntity user = userDao.getUser(username);
        
        if (null == user) {
            throw new UsernameNotFoundException("用户" + username + "不存在");
        }
        
        
        Collection<GrantedAuthority> auths = new ArrayList<GrantedAuthority>();
        List<String> list = userDao.getAuthoritiesByUsername(username);
        for (int i = 0; i < list.size(); i++) {
            auths.add(new GrantedAuthorityImpl(list.get(i)));
            System.out.println("loadUserByUsername : " + list.get(i));
        }
      //因为UserEntity实现了UserDetails,所以也可以直接返回user
        return new User(username, user.getPassword(), true, true, true, true, auths);
    }

}



b、CustomAccessDecisionManagerImpl 实现 CustomAccessDecisionManager接口,  CustomAccessDecisionManager接口类继承自 org.springframework.security.access.AccessDecisionManager
这个类主要是处理用户在访问某个URL的时候,就会通过访问该类的权限与登录用户所拥有的权限做比较,如果拥有权限,那你就可以到访问外星文明了,如果没有权限,那你就要被遣送回非洲,还会抛一个异常告诉你,你已经被遣送回非洲!
package com.doone.wisdom.security.impl;

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

import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl;
import org.springframework.security.provisioning.JdbcUserDetailsManager;
import org.springframework.stereotype.Service;

import com.doone.wisdom.security.service.CustomAccessDecisionManager;

/** 
 * 
 * AccessdecisionManager在Spring security中是很重要的。 
 *  
 * 在验证部分简略提过了,所有的Authentication实现需要保存在一个GrantedAuthority对象数组中。 这就是赋予给主体的权限。 
 * GrantedAuthority对象通过AuthenticationManager 保存到 
 * Authentication对象里,然后从AccessDecisionManager读出来,进行授权判断。 
 *  
 * Spring Security提供了一些拦截器,来控制对安全对象的访问权限,例如方法调用或web请求。 
 * 一个是否允许执行调用的预调用决定,是由AccessDecisionManager实现的。 这个 AccessDecisionManager 
 * 被AbstractSecurityInterceptor调用, 它用来作最终访问控制的决定。 
 * 这个AccessDecisionManager接口包含三个方法: 
 *  
 * void decide(Authentication authentication, Object secureObject, 
 * List<ConfigAttributeDefinition> config) throws AccessDeniedException; boolean 
 * supports(ConfigAttribute attribute); boolean supports(Class clazz); 
 *  
 * 从第一个方法可以看出来,AccessDecisionManager使用方法参数传递所有信息,这好像在认证评估时进行决定。 
 * 特别是,在真实的安全方法期望调用的时候,传递安全Object启用那些参数。 比如,让我们假设安全对象是一个MethodInvocation。 
 * 很容易为任何Customer参数查询MethodInvocation, 
 * 然后在AccessDecisionManager里实现一些有序的安全逻辑,来确认主体是否允许在那个客户上操作。 
 * 如果访问被拒绝,实现将抛出一个AccessDeniedException异常。 
 *  
 * 这个 supports(ConfigAttribute) 方法在启动的时候被 
 * AbstractSecurityInterceptor调用,来决定AccessDecisionManager 
 * 是否可以执行传递ConfigAttribute。 supports(Class)方法被安全拦截器实现调用, 
 * 包含安全拦截器将显示的AccessDecisionManager支持安全对象的类型。  
 * TODO(这里用一句话描述这个类的职责).
 * @ClassName: CustomAccessDecisionManagerImpl
 * @author Johnny_L_Q
 */
@Service("customAccessDecisionManager")
public class CustomAccessDecisionManagerImpl implements
        CustomAccessDecisionManager {

    /*
     * <p>Title: decide</p>
     * <p>Description: </p>
     * @param arg0
     * @param arg1
     * @param arg2
     * @throws AccessDeniedException
     * @throws InsufficientAuthenticationException
     * @see org.springframework.security.access.AccessDecisionManager
     * #decide(org.springframework.security.core.Authentication, java.lang.Object, java.util.Collection)
     */
    @Override
    public void decide(Authentication authentication, Object object,
            Collection<ConfigAttribute> configAttributes) throws AccessDeniedException,
            InsufficientAuthenticationException {
        if (null == configAttributes) {
            return;
        }
        
        Iterator<ConfigAttribute> cons = configAttributes.iterator();
        
        while(cons.hasNext()){
            ConfigAttribute ca = cons.next();
            String needRole = ((SecurityConfig) ca).getAttribute();
            //gra 为用户所被赋予的权限,needRole为访问相应的资源应具有的权限
            for (GrantedAuthority gra : authentication.getAuthorities()) {
                if (needRole.trim().equals(gra.getAuthority().trim())) {
                    return;
                }
            }
        }
        throw new AccessDeniedException("Access Denied");
    }

    /*
     * <p>Title: supports</p>
     * <p>Description: </p>
     * @param arg0
     * @return
     * @see org.springframework.security.access.AccessDecisionManager#supports(org.springframework.security.access.ConfigAttribute)
     */
    @Override
    public boolean supports(ConfigAttribute arg0) {
        // TODO Auto-generated method stub
        return true;
    }

    /*
     * <p>Title: supports</p>
     * <p>Description: </p>
     * @param arg0
     * @return
     * @see org.springframework.security.access.AccessDecisionManager#supports(java.lang.Class)
     */
    @Override
    public boolean supports(Class<?> arg0) {
        // TODO Auto-generated method stub
        return true;
    }
}


c、CustomFilterSecurityInterceptorImpl 实现 CustomFilterSecurityInterceptor接口, CustomFilterSecurityInterceptor接口类继承 javax.servlet.Filter
该类的目的是拦截的入口,首先启动的时候会注入注入里面的属性,然后当用户访问你配置过的URL的时候,就会被拦截进入到doFilter方法中,在调用infoke()方法进入到CustomAccessDecisionManagerImpl.decide()方法中将访问的url与配置的url的权限做比较
package com.doone.wisdom.security.impl;

import java.io.IOException;

import javax.annotation.Resource;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.SecurityMetadataSource;
import org.springframework.security.access.intercept.AbstractSecurityInterceptor;
import org.springframework.security.access.intercept.InterceptorStatusToken;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.stereotype.Service;

import com.doone.wisdom.security.service.CustomFilterSecurityInterceptor;

/**  
 * 该过滤器的主要作用就是通过spring著名的IoC生成securityMetadataSource。 
 * securityMetadataSource相当于本包中自定义的CostomInvocationSecurityMetadataSourceService。 
 * 该CostomInvocationSecurityMetadataSourceService的作用提从数据库提取权限和资源,装配到HashMap中, 
 * 供Spring Security使用,用于权限校验。 
 *  
 * TODO(这里用一句话描述这个类的职责).
 * @ClassName: CustomFilterSecurityInterceptorImpl
 * @author Johnny_L_Q
 */
@Service("customFilterSecurityInterceptor")
public class CustomFilterSecurityInterceptorImpl extends
        AbstractSecurityInterceptor implements CustomFilterSecurityInterceptor {

    @Resource
    @Qualifier("customInvocationSecurityMetadataSource")
    private FilterInvocationSecurityMetadataSource securityMetadataSource;
   

    @Resource
    @Qualifier("customAccessDecisionManager")
    @Override
    public void setAccessDecisionManager(
            AccessDecisionManager accessDecisionManager) {
        // TODO Auto-generated method stub
        super.setAccessDecisionManager(accessDecisionManager);
    }
/*    @Resource
    @Qualifier("customAccessDecisionManager")
    public AccessDecisionManager accessDecisionManager;*/
    
/*    @Resource
    @Qualifier("authenticationManager")
    public AuthenticationManager authenticationManager;*/
    
    
    @Resource
    @Qualifier("authenticationManager")
    @Override
    public void setAuthenticationManager(AuthenticationManager newManager) {
        super.setAuthenticationManager(newManager);
    }
    
    /*
     * <p>Title: doFilter</p>
     * <p>Description: </p>
     * @param arg0
     * @param arg1
     * @param arg2
     * @throws IOException
     * @throws ServletException
     * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)
     */
    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
        FilterInvocation fi = new FilterInvocation(request, response, chain);
        infoke(fi);

    }

    /**
     * TODO(这里用一句话描述这个方法的作用).
     * @param fi 
     * @throws ServletException 
     * @throws IOException 
     */
    private void infoke(FilterInvocation fi) throws IOException, ServletException {
        InterceptorStatusToken token = super.beforeInvocation(fi);
        
        try {
            fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
        } finally {
            super.afterInvocation(token, null);
        }
        
    }

    /*
     * <p>Title: init</p>
     * <p>Description: </p>
     * @param arg0
     * @throws ServletException
     * @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
     */
    @Override
    public void init(FilterConfig arg0) throws ServletException {
        // TODO Auto-generated method stub

    }

    /*
     * <p>Title: getSecureObjectClass</p>
     * <p>Description: </p>
     * @return
     * @see org.springframework.security.access.intercept.AbstractSecurityInterceptor#getSecureObjectClass()
     */
    @Override
    public Class<?> getSecureObjectClass() {
        // TODO Auto-generated method stub
        return FilterInvocation.class;
    }

    /*
     * <p>Title: obtainSecurityMetadataSource</p>
     * <p>Description: </p>
     * @return
     * @see org.springframework.security.access.intercept.AbstractSecurityInterceptor#obtainSecurityMetadataSource()
     */
    @Override
    public SecurityMetadataSource obtainSecurityMetadataSource() {
        // TODO Auto-generated method stub
        return this.securityMetadataSource;
    }
    
    /*
     * <p>Title: destroy</p>
     * <p>Description: </p>
     * @see javax.servlet.Filter#destroy()
     */
    @Override
    public void destroy() {
        // TODO Auto-generated method stub

    }
    

    public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() {
        return securityMetadataSource;
    }
    

    public void setSecurityMetadataSource(
            FilterInvocationSecurityMetadataSource securityMetadataSource) {
        this.securityMetadataSource = securityMetadataSource;
    }
}

d、CustomFilterInvocationSecurityMetadataSourceImpl 实现 CustomFilterInvocationSecurityMetadataSource接口,CustomFilterInvocationSecurityMetadataSource接口类继承自
org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource
该类的主要作用是在Spring Security的整个过滤链启动后,在容器启动的时候,程序就会进入到该类中的init()方法,init调用了loadResourceDefine()方法,该方法的主要目的是将数据库中的所有资源与权限读取到本地缓存中保存起来!类中的resourceMap就是保存的所有资源和权限的集合,URL为Key,权限作为Value!
package com.doone.wisdom.security.impl;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;

import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.util.AntUrlPathMatcher;
import org.springframework.security.web.util.UrlMatcher;
import org.springframework.stereotype.Service;

import com.doone.wisdom.entity.security.AuthorityEntity;
import com.doone.wisdom.entity.security.RoleEntity;
import com.doone.wisdom.security.service.CustomInvocationSecurityMetadataSource;
import com.doone.wisdom.service.iface.AuthorityService;
import com.doone.wisdom.service.iface.ResourceService;

/** 
 * TODO(这里用一句话描述这个类的职责).
 * @ClassName: CustomInvocationSecurityMetadataSourceImpl
 * @author Johnny_L_Q
 */
@Service("customInvocationSecurityMetadataSource")
public class CustomInvocationSecurityMetadataSourceImpl implements
        CustomInvocationSecurityMetadataSource {

    @Resource
    private ResourceService resourceService;
    
    @Resource
    private AuthorityService authorityService;

    private UrlMatcher urlMatcher = new AntUrlPathMatcher();

    //private AntPathRequestMatcher pathMatcher;

    private HashMap<String, Collection<ConfigAttribute>> resourceMap = null;

    /**
     *
     * 自定义方法,这个类放入到Spring容器后, 
     * 指定init为初始化方法,从数据库中读取资源 
     * TODO(这里用一句话描述这个方法的作用).
     */
    @PostConstruct
    public void init() {
        loadResourceDefine();
    }

    /**
     * 
     * TODO(程序启动的时候就加载所有资源信息).
     */
    private void loadResourceDefine() {

        // 在Web服务器启动时,提取系统中的所有权限。
        //sql = "select authority_name from pub_authorities";

        List<AuthorityEntity> query = authorityService.getAllAuthoritys();

        /**//*
             * 应当是资源为key, 权限为value。 资源通常为url, 权限就是那些以ROLE_为前缀的角色。 一个资源可以由多个权限来访问。
             * sparta
             */
        resourceMap = new HashMap<String, Collection<ConfigAttribute>>();

        for (AuthorityEntity auth : query) {
            String authName = auth.getAuthorityName();
            
            ConfigAttribute ca = new SecurityConfig(auth.getAuthorityName());

            List<String> resources = resourceService.getResourcesByAuthName(authName);
         

            for (String str : resources) {
                //String authName = auth2.getAuthorityName();
                String url = str;

                /**//*
                     * 判断资源文件和权限的对应关系,如果已经存在相关的资源url,则要通过该url为key提取出权限集合,将权限增加到权限集合中。
                     * sparta
                     */
                if (resourceMap.containsKey(url)) {

                    Collection<ConfigAttribute> value = resourceMap.get(url);
                    value.add(ca);
                    resourceMap.put(url, value);
                } else {
                    Collection<ConfigAttribute> atts = new ArrayList<ConfigAttribute>();
                    atts.add(ca);
                    resourceMap.put(url, atts);
                }

            }

        }

    }

    /**
     * TODO(自定义方法,将List<Role>集合转换为框架需要的Collection<ConfigAttribute>集合).
     * @param roles 角色集合
     * @return list 封装好的Collection集合
     */
    private Collection<ConfigAttribute> listToCollection(List<RoleEntity> roles) {
        List<ConfigAttribute> list = new ArrayList<ConfigAttribute>();

        for (RoleEntity role : roles) {
            list.add(new SecurityConfig(role.getRoleName()));

        }
        return list;
    }

    /*
     * <p>Title: getAllConfigAttributes</p>
     * <p>Description: </p>
     * @return
     * @see org.springframework.security.access.SecurityMetadataSource#getAllConfigAttributes()
     */
    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return null;
    }

    /*
     * <p>Title: getAttributes</p>
     * <p>Description: </p>
     * @param arg0
     * @return
     * @throws IllegalArgumentException
     * @see org.springframework.security.access.SecurityMetadataSource#getAttributes(java.lang.Object)
     */
    @Override
    public Collection<ConfigAttribute> getAttributes(Object object)
            throws IllegalArgumentException {
        //object 是一个URL ,为用户请求URL
        String url = ((FilterInvocation)object).getRequestUrl();
       if("/".equals(url)){
           return null;
       }
        int firstQuestionMarkIndex = url.indexOf(".");
        //判断请求是否带有参数 如果有参数就去掉后面的后缀和参数(/index.do  --> /index)
        if(firstQuestionMarkIndex != -1){
            url = url.substring(0,firstQuestionMarkIndex);
        }
        
        Iterator<String> ite = resourceMap.keySet().iterator();
        //取到请求的URL后与上面取出来的资源做比较
        while (ite.hasNext()) {
            String resURL = ite.next();
            if(urlMatcher.pathMatchesUrl(url, resURL)){
                return resourceMap.get(resURL);
            }
        }
        return null;
    }


    /*
     * <p>Title: supports</p>
     * <p>Description: </p>
     * @param arg0
     * @return
     * @see org.springframework.security.access.SecurityMetadataSource#supports(java.lang.Class)
     */
    @Override
    public boolean supports(Class<?> arg0) {
        // TODO Auto-generated method stub
        return true;
    }
}



以上就是spring security所要用到的代码,接下来是数据库
5、数据库设计的话就是标准的7张表
1、pub_authorities
/*
MySQL Data Transfer
Source Host: localhost
Source Database: wisdom
Target Host: localhost
Target Database: wisdom
Date: 2012/10/19 9:29:52
*/

SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for pub_authorities
-- ----------------------------
CREATE TABLE `pub_authorities` (
  `authority_id` varchar(32) COLLATE utf8_bin NOT NULL,
  `authority_name` varchar(40) COLLATE utf8_bin NOT NULL,
  `authority_desc` varchar(100) COLLATE utf8_bin DEFAULT NULL,
  `enabled` int(10) NOT NULL,
  `issys` int(10) NOT NULL,
  PRIMARY KEY (`authority_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;

-- ----------------------------
-- Records 
-- ----------------------------
INSERT INTO `pub_authorities` VALUES ('1', 'ROLE_ADMIN', '首页广告条管理', '1', '0');
INSERT INTO `pub_authorities` VALUES ('2', 'ROLE_USER', '首页', '1', '0');

2、pub_resources
/*
MySQL Data Transfer
Source Host: localhost
Source Database: wisdom
Target Host: localhost
Target Database: wisdom
Date: 2012/10/19 9:30:25
*/

SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for pub_resources
-- ----------------------------
CREATE TABLE `pub_resources` (
  `resource_id` varchar(32) COLLATE utf8_bin NOT NULL,
  `resource_name` varchar(100) COLLATE utf8_bin NOT NULL,
  `resource_type` varchar(40) COLLATE utf8_bin NOT NULL,
  `priority` int(10) NOT NULL,
  `resource_string` varchar(200) COLLATE utf8_bin NOT NULL,
  `resource_desc` varchar(100) COLLATE utf8_bin DEFAULT NULL,
  `enabled` int(10) NOT NULL,
  `issys` int(10) NOT NULL,
  PRIMARY KEY (`resource_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;

-- ----------------------------
-- Records 
-- ----------------------------
INSERT INTO `pub_resources` VALUES ('1', '首页广告条管理', 'action', '0', '/funcPages/adManager', '首页广告条管理', '1', '0');
INSERT INTO `pub_resources` VALUES ('2', '首页', 'action', '0', '/index', '首页', '1', '0');

3、pub_authorities_resources
/*
MySQL Data Transfer
Source Host: localhost
Source Database: wisdom
Target Host: localhost
Target Database: wisdom
Date: 2012/10/19 9:30:17
*/

SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for pub_authorities_resources
-- ----------------------------
CREATE TABLE `pub_authorities_resources` (
  `id` varchar(32) COLLATE utf8_bin NOT NULL,
  `authority_id` varchar(32) COLLATE utf8_bin NOT NULL,
  `resource_id` varchar(32) COLLATE utf8_bin NOT NULL,
  PRIMARY KEY (`id`),
  KEY `fk_res_aut` (`authority_id`),
  KEY `fk_res` (`resource_id`),
  CONSTRAINT `fk_res` FOREIGN KEY (`resource_id`) REFERENCES `pub_resources` (`resource_id`),
  CONSTRAINT `fk_res_aut` FOREIGN KEY (`authority_id`) REFERENCES `pub_authorities` (`authority_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;

-- ----------------------------
-- Records 
-- ----------------------------
INSERT INTO `pub_authorities_resources` VALUES ('1', '1', '1');
INSERT INTO `pub_authorities_resources` VALUES ('2', '2', '2');

4、pub_roles
/*
MySQL Data Transfer
Source Host: localhost
Source Database: wisdom
Target Host: localhost
Target Database: wisdom
Date: 2012/10/19 9:30:30
*/

SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for pub_roles
-- ----------------------------
CREATE TABLE `pub_roles` (
  `role_id` varchar(32) COLLATE utf8_bin NOT NULL,
  `role_name` varchar(100) COLLATE utf8_bin NOT NULL,
  `role_desc` varchar(100) COLLATE utf8_bin DEFAULT NULL,
  `enabled` int(10) NOT NULL,
  `issys` int(10) NOT NULL COMMENT '角色表',
  PRIMARY KEY (`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;

-- ----------------------------
-- Records 
-- ----------------------------
INSERT INTO `pub_roles` VALUES ('1', 'ROLE_ADMIN', '系统登录', '1', '0');
INSERT INTO `pub_roles` VALUES ('2', 'ROLE_USER', '普通用户', '1', '0');

5、pub_roles_authorities
/*
MySQL Data Transfer
Source Host: localhost
Source Database: wisdom
Target Host: localhost
Target Database: wisdom
Date: 2012/10/19 9:30:36
*/

SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for pub_roles_authorities
-- ----------------------------
CREATE TABLE `pub_roles_authorities` (
  `id` varchar(32) COLLATE utf8_bin NOT NULL,
  `role_id` varchar(32) COLLATE utf8_bin NOT NULL,
  `authority_id` varchar(32) COLLATE utf8_bin NOT NULL,
  PRIMARY KEY (`id`),
  KEY `fk_aut_role` (`role_id`),
  KEY `fk_aut` (`authority_id`),
  CONSTRAINT `fk_aut` FOREIGN KEY (`authority_id`) REFERENCES `pub_authorities` (`authority_id`),
  CONSTRAINT `fk_aut_role` FOREIGN KEY (`role_id`) REFERENCES `pub_roles` (`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;

-- ----------------------------
-- Records 
-- ----------------------------
INSERT INTO `pub_roles_authorities` VALUES ('1', '1', '1');
INSERT INTO `pub_roles_authorities` VALUES ('2', '1', '2');
INSERT INTO `pub_roles_authorities` VALUES ('3', '2', '2');

6、pub_users
/*
MySQL Data Transfer
Source Host: localhost
Source Database: wisdom
Target Host: localhost
Target Database: wisdom
Date: 2012/10/19 9:30:42
*/

SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for pub_users
-- ----------------------------
CREATE TABLE `pub_users` (
  `user_id` varchar(32) COLLATE utf8_bin NOT NULL,
  `user_account` varchar(30) COLLATE utf8_bin NOT NULL,
  `user_name` varchar(40) COLLATE utf8_bin NOT NULL,
  `user_password` varchar(100) COLLATE utf8_bin NOT NULL,
  `enabled` int(10) NOT NULL,
  `issys` int(10) NOT NULL,
  `user_desc` varchar(100) COLLATE utf8_bin DEFAULT NULL COMMENT '用户表',
  PRIMARY KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;

-- ----------------------------
-- Records 
-- ----------------------------
INSERT INTO `pub_users` VALUES ('1', 'admin', 'admin', '21232f297a57a5a743894a0e4a801fc3', '1', '1', '超级管理员');
INSERT INTO `pub_users` VALUES ('2', 'user', 'user', 'ee11cbb19052e40b07aac0ca060c23ee', '1', '0', '普通用户');

7、pub_users_roles
/*
MySQL Data Transfer
Source Host: localhost
Source Database: wisdom
Target Host: localhost
Target Database: wisdom
Date: 2012/10/19 9:30:48
*/

SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for pub_users_roles
-- ----------------------------
CREATE TABLE `pub_users_roles` (
  `id` varchar(32) CHARACTER SET utf8 NOT NULL,
  `user_id` varchar(32) COLLATE utf8_bin NOT NULL,
  `role_id` varchar(32) COLLATE utf8_bin NOT NULL COMMENT '角色和用户中间表',
  PRIMARY KEY (`id`),
  KEY `fk_user` (`user_id`),
  KEY `fk_role` (`role_id`),
  CONSTRAINT `fk_role` FOREIGN KEY (`role_id`) REFERENCES `pub_roles` (`role_id`),
  CONSTRAINT `fk_user` FOREIGN KEY (`user_id`) REFERENCES `pub_users` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;

-- ----------------------------
-- Records 
-- ----------------------------
INSERT INTO `pub_users_roles` VALUES ('1', '1', '1');
INSERT INTO `pub_users_roles` VALUES ('2', '2', '2');




在最开始我运行的时候,我pub_authoritys表中存储的是AUTH_***的东西,但是好像spring security只认识ROLE_为前缀的权限,只有是ROLE_为前缀的它才能做比较,所以有一个地方需要注意,CustomInvocationSecurityMetadataSourceImpl.loadResourceDefine()方法中的resourceMap中的URL对于的权限必须是ROLE_为前缀的数据!如果你不确定的话,可以在方法在吧resourceMap的值打印出来看看!
在这里我提供下几个类中做用到的查询方法的SQL,这些都是基于myibatis的SQL:

1、getAuthoritiesByUsername(String username)
select b.authority_name 
	     from
	     PUB_ROLES a,
	     PUB_AUTHORITIES b,
	     PUB_ROLES_AUTHORITIES c 
	     where c.role_id = a.role_id and c.authority_id = b.authority_id and a.role_name in 
	     (select role_name from
	     PUB_USERS_ROLES ur,
	     PUB_USERS u,
	     PUB_ROLES r
	     where ur.user_id = u.user_id and ur.role_id = r.role_id and u.user_name = #{username}
	     )


2、getUser(String username)
select * from 
	     PUB_USERS u
	     where u.user_name = #{username}


3、getResourcesByAuthName(String authName)
select b.resource_string from PUB_AUTHORITIES_RESOURCES a ,PUB_RESOURCES b, PUB_AUTHORITIES c
	     where a.resource_id = b.resource_id and a.authority_id = c.authority_id and c.authority_name = #{authName}


4、getAllAuthoritys()
select * from pub_authorities
  • 大小: 80.3 KB
分享到:
评论
18 楼 Johnny_L 2014-11-21  
lilihongmm 写道
这样实现是有问题的。。。。CustomInvocationSecurityMetadataSourceImpl下的getAttributes方法重写会导致数据库存不了/jsp/** 带*号的,看了下源码才知道的。。。。。为了赶进度不去看官网文档,找帖子搞,是有风险的。。。。楼主好好再研究下吧。。。真的有问题。。。


是吗?我研究的也不深,所以难免会有些不足或者是有误的地方,还请各位谅解!回头我再多看看官方文档!
17 楼 lilihongmm 2014-11-13  
这样实现是有问题的。。。。CustomInvocationSecurityMetadataSourceImpl下的getAttributes方法重写会导致数据库存不了/jsp/** 带*号的,看了下源码才知道的。。。。。为了赶进度不去看官网文档,找帖子搞,是有风险的。。。。楼主好好再研究下吧。。。真的有问题。。。
16 楼 SundayHa 2014-11-12  
SundayHa 写道
楼主你好 为什么 我按你的配置完后 没有进入CustomAccessDecisionManagerImpl这个类里呢?

已经找出问题的所在了 是因为CustomInvocationSecurityMetadataSourceImpl类中
public Collection<ConfigAttribute> getAttributes(Object object) throws    IllegalArgumentException {}方法中返回null所导致的。
15 楼 SundayHa 2014-11-12  
楼主你好 为什么 我按你的配置完后 没有进入CustomAccessDecisionManagerImpl这个类里呢?
14 楼 java-lxm 2014-06-23  
在哪个地方重新加载?
13 楼 huxize 2014-06-11  
pengshuai1214 写道
LZ:系统会在初始化时一次将所有资源加载到内存中,即使在数据库中修改了资源信息,系统也不会再次去从数据库中读取资源信息。这就造成了每次修改完数据库后,都需要重启系统才能时资源配置生效。请问怎么解决该问题啊!!!


重新在加载loadResourceDefine()这个方法。
12 楼 pengshuai1214 2014-05-19  
LZ:系统会在初始化时一次将所有资源加载到内存中,即使在数据库中修改了资源信息,系统也不会再次去从数据库中读取资源信息。这就造成了每次修改完数据库后,都需要重启系统才能时资源配置生效。请问怎么解决该问题啊!!!
11 楼 java-lxm 2014-05-07  
pub_authoritys表 我就是存的AUTH_xx,  但是起作用了是怎么回事。。。。。。。
10 楼 zhaiang 2014-02-27  
Johnny_L 写道
Johnny_L 写道
zhaiang 写道
楼主能不能麻烦也把接口贴出来,看到直接把实现类弄出来,就搞不懂LZ是怎么定义接口的,这些接口是继承了哪些SpringSecurity的关键接口呢...

直接贴出实现类让我有好多疑惑呀?

这些接口都是Spring Security中定义好的接口,每个接口对应不同的功能,所以需要实现某些功能直接实现相应接口,写自己的处理代码就行了!



我恐怕误解你的意思了,你是让我把自己写的那4个接口类的代码贴出来是吧?我误解为你说框架内的接口了,不好意思,我已经把接口代码贴上去了!



对对,就是这个意思,看到了,谢谢啦...
9 楼 Johnny_L 2014-02-27  
Johnny_L 写道
zhaiang 写道
楼主能不能麻烦也把接口贴出来,看到直接把实现类弄出来,就搞不懂LZ是怎么定义接口的,这些接口是继承了哪些SpringSecurity的关键接口呢...

直接贴出实现类让我有好多疑惑呀?

这些接口都是Spring Security中定义好的接口,每个接口对应不同的功能,所以需要实现某些功能直接实现相应接口,写自己的处理代码就行了!



我恐怕误解你的意思了,你是让我把自己写的那4个接口类的代码贴出来是吧?我误解为你说框架内的接口了,不好意思,我已经把接口代码贴上去了!
8 楼 Johnny_L 2014-02-27  
zmx729618 写道
请问楼主按您的方法我的应用报了:java.lang.IllegalArgumentException: AccessDecisionManager does not support secure object class: class org.springframework.security.web.FilterInvocation 这样的错。请问是什么问题怎么样解决


AccessDecisionManager是框架内部的一个接口类,我这里是用自己写的CustomAccessDecisionManager接口类来继承了AccessDecisionManager类!所以你看下你程序里面是不是哪里写错了!
7 楼 Johnny_L 2014-02-27  
zhaiang 写道
楼主能不能麻烦也把接口贴出来,看到直接把实现类弄出来,就搞不懂LZ是怎么定义接口的,这些接口是继承了哪些SpringSecurity的关键接口呢...

直接贴出实现类让我有好多疑惑呀?

这些接口都是Spring Security中定义好的接口,每个接口对应不同的功能,所以需要实现某些功能直接实现相应接口,写自己的处理代码就行了!
6 楼 zmx729618 2014-02-24  
请问楼主按您的方法我的应用报了:java.lang.IllegalArgumentException: AccessDecisionManager does not support secure object class: class org.springframework.security.web.FilterInvocation 这样的错。请问是什么问题怎么样解决
5 楼 zhaiang 2014-02-14  
楼主能不能麻烦也把接口贴出来,看到直接把实现类弄出来,就搞不懂LZ是怎么定义接口的,这些接口是继承了哪些SpringSecurity的关键接口呢...

直接贴出实现类让我有好多疑惑呀?
4 楼 kindhyh 2014-01-27  
写得不错,支持!
3 楼 u012767682 2013-12-04  
 
2 楼 yan124673729 2013-07-12  
1 楼 53873039oycg 2013-01-04  
楼主写的不错!

相关推荐

    spring_security_demo

    Spring Security 3.0数据库动态实现权限控制.

    Spring Security 3.0 安全权限管理手册

    Spring Security 3.0 安全权限管理手册(最新),可以作为公司权限管理资料用,权限管理将是系统中的非常重要的一个模块,权限的设计也是参考相关资料进行整理和补充。系统将通过数据库进行管理用户权限

    spring3.0+mybatis3.0+springSecurity+SpringMVC

    这个是基于Spring的一个小例子 , 主要是为了帮助大家学习SpringSecurity和SpringMvc 和Mybatis3.0 1.SS不用再数据库建表 2.使用了SS提供的登录方式,在输入用户名和密码时,访问到服务器后台 3.判断如果是用户名是...

    Spring Security-3.0.1中文官方文档(翻译版)

    这次发布的Spring Security-3.0.1 是一个bug fix 版,主要是对3.0 中存在的一些问题进 行修 正。文档中没有添加新功能的介绍,但是将之前拼写错误的一些类名进行了修正,建议开发 者以这一版本的文档为参考。 ...

    Java开发的一个简约后台管理系统源码.zip

    权限框架:SpringSecurity 模板引擎:Thymeleaf 持久层框架:Mybatis-Plus 日志管理:LogBack 工具类:Apache Commons、Hutool 视图框架:Spring MVC 工作流:Activiti6 定时器:Quartz 数据库连接池:Druid 页面...

    一款面向模板开发,支持静态生成的CMS 系统,其支持前后端分离部署,是一款好用的 cms 系统

    ThinkItCMS 采用 SpringBoot + Mybatis Plus + Redis+ Spring Security + OAuth2 + Freemark 搭建的一套cms 系统,数据库采用 mysql 数据库,文件服务器采用 Fastdfs 全文检索采用 Solr 。 前端架构采用ant design ...

    基于springboot的设备管理系统+源代码+文档说明

    - 完善的部门管理及数据权限,通过注解实现数据权限的控制 - 完善的XSS防范及脚本过滤,彻底杜绝XSS攻击 - 支持分布式部署,session存储在redis中 - 友好的代码结构及注释,便于阅读及二次开发 - 引入quartz定时任务...

    MagicErp是使用java语言开发基于SpringBoot2.X架构体系构建的一套erp系统

    Spring Security安全框架2.0.1.RELEASE。Druid 数据库连接池1.1.22。xxl-job定时任务2.0.0。webpack构建工具3.10.0。ES6JS版本Vue.js基础JS框架2.6.14jQuery辅助JS库2.1.4Vue Router路由管理3.0.1Vuex状态管理3.0.

    基于jbpm与activiti的工作流平台技术架构介绍

    系统的安全管理由Spring Security 3提供配置及管理,非常容易与第三方的用户或认证平台进行整合,如与CAS服务器作统一认证,只需要加上新的配置模块即可实现,不影响系统现有的功能模块。大大满足了各种不同系统的...

    asp.net知识库

    在 SQL Server 2005 中使用表值函数来实现空间数据库 SQL Server 2005的30个最重要特点 同时安装sql2000和sql2005的经验 类如何与界面绑定 在Asp.net中如何用SQLDMO来获取SQL Server中的对象信息 使用Relations建立...

    【毕业设计】基于springboot的仿共享单车后台源码及笔记【源码+SQL脚本】.zip

    springSecurity(权限验证和请求拦截) AES对称加密数据,RSA非对称加密公钥key(对用户信息进行加密) redis缓存token(token作为用户的标识,维护用户的状态,类似于session) redis结合ActiveMQ发送短信验证码和防止...

    entfrm-boot可视化开发平台-其他

    9、拓展插件10、分布式与微服务开发环境:开发工具IntelliJ IDEA、Navicat Premium后端技术Springboot2.x、SpringSecurity、Oauth2、JWT、mybatis-plus、activiti、Flutter、Mysql等前端技术Node.js 10+、Vue.js 2.6...

    阿里云javasdk源码-spring-boot-adminlte:spring-boot-adminlte

    ybg3.0 基于springboot2开发 ,停止框架 地址 ybg4.0 基于权限服务器,微服务化开发 (维护中) 地址 QQ群:314658875(免费),310391018(免费) 声明,虽然本产品是开源。但未经本人允许擅自申请专利,将公开追究...

    JooLun微信管理平台-其他

    JooLun微信管理平台是一个 Java EE 企业级微信快速开发平台,基于经典技术组合(Spring Boot、Spring Security、MyBatis、Jwt、Vue),内置模块如:公众号管理、部门管理、角色用户、菜单及按钮授权、数据权限、系统...

    java面试题以及技巧

    │ Struts+Hibernate+Spring轻量级J2EE企业应用实战.pdf │ Struts中文手册.pdf │ Struts配置文件详解.txt │ 上海税友.txt │ 上海税友软件 面试题.doc │ 公司培训文档-混淆的基本概念.doc │ 基本算法.doc │ ...

    java面试题目与技巧1

    │ Struts+Hibernate+Spring轻量级J2EE企业应用实战.pdf │ Struts中文手册.pdf │ Struts配置文件详解.txt │ 上海税友.txt │ 上海税友软件 面试题.doc │ 公司培训文档-混淆的基本概念.doc │ 基本算法.doc │ ...

    java面试题及技巧4

    │ Struts+Hibernate+Spring轻量级J2EE企业应用实战.pdf │ Struts中文手册.pdf │ Struts配置文件详解.txt │ 上海税友.txt │ 上海税友软件 面试题.doc │ 公司培训文档-混淆的基本概念.doc │ 基本算法.doc │ ...

    java面试题及技巧3

    │ Struts+Hibernate+Spring轻量级J2EE企业应用实战.pdf │ Struts中文手册.pdf │ Struts配置文件详解.txt │ 上海税友.txt │ 上海税友软件 面试题.doc │ 公司培训文档-混淆的基本概念.doc │ 基本算法.doc │ ...

    java面试题以及技巧6

    │ Struts+Hibernate+Spring轻量级J2EE企业应用实战.pdf │ Struts中文手册.pdf │ Struts配置文件详解.txt │ 上海税友.txt │ 上海税友软件 面试题.doc │ 公司培训文档-混淆的基本概念.doc │ 基本算法.doc │ ...

Global site tag (gtag.js) - Google Analytics