`

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 security3.0帮助文档

    Spring Security 3.0提供了强大的权限控制功能,通过合理设计数据库结构、实体模型以及精心配置,可以实现灵活且安全的应用程序权限管理。对于那些希望深入理解并掌握Spring Security 3.0权限控制的开发者而言,以上...

    SpringSecurity3.0 教程

    SpringSecurity3.0是Spring框架的一个重要扩展,主要用于企业级应用的安全管理,提供了一套全面的访问控制和安全解决方案。本教程将深入探讨SpringSecurity3.0的核心概念、配置及其实现方式,帮助开发者理解并掌握...

    Spring Security 3.0 权限管理

    Spring Security 3.0 是一个强大的安全框架,用于在Java应用程序中实现全面的安全管理解决方案。它专注于提供身份验证、授权和访问控制功能,确保只有经过验证的用户才能访问受保护的资源。本教程将深入探讨Spring ...

    SpringSecurity3.0.x官方参考文档(英文+中文双解版)

    Spring Security 支持使用`@Secured`和`@PreAuthorize`注解进行方法级别的权限控制,以及`&lt;intercept-url&gt;`元素进行URL级别的权限控制。 ### 5. 认证流程 - 用户尝试访问受保护的资源。 - 请求被Spring Security的...

    Spring Security 3.0 安全权限管理手册

    &lt;html xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5"&gt; &lt;h1&gt;Welcome, &lt;sec:authentication property="principal.username"/&gt;&lt;/h1&gt; ``` ##### 4. 实现方法级安全 在方法级别添加安全...

    spring security 3.0入门教程PDF

    Spring Security 3.0是该框架的一个重要版本,提供了许多安全控制功能,用于保护Web应用程序不受到黑客的攻击。 ### Spring Security 3.0入门知识点: 1. **Spring Security概述**: Spring Security是一个提供...

    spring3.0+spring security3.1.4 api chm 中文版

    Spring Security通过拦截器模式,可以方便地实现对Web请求的细粒度访问控制。同时,它还提供了对Remember-Me服务、CSRF防护、以及基于角色的权限控制等功能,确保了应用的安全性。 在《Spring Security 3.1.4 API》...

    Spring-Security3.0自定义表结构

    ### Spring Security 3.0 自定义表结构详解 在企业级应用开发中,Spring Security作为Spring框架...通过以上步骤,开发者可以利用Spring Security 3.0实现高度定制化的权限管理方案,有效提升应用的安全性和用户体验。

    Spring-Security2.0 和 3.0中文文档

    3.0 引入了基于Spring Expression Language (SpEL)的访问控制,允许开发者使用更灵活的表达式来定义权限规则,比如 `hasRole('ROLE_ADMIN')` 或 `@Secured('IS_AUTHENTICATED_FULLY')`。 3. **Remember Me服务**:...

    SpringSecurity_3.0

    《SpringSecurity_3.0》是一本面向Java Web开发初学者和中级程序员的教程,它深入浅出地介绍了Spring Security这一强大的安全框架。Spring Security是一个为Java应用提供身份验证和授权服务的开源项目,它旨在保护...

    spring security3,spring3.0,hibernate3.5,struts2

    整合这四个框架,首先需要配置Spring Security来处理用户的登录和权限控制。这通常涉及创建安全配置类,定义访问规则,以及设置认证和授权机制。接着,Spring Security可以与Struts2集成,通过拦截器来控制Action的...

    spring security3 中文版本

    Spring Security 3.0.1 是在 Spring Security 3.0 的基础上进行的一次 bug 修复版本,主要针对先前版本中存在的问题进行了修正。此次版本未引入新的功能,但修正了一些已知的问题,增强了稳定性。 ##### 1.4 获取 ...

    spring-security Jar包

    Spring Security 提供基于角色的访问控制(RBAC),允许开发者定义安全角色和权限,并通过`@Secured`、`@PreAuthorize`、`@PostAuthorize`等注解实现方法级别的细粒度授权。 3. **过滤器链**:Spring Security 的...

    spring3.0+mybatis3.0+springSecurity+SpringMVC

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

    Spring_Security_3权限管理

    本文档主要涵盖了Spring Security 3.0版本中的权限管理,旨在帮助企业构建高效且安全的权限管理系统。 1. **区分Authentication(验证)与Authorization(授权)** - 验证(Authentication)是确认用户身份的过程...

    spring_security_demo

    【Spring Security 数据库动态实现权限控制】 在Spring Security 3.0中,动态权限控制意味着权限和角色的分配可以通过数据库来实现,而不是硬编码在配置文件中。这提供了更大的灵活性,使得管理员可以在运行时更改...

    Spring_security例子源码

    5. **表达式式访问控制**:Spring Security 3.0引入了基于Spring EL(Expression Language)的访问控制,允许更灵活的权限表达式,如`hasRole('ROLE_ADMIN')`或`isFullyAuthenticated()`。 6. **异常处理**:当访问...

    springSecurity3例子

    Spring Security 是一个强大的安全框架,主要用于Java应用的安全管理。它为Web应用程序提供了全面的身份验证、授权和访问控制功能。在Spring Security 3版本中,这个框架进一步完善了其特性和性能,使其成为开发者...

Global site tag (gtag.js) - Google Analytics