首页 > 代码库 > SpringSecurity3.2.5自定义角色及权限的教程

SpringSecurity3.2.5自定义角色及权限的教程

最近阴差阳错的搞上了SpringSecurity3,因为是自己做的小系统,中间遇到了很多坑,基本每个坑都踩过了,网上也查了不少资料,发现有不少错误的,更是让我绕了一圈又一圈,现在把一些基本的东西总结一下。

先从整体上总结一下为什么使用SS,一般的,在不使用ss的情况下,我们基本会在每个业务方法执行前,插入一段用于验证权限的代码,从而判断当前用户是否有相应权限进行操作,这样做就会让业务方法和验证权限有了一个紧密的耦合;如果使用ss,我们就可以通过注解或者XML配置方式代替权限验证,使得业务和权限代码彻底分离,通过下图可以更形象的理解:

技术分享

目前,权限管理采用最多的技术都是基于角色访问控制技术RBAC(Role Based Access Control)。一般来说,提供如下功能:1,角色管理界面,由用户定义角色,给角色赋权限;2,用户角色管理界面,由用户给系统用户赋予角色。什么是RBAC,说到底其实就是五张表,权限表-权限角色对应表-角色表-角色用户对应表-用户表,比较常见。但是ss3默认支持的并不是这种模式,而是通过XML配置角色及用户的方式实现的权限验证等操作,所以需要我们去实现SS中一些接口,让其支持RBAC,下面开始搭建一套支持RBAC技术的SS框架:

(1)数据库相关表格:

1.用户表Users

    CREATE TABLE `users` (

       `password` varchar(255) default NULL,
       `username` varchar(255) default NULL,
       `uid` int(11) NOT NULL auto_increment,
       PRIMARY KEY  (`uid`)
    )

 

   2.角色表Roles

   CREATE TABLE `roles` (
     `rolename` varchar(255) default NULL,
     `rid` int(11) NOT NULL auto_increment,
     PRIMARY KEY  (`rid`)
   )

 

   3 用户_角色表users_roles

   CREATE TABLE `users_roles` (

     --用户表的外键
     `uid` int(11) default NULL,

     --角色表的外键
     `rid` int(11) default NULL,
     `urid` int(11) ,
     PRIMARY KEY  (`urid`),
   )

 

   4.资源表resources

   CREATE TABLE `resources` (

     -- 权限所对应的url地址
     `url` varchar(255) default NULL,

     --权限所对应的编码,例201代表发表文章
     `resourcename` varchar(255) default NULL,
     `rsid` int(11) ,
     PRIMARY KEY  (`rsid`)
   )

 

   5.角色_资源表roles_resources

    CREATE TABLE `roles_resources` (
      `rsid` int(11) default NULL,
      `rid` int(11) default NULL,
      `rrid` int(11) NOT NULL ,
      PRIMARY KEY  (`rrid`),
      )


(2)在继续配置前,需要知道ss是如何通过权限验证的,实际上ss通过拦截器,拦截发来的请求,对其进行验证的。而具体验证的方式则是通过我们实现相关接口的方法来进行的。既然是拦截器,web.xml势必是优先配置的。

[html] view plain copy
 技术分享技术分享
  1. <!DOCTYPE web-app PUBLIC  
  2.  "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"  
  3.  "http://java.sun.com/dtd/web-app_2_3.dtd" >  
  4.   
  5.   
  6. <web-app>  
  7.   <display-name>Archetype Created Web Application</display-name>  
  8.       <!-- Spring Security配置 -->  
  9.       <filter>    
  10.         <filter-name>springSecurityFilterChain</filter-name>    
  11.         <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>    
  12.       </filter>    
  13.           
  14.       <filter-mapping>    
  15.         <filter-name>springSecurityFilterChain</filter-name>    
  16.         <url-pattern>/*</url-pattern>    
  17.       </filter-mapping>    
  18.      <!-- Spring MVC配置 -->  
  19.     <servlet>  
  20.         <servlet-name>spring</servlet-name>  
  21.         <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>  
  22.          <init-param>  
  23.             <param-name>contextConfigLocation</param-name>  
  24.             <param-value>classpath:spring-mvc.xml</param-value>  
  25.         </init-param>  
  26.         <load-on-startup>1</load-on-startup>  
  27.     </servlet>  
  28.        
  29.     <servlet-mapping>  
  30.         <servlet-name>spring</servlet-name>  
  31.         <url-pattern>*.do</url-pattern>  
  32.     </servlet-mapping>  
  33.        
  34.        
  35.     <!-- Spring配置 -->  
  36.     <listener>  
  37.         <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>  
  38.     </listener>  
  39.     <listener>  
  40.         <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>  
  41.     </listener>  
  42.        
  43.     <!-- 指定Spring Bean的配置文件所在目录。默认配置在WEB-INF目录下 -->  
  44.     <context-param>  
  45.         <param-name>contextConfigLocation</param-name>  
  46.         <param-value>classpath:applicationContext*.xml,classpath:spring-mybatis.xml</param-value>  
  47.     </context-param>  
  48.     <filter>  
  49.         <filter-name>encodingFilter</filter-name>  
  50.         <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>  
  51.         <init-param>  
  52.             <param-name>encoding</param-name>  
  53.             <param-value>UTF-8</param-value>  
  54.         </init-param>  
  55.     </filter>  
  56.       
  57.      <!-- Spring 刷新Introspector防止内存泄露 -->    
  58.     <listener>    
  59.         <listener-class>    
  60.             org.springframework.web.util.IntrospectorCleanupListener    
  61.         </listener-class>    
  62.     </listener>    
  63.         
  64.     <!--  获取Spring Security session的生命周期-->    
  65.     <listener>    
  66.         <listener-class>    
  67.             org.springframework.security.web.session.HttpSessionEventPublisher     
  68.         </listener-class>    
  69.     </listener>    
  70.     
  71.     <!-- session超时定义,单位为分钟 -->    
  72.     <session-config>    
  73.         <session-timeout>20</session-timeout>    
  74.     </session-config>    
  75. </web-app>  

接下来是spring security3的一些配置,具体的每一个是什么意思,网上很多资料,这里不赘述了。总之,需要根据自己的需求,进行相应的修改。
[html] view plain copy
 技术分享技术分享
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <beans:beans xmlns="http://www.springframework.org/schema/security"    
  3.        xmlns:beans="http://www.springframework.org/schema/beans"  
  4.        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"   
  5.        xmlns:aop="http://www.springframework.org/schema/aop"   
  6.        xmlns:tx="http://www.springframework.org/schema/tx"   
  7.        xmlns:context="http://www.springframework.org/schema/context"   
  8.        xmlns:mvc="http://www.springframework.org/schema/mvc"   
  9.        xsi:schemaLocation="  
  10.        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd   
  11.        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd   
  12.        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd   
  13.        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd  
  14.        http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd  
  15.        http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.2.xsd">  
  16.    <http pattern="/css/**" security="none"></http>     
  17.    <http pattern="/images/**" security="none"></http>     
  18.    <http pattern="/img/**" security="none"></http>     
  19.    <http pattern="/scripts/**" security="none"></http>  
  20.    <http pattern="/font-awesome/**" security="none"></http>  
  21.    <http pattern="/system/resources/**" security="none"></http>  
  22.    <http pattern="/system/login.do" security="none"/>  
  23.    <http auto-config="true" use-expressions="true">  
  24.      <form-login login-page="/system/login.do"  default-target-url="/system/sysManage.do"/>  
  25.       <!--     
  26.          error-if-maximum-exceeded 后登陆的账号会挤掉第一次登陆的账号     
  27.          session-fixation-protection  防止伪造sessionid攻击,用户登录成功后会销毁用户当前的session。    
  28.     -->    
  29.     <!-- <session-management invalid-session-url="/user/timedout" session-fixation-protection="none">    
  30.         <concurrency-control max-sessions="1" error-if-maximum-exceeded="true"/>    
  31.     </session-management> -->  
  32.      <custom-filter ref="myFilterSecurityInterceptor" before="FILTER_SECURITY_INTERCEPTOR"/>  
  33.    </http>  
  34.    <!-- 认证管理器,实现用户认证的入口,主要实现UserDetailsService接口即可 -->  
  35.     <authentication-manager alias="authenticationManager">  
  36.         <authentication-provider  
  37.             user-service-ref="myUserDetailsServiceImpl">  
  38.                 <!-- <password-encoder hash="md5" /> -->  <!--盐值  [添加这个属性后,加密密码明文为:"密码明文{盐值}"]  -->   
  39.         </authentication-provider>  
  40.     </authentication-manager>  
  41.      <!-- 配置过滤器 -->    
  42.     <beans:bean id="myFilterSecurityInterceptor" class="com.product.sys.security.MyFilterSecurityInterceptor">    
  43.         <!-- 用户是否拥有所请求资源的权限 -->    
  44.         <beans:property name="accessDecisionManager" ref="myAccessDescisionManager" />    
  45.         <!-- 资源与权限对应关系 -->    
  46.         <beans:property name="fisMetadataSource" ref="mySecurityMetadataSource" />    
  47.         <!-- 用户拥有的权限 -->    
  48.         <beans:property name="authenticationManager" ref="authenticationManager" />   
  49.     </beans:bean>    
  50.     <beans:bean id="mySecurityMetadataSource" class="com.product.sys.security.MySecurityMetadataSource"><beans:constructor-arg name="userMapper" ref="userMapper"></beans:constructor-arg></beans:bean>  
  51.     <beans:bean id="myAccessDescisionManager" class="com.product.sys.security.MyAccessDescisionManager"></beans:bean>  
  52.       
  53. </beans:beans>  

到上面的这个配置文件,则是重中之重了,和ss3打交道,主要都是这个文件。简单说一下,我们需要实现一个自己的filter,在配置中就是myFilterSecurityInterceptor,而这个filter中,还需要我们额外注入三个bean,分别是accessDecisionManager、fisMetadataSource以及authenticationManager,这三个属性中除了fisMetadataSource可以自定义名称外,其他两个都在ss3的父类中定义好了,所以此处需要特别注意,在这里掉过坑了。另外这里说一下这三个分别的作用,accessDecisionManager中有decide(Authentication authentication, Object object,Collection<ConfigAttribute> configAttributes)方法,该方法用于判断当前用户是否有权限进行操作,参数中authentication包含了当前用户所拥有的权限,configAttributes中包含了进行该步骤需要的权限,对其进行对比就可以判断该用户是否有权限进行操作。

[html] view plain copy
 技术分享技术分享
  1. /**   
  2.  * @description  访问决策器,决定某个用户具有的角色,是否有足够的权限去访问某个资源 ;做最终的访问控制决定   
  3.   
  4.  */   
  5. public class MyAccessDescisionManager implements AccessDecisionManager{  
  6.   
  7.     @Override  
  8.     public void decide(Authentication authentication, Object object,  
  9.             Collection<ConfigAttribute> configAttributes)  
  10.             throws AccessDeniedException, InsufficientAuthenticationException {  
  11.         // TODO Auto-generated method stub  
  12.          System.out.println("MyAccessDescisionManager.decide()------------------验证用户是否具有一定的权限--------");    
  13.             if(configAttributes==null) return;    
  14.             Iterator<ConfigAttribute> it = configAttributes.iterator();    
  15.             while(it.hasNext()){    
  16.                 String needResource = it.next().getAttribute();    
  17.                 //authentication.getAuthorities()  用户所有的权限    
  18.                 for(GrantedAuthority ga:authentication.getAuthorities()){    
  19.                     if(needResource.equals(ga.getAuthority())){    
  20.                         return;    
  21.                     }    
  22.                 }    
  23.             }    
  24.             throw new AccessDeniedException("--------MyAccessDescisionManager:decide-------权限认证失败!");    
  25.           
  26.     }  
  27.   
  28.     @Override  
  29.     public boolean supports(ConfigAttribute attribute) {  
  30.         // TODO Auto-generated method stub  
  31.         return true;  
  32.     }  
  33.   
  34.     @Override  
  35.     public boolean supports(Class<?> clazz) {  
  36.         // TODO Auto-generated method stub  
  37.         return true;  
  38.     }  
  39.   
  40. }  

到这里,可以很自然的想到是权限和用户数据从哪里得到的,filterInvocationSecurityMetadataSource在被加载时候,会首先将权限的信息建立起来,这里我用一个map,key为url,value为该权限的名称,这一步是在构造方法中进行的,也就是服务器启动时候完成的。而当用户访问某一个地址时,ss会到该类中调用getAttributes(Object obj)方法,obj中包含了访问的url地址,我们需要做的就是将该url对应的权限名称返回给ss,而ss会将返回的这个对象,其实就是accessDecisionManager的decide方法中的configAttributes对象。

[java] view plain copy
 技术分享技术分享
  1. /**  
  2.  * @description  资源源数据定义,将所有的资源和权限对应关系建立起来,即定义某一资源可以被哪些角色访问  
  3.  * @author aokunsang  
  4.  * @date 2012-8-15  
  5.  */    
  6. public class MySecurityMetadataSource implements FilterInvocationSecurityMetadataSource {    
  7.   
  8.     private UserMapper userMapper;    
  9.     public UserMapper getUserMapper() {  
  10.         return userMapper;  
  11.     }  
  12.   
  13.     public void setUserMapper(UserMapper userMapper) {  
  14.         this.userMapper = userMapper;  
  15.     }  
  16.   
  17.     /* 保存资源和权限的对应关系  key-资源url  value-权限 */    
  18.     private static Map<String,Collection<ConfigAttribute>> resourceMap = null;     
  19.     private AntPathMatcher urlMatcher = new AntPathMatcher();    
  20.         
  21.     public MySecurityMetadataSource(UserMapper userMapper) {    
  22.         this.userMapper = userMapper;    
  23.         loadResourcesDefine();    
  24.     }    
  25.         
  26.     @Override    
  27.     public Collection<ConfigAttribute> getAllConfigAttributes() {    
  28.         return null;    
  29.     }    
  30.     
  31.     private void loadResourcesDefine(){    
  32.         resourceMap = new HashMap<String,Collection<ConfigAttribute>>();    
  33.             
  34.         System.out.println("MySecurityMetadataSource.loadResourcesDefine()--------------开始加载资源列表数据--------");    
  35.         List<RolePO> roles = userMapper.findAllRoles();    
  36.         for(RolePO role : roles){    
  37.             List<ResourcePO> resources = role.getResources();   
  38.             for(ResourcePO resource : resources){    
  39.                 Collection<ConfigAttribute> configAttributes = null;    
  40.                 ConfigAttribute configAttribute = new SecurityConfig(resource.getResourceName());    
  41.                 if(resourceMap.containsKey(resource.getUrl())){    
  42.                     configAttributes = resourceMap.get(resource.getUrl());    
  43.                     configAttributes.add(configAttribute);    
  44.                 }else{    
  45.                     configAttributes = new ArrayList<ConfigAttribute>() ;    
  46.                     configAttributes.add(configAttribute);    
  47.                     resourceMap.put(resource.getUrl(), configAttributes);    
  48.                 }    
  49.             }    
  50.         }  
  51.         System.out.println("11");  
  52.         Set<String> set = resourceMap.keySet();  
  53.         Iterator<String> it = set.iterator();  
  54.         while(it.hasNext()){  
  55.             String s = it.next();  
  56.             System.out.println("key:"+s+"|value:"+resourceMap.get(s));  
  57.         }  
  58.     }    
  59.     /*   
  60.      * 根据请求的资源地址,获取它所拥有的权限  
  61.      */    
  62.     @Override    
  63.     public Collection<ConfigAttribute> getAttributes(Object obj)    
  64.             throws IllegalArgumentException {    
  65.         //获取请求的url地址    
  66.         String url = ((FilterInvocation)obj).getRequestUrl();    
  67.         System.out.println("MySecurityMetadataSource:getAttributes()---------------请求地址为:"+url);    
  68.         Iterator<String> it = resourceMap.keySet().iterator();    
  69.         while(it.hasNext()){    
  70.             String _url = it.next();    
  71.             if(_url.indexOf("?")!=-1){    
  72.                 _url = _url.substring(0, _url.indexOf("?"));    
  73.             }    
  74.             if(urlMatcher.match(_url,url)){  
  75.                 System.out.println("MySecurityMetadataSource:getAttributes()---------------需要的权限是:"+resourceMap.get(_url));    
  76.                 return resourceMap.get(_url);    
  77.             }  
  78.                   
  79.         }  
  80.         Collection<ConfigAttribute> nouse = new ArrayList<ConfigAttribute>();  
  81.         nouse.add(new SecurityConfig("无相应权限"));  
  82.         return nouse;  
  83.     }    
  84.     
  85.     @Override    
  86.     public boolean supports(Class<?> arg0) {    
  87.         System.out.println("MySecurityMetadataSource.supports()---------------------");    
  88.         return true;    
  89.     }    
  90.         
  91. }    

到这里,我们还有一个疑问,就是decide方法中的authentication对象(authentication.getAuthorities()包含当前用户拥有的权限),用户的对应角色和权限信息是从哪里获得的?其实这里是通过调用MyUserDetailsServiceImpl来获取的,该类需要实现UserDetailService接口,更具体一些实际上是通过loadUserByUsername进行获取用户权限信息的,这里注意返回的User不是我们自己定义的PO,而是ss3框架中的User。(这里说下为什么我自己的UserPO没有继承ss的User,就是因为User没有默认无参构造方法,导致mybatis无法创建对象,具体可能还是有办法的,比如重写mybatis的相关接口,比较麻烦,所以这里是先通过返回我们自己的UserPO后,再组装成ss需要的User对象进行的)这里在回到刚才AccessDescisionManager中的decide方法想一下,authentication.getAuthorities()其实获得的就是下面的Collection<GrantedAuthority>类型的对象。

最后下面的这段代码,我没有直接从username中直接获得resource,而是通过先获得role,再通过role获取resource,我感觉这样方便一些,sql也简单,当然有更好的可以替换掉。

[java] view plain copy
 技术分享技术分享
  1. @Component("myUserDetailsServiceImpl")  
  2. public class MyUserDetailsServiceImpl implements UserDetailsService{  
  3.   
  4.     @Resource  
  5.     private UserMapper userMapper;  
  6.       
  7.     @Override  
  8.     public UserDetails loadUserByUsername(String username)  
  9.             throws UsernameNotFoundException {  
  10.         System.out.println("username is " + username);    
  11.         UserPO user = userMapper.getUserByUserName(username);  
  12.         if(user == null) {    
  13.             throw new UsernameNotFoundException(username);    
  14.         }    
  15.         Collection<GrantedAuthority> grantedAuths = obtionGrantedAuthorities(user);  
  16.           System.out.println(user.getUsername());  
  17.         return new User(  
  18.                 user.getUsername(),    
  19.                 user.getPassword(),     
  20.                 true,   
  21.                 true,  
  22.                 true,  
  23.                 true,  
  24.                 grantedAuths);    
  25.     }  
  26.   
  27.     //取得用户的权限    
  28.     private Set<GrantedAuthority> obtionGrantedAuthorities(UserPO user) {    
  29.         Set<GrantedAuthority> authSet = new HashSet<GrantedAuthority>();    
  30.         List<RolePO> roles = user.getRoles();    
  31.             
  32.         for(RolePO role : roles) {    
  33.             RolePO innerRole = userMapper.getRoleByRoleName(role.getRoleName());  
  34.             List<ResourcePO> tempRes = innerRole.getResources();  
  35.             for(ResourcePO res : tempRes) {    
  36.                 authSet.add(new GrantedAuthorityImpl(res.getResourceName()));    
  37.            }    
  38.         }    
  39.         return authSet;    
  40.     }    
  41.       
  42.      
  43. }  

到这里,所有的权限-角色-用户信息已经可以串起来了。再来梳理一下流程,启动服务器时,通过FilterInvocationSecurityMetadataSource获得用户的所有角色及权限信息,当用户登陆时,通过MyUserDetailsServiceImpl中的loadUserByUsername获得该登陆用户所有的权限,发出请求时,通过FilterInvocationSecurityMetadataSource的getAttributes(Object url)获得需要的权限名,最后在AccessDecisionManager中decide方法进行对比,如果用户拥有的权限名称和该url需要的权限名相同,那么放行,否则认证失败!清楚这些后,我们还需要一个filter,把上述流程串起来,就像提葡萄一样~

[java] view plain copy
 技术分享技术分享
  1. /**  
  2.  * @description 一个自定义的filter,  
  3.  *  必须包含authenticationManager,accessDecisionManager,securityMetadataSource三个属性,  
  4.         我们的所有控制将在这三个类中实现  
  5.  */   
  6. public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter{  
  7.   
  8.      private FilterInvocationSecurityMetadataSource fisMetadataSource;    
  9.        
  10.           
  11.   
  12.         /* (non-Javadoc)  
  13.          * @see org.springframework.security.access.intercept.AbstractSecurityInterceptor#getSecureObjectClass()  
  14.          */    
  15.         @Override    
  16.         public Class<?> getSecureObjectClass() {    
  17.             return FilterInvocation.class;    
  18.         }    
  19.         
  20.         @Override    
  21.         public SecurityMetadataSource obtainSecurityMetadataSource() {    
  22.             return fisMetadataSource;    
  23.         }    
  24.         
  25.         @Override    
  26.         public void destroy() {}    
  27.             
  28.         @Override    
  29.         public void doFilter(ServletRequest request, ServletResponse response,    
  30.                 FilterChain chain) throws IOException, ServletException {    
  31.             System.out.println("------------MyFilterSecurityInterceptor.doFilter()-----------开始拦截器了....");    
  32.             FilterInvocation fi = new FilterInvocation(request, response, chain);    
  33.             InterceptorStatusToken token = super.beforeInvocation(fi);    
  34.               
  35.             try {    
  36.                 fi.getChain().doFilter(fi.getRequest(), fi.getResponse());    
  37.             } catch (Exception e) {    
  38.                 e.printStackTrace();    
  39.             }finally{    
  40.                 super.afterInvocation(token,null);    
  41.             }    
  42.               
  43.             System.out.println("------------MyFilterSecurityInterceptor.doFilter()-----------拦截器该方法结束了....");    
  44.         }    
  45.          
  46.         @Override    
  47.         public void init(FilterConfig config) throws ServletException {    
  48.                 
  49.         }    
  50.             
  51.             
  52.         public void setFisMetadataSource(    
  53.                 FilterInvocationSecurityMetadataSource fisMetadataSource) {    
  54.             this.fisMetadataSource = fisMetadataSource;    
  55.         }    
  56.         public FilterInvocationSecurityMetadataSource getFisMetadataSource() {  
  57.             return fisMetadataSource;  
  58.         }  
  59.   
  60. }  

如果全部照搬上边的代码,到这里就已经结束了。

但是昨天晚上遇到一个大坑,就是发现如果我在数据库中配置了该用户的相关权限url后,用户可以访问,如果用户没有该url的权限,该用户依然可以访问url,这是让我无比吃惊,因为大部分都是参考网络的资料写的,后来看了一下ss的源码,才发现可能是其他人写错了。这里简单说一下,因为单位电脑没有ss的源码,主要问题出在MyFilterSecurityInterceptor中的doFilter方法:InterceptorStatusToken token = super.beforeInvocation(fi);  当ss在未匹配到url的权限时,即MySecurityMetadataSource中的getAttributes返回的对象为空时,该方法beforeInvocation直接return null,而实际decide方法在下方并未运行。

[java] view plain copy
 技术分享技术分享
  1. protected InterceptorStatusToken beforeInvocation(Object object) {  
  2.   
  3.        if (!getSecureObjectClass().isAssignableFrom(object.getClass())) {  
  4.            .....  
  5.        }  
  6.   
  7.        Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object);  
  8.   
  9.        if (attributes == null || attributes.isEmpty()) {//此处判断MySecurityMetadataSource中的getAttributes返回的对象  
  10.            if (rejectPublicInvocations) {  
  11.                throw new IllegalArgumentException("Secure object invocation " + object +  
  12.                        " was denied as public invocations are not allowed via this interceptor. "  
  13.                                + "This indicates a configuration error because the "  
  14.                                + "rejectPublicInvocations property is set to ‘true‘");  
  15.            }  
  16.   
  17.            if (debug) {  
  18.                logger.debug("Public object - authentication not attempted");  
  19.            }  
  20.   
  21.            publishEvent(new PublicInvocationEvent(object));  
  22.   
  23.            return null// no further work post-invocation  
  24.        }  
  25.   
  26.        if (debug) {  
  27.            logger.debug("Secure object: " + object + "; Attributes: " + attributes);  
  28.        }  
  29.   
  30.        if (SecurityContextHolder.getContext().getAuthentication() == null) {  
  31.            credentialsNotFound(messages.getMessage("AbstractSecurityInterceptor.authenticationNotFound",  
  32.                    "An Authentication object was not found in the SecurityContext"), object, attributes);  
  33.        }  
  34.   
  35.        Authentication authenticated = authenticateIfRequired();//实际运行decide方法的地方  
  36.   
  37.        // Attempt authorization  
  38.        try {  
  39.            this.accessDecisionManager.decide(authenticated, object, attributes);  
  40.        }  
  41.        catch (AccessDeniedException accessDeniedException) {  
  42.            publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated, accessDeniedException));  
  43.   
  44.            throw accessDeniedException;  
  45.        }  
  46.   
  47.        if (debug) {  
  48.            logger.debug("Authorization successful");  
  49.        }  
  50.   
  51.        if (publishAuthorizationSuccess) {  
  52.            publishEvent(new AuthorizedEvent(object, attributes, authenticated));  
  53.        }  
  54.   
  55.        // Attempt to run as a different user  
  56.        Authentication runAs = this.runAsManager.buildRunAs(authenticated, object, attributes);  
  57.   
  58.        if (runAs == null) {  
  59.            if (debug) {  
  60.                logger.debug("RunAsManager did not change Authentication object");  
  61.            }  
  62.   
  63.            // no further work post-invocation  
  64.            return new InterceptorStatusToken(SecurityContextHolder.getContext(), false, attributes, object);  
  65.        } else {  
  66.            if (debug) {  
  67.                logger.debug("Switching to RunAs Authentication: " + runAs);  
  68.            }  
  69.   
  70.            SecurityContext origCtx = SecurityContextHolder.getContext();  
  71.            SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext());  
  72.            SecurityContextHolder.getContext().setAuthentication(runAs);  
  73.   
  74.            // need to revert to token.Authenticated post-invocation  
  75.            return new InterceptorStatusToken(origCtx, true, attributes, object);  
  76.        }  
  77.    }  


在我看的所有BLOG中,当匹配不到时,全部返回了Null,而当我追到 super.beforeInvocation(fi)源码中时,发现当getAttributes返回null后,ss就会跳过AccessDecisionManager的decide方法,导致未进行判断!从而ss会让用户请求顺利的通过。之后,查了一下ss官方英文文档,如下描述:

Collection<ConfigAttribute> getAttributes(Object object)
                                          throws IllegalArgumentException
Accesses the ConfigAttributes that apply to a given secure object.
Parameters:
object - the object being secured
Returns:
the attributes that apply to the passed in secured object. Should return an empty collection if there are no applicable attributes.
Throws:
IllegalArgumentException - if the passed object is not of a type supported by the SecurityMetadataSource implementation

红色标出了,应当返回一个空的对象集合如果没有相应权限的时候。而其他blog文返回的是null,导致后续跳过了decide方法!所以我在MySecurityMetadataSource中的getAttributes中写的是:

[java] view plain copy
 技术分享技术分享
  1. Collection<ConfigAttribute> nouse = new ArrayList<ConfigAttribute>();  
  2. nouse.add(new SecurityConfig("无相应权限"));  
  3. return nouse;  
这样当没有权限时,才可以正常拦截!现在博文抄来抄去,正确的还好,但凡有错误。。真是坑死人。

SpringSecurity3.2.5自定义角色及权限的教程