1. 程式人生 > >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勢必是優先配置的。

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >


<web-app>
  <display-name>Archetype Created Web Application</display-name>
      <!-- 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>  
	 <!-- Spring MVC配置 -->
	<servlet>
	    <servlet-name>spring</servlet-name>
	    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
	     <init-param>
	        <param-name>contextConfigLocation</param-name>
	        <param-value>classpath:spring-mvc.xml</param-value>
	    </init-param>
	    <load-on-startup>1</load-on-startup>
	</servlet>
	 
	<servlet-mapping>
	    <servlet-name>spring</servlet-name>
	    <url-pattern>*.do</url-pattern>
	</servlet-mapping>
	 
	 
	<!-- Spring配置 -->
	<listener>
	    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>
	<listener>
		<listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
	</listener>
	 
	<!-- 指定Spring Bean的配置檔案所在目錄。預設配置在WEB-INF目錄下 -->
	<context-param>
	    <param-name>contextConfigLocation</param-name>
	    <param-value>classpath:applicationContext*.xml,classpath:spring-mybatis.xml</param-value>
	</context-param>
	<filter>
		<filter-name>encodingFilter</filter-name>
		<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
		<init-param>
			<param-name>encoding</param-name>
			<param-value>UTF-8</param-value>
		</init-param>
	</filter>
	
	 <!-- Spring 重新整理Introspector防止記憶體洩露 -->  
    <listener>  
        <listener-class>  
            org.springframework.web.util.IntrospectorCleanupListener  
        </listener-class>  
    </listener>  
      
    <!--  獲取Spring Security session的生命週期-->  
    <listener>  
        <listener-class>  
            org.springframework.security.web.session.HttpSessionEventPublisher   
        </listener-class>  
    </listener>  
  
    <!-- session超時定義,單位為分鐘 -->  
    <session-config>  
        <session-timeout>20</session-timeout>  
    </session-config>  
</web-app>

接下來是spring security3的一些配置,具體的每一個是什麼意思,網上很多資料,這裡不贅述了。總之,需要根據自己的需求,進行相應的修改。
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"  
       xmlns:beans="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
       xmlns:aop="http://www.springframework.org/schema/aop" 
       xmlns:tx="http://www.springframework.org/schema/tx" 
       xmlns:context="http://www.springframework.org/schema/context" 
       xmlns:mvc="http://www.springframework.org/schema/mvc" 
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd 
       http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd 
       http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd 
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd
       http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
       http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.2.xsd">
   <http pattern="/css/**" security="none"></http>   
   <http pattern="/images/**" security="none"></http>   
   <http pattern="/img/**" security="none"></http>   
   <http pattern="/scripts/**" security="none"></http>
   <http pattern="/font-awesome/**" security="none"></http>
   <http pattern="/system/resources/**" security="none"></http>
   <http pattern="/system/login.do" security="none"/>
   <http auto-config="true" use-expressions="true">
     <form-login login-page="/system/login.do"  default-target-url="/system/sysManage.do"/>
      <!--   
         error-if-maximum-exceeded 後登陸的賬號會擠掉第一次登陸的賬號   
         session-fixation-protection  防止偽造sessionid攻擊,使用者登入成功後會銷燬使用者當前的session。  
    -->  
    <!-- <session-management invalid-session-url="/user/timedout" session-fixation-protection="none">  
        <concurrency-control max-sessions="1" error-if-maximum-exceeded="true"/>  
    </session-management> -->
     <custom-filter ref="myFilterSecurityInterceptor" before="FILTER_SECURITY_INTERCEPTOR"/>
   </http>
   <!-- 認證管理器,實現使用者認證的入口,主要實現UserDetailsService介面即可 -->
    <authentication-manager alias="authenticationManager">
        <authentication-provider
            user-service-ref="myUserDetailsServiceImpl">
                <!-- <password-encoder hash="md5" /> -->  <!--鹽值  [新增這個屬性後,加密密碼明文為:"密碼明文{鹽值}"]  --> 
        </authentication-provider>
    </authentication-manager>
     <!-- 配置過濾器 -->  
    <beans:bean id="myFilterSecurityInterceptor" class="com.product.sys.security.MyFilterSecurityInterceptor">  
        <!-- 使用者是否擁有所請求資源的許可權 -->  
        <beans:property name="accessDecisionManager" ref="myAccessDescisionManager" />  
        <!-- 資源與許可權對應關係 -->  
        <beans:property name="fisMetadataSource" ref="mySecurityMetadataSource" />  
        <!-- 使用者擁有的許可權 -->  
        <beans:property name="authenticationManager" ref="authenticationManager" /> 
    </beans:bean>  
    <beans:bean id="mySecurityMetadataSource" class="com.product.sys.security.MySecurityMetadataSource"><beans:constructor-arg name="userMapper" ref="userMapper"></beans:constructor-arg></beans:bean>
    <beans:bean id="myAccessDescisionManager" class="com.product.sys.security.MyAccessDescisionManager"></beans:bean>
	
</beans:beans>

到上面的這個配置檔案,則是重中之重了,和ss3打交道,主要都是這個檔案。簡單說一下,我們需要實現一個自己的filter,在配置中就是myFilterSecurityInterceptor,而這個filter中,還需要我們額外注入三個bean,分別是accessDecisionManager、fisMetadataSource以及authenticationManager,這三個屬性中除了fisMetadataSource可以自定義名稱外,其他兩個都在ss3的父類中定義好了,所以此處需要特別注意,在這裡掉過坑了。另外這裡說一下這三個分別的作用,accessDecisionManager中有decide(Authentication authentication, Object object,Collection<ConfigAttribute> configAttributes)方法,該方法用於判斷當前使用者是否有許可權進行操作,引數中authentication包含了當前使用者所擁有的許可權,configAttributes中包含了進行該步驟需要的許可權,對其進行對比就可以判斷該使用者是否有許可權進行操作。

/** 
 * @description  訪問決策器,決定某個使用者具有的角色,是否有足夠的許可權去訪問某個資源 ;做最終的訪問控制決定 

 */ 
public class MyAccessDescisionManager implements AccessDecisionManager{

	@Override
	public void decide(Authentication authentication, Object object,
			Collection<ConfigAttribute> configAttributes)
			throws AccessDeniedException, InsufficientAuthenticationException {
		// TODO Auto-generated method stub
		 System.out.println("MyAccessDescisionManager.decide()------------------驗證使用者是否具有一定的許可權--------");  
	        if(configAttributes==null) return;  
	        Iterator<ConfigAttribute> it = configAttributes.iterator();  
	        while(it.hasNext()){  
	            String needResource = it.next().getAttribute();  
	            //authentication.getAuthorities()  使用者所有的許可權  
	            for(GrantedAuthority ga:authentication.getAuthorities()){  
	                if(needResource.equals(ga.getAuthority())){  
	                    return;  
	                }  
	            }  
	        }  
	        throw new AccessDeniedException("--------MyAccessDescisionManager:decide-------許可權認證失敗!");  
		
	}

	@Override
	public boolean supports(ConfigAttribute attribute) {
		// TODO Auto-generated method stub
		return true;
	}

	@Override
	public boolean supports(Class<?> clazz) {
		// TODO Auto-generated method stub
		return true;
	}

}

到這裡,可以很自然的想到是許可權和使用者資料從哪裡得到的,filterInvocationSecurityMetadataSource在被載入時候,會首先將許可權的資訊建立起來,這裡我用一個map,key為url,value為該許可權的名稱,這一步是在構造方法中進行的,也就是伺服器啟動時候完成的。而當用戶訪問某一個地址時,ss會到該類中呼叫getAttributes(Object obj)方法,obj中包含了訪問的url地址,我們需要做的就是將該url對應的許可權名稱返回給ss,而ss會將返回的這個物件,其實就是accessDecisionManager的decide方法中的configAttributes物件。

/** 
 * @description  資源源資料定義,將所有的資源和許可權對應關係建立起來,即定義某一資源可以被哪些角色訪問 
 * @author aokunsang 
 * @date 2012-8-15 
 */  
public class MySecurityMetadataSource implements FilterInvocationSecurityMetadataSource {  

    private UserMapper userMapper;  
    public UserMapper getUserMapper() {
		return userMapper;
	}

	public void setUserMapper(UserMapper userMapper) {
		this.userMapper = userMapper;
	}

	/* 儲存資源和許可權的對應關係  key-資源url  value-許可權 */  
    private static Map<String,Collection<ConfigAttribute>> resourceMap = null;   
    private AntPathMatcher urlMatcher = new AntPathMatcher();  
      
    public MySecurityMetadataSource(UserMapper userMapper) {  
        this.userMapper = userMapper;  
        loadResourcesDefine();  
    }  
      
    @Override  
    public Collection<ConfigAttribute> getAllConfigAttributes() {  
        return null;  
    }  
  
    private void loadResourcesDefine(){  
        resourceMap = new HashMap<String,Collection<ConfigAttribute>>();  
          
        System.out.println("MySecurityMetadataSource.loadResourcesDefine()--------------開始載入資源列表資料--------");  
        List<RolePO> roles = userMapper.findAllRoles();  
        for(RolePO role : roles){  
            List<ResourcePO> resources = role.getResources(); 
            for(ResourcePO resource : resources){  
                Collection<ConfigAttribute> configAttributes = null;  
                ConfigAttribute configAttribute = new SecurityConfig(resource.getResourceName());  
                if(resourceMap.containsKey(resource.getUrl())){  
                    configAttributes = resourceMap.get(resource.getUrl());  
                    configAttributes.add(configAttribute);  
                }else{  
                    configAttributes = new ArrayList<ConfigAttribute>() ;  
                    configAttributes.add(configAttribute);  
                    resourceMap.put(resource.getUrl(), configAttributes);  
                }  
            }  
        }
        System.out.println("11");
        Set<String> set = resourceMap.keySet();
        Iterator<String> it = set.iterator();
        while(it.hasNext()){
        	String s = it.next();
        	System.out.println("key:"+s+"|value:"+resourceMap.get(s));
        }
    }  
    /*  
     * 根據請求的資源地址,獲取它所擁有的許可權 
     */  
    @Override  
    public Collection<ConfigAttribute> getAttributes(Object obj)  
            throws IllegalArgumentException {  
        //獲取請求的url地址  
        String url = ((FilterInvocation)obj).getRequestUrl();  
        System.out.println("MySecurityMetadataSource:getAttributes()---------------請求地址為:"+url);  
        Iterator<String> it = resourceMap.keySet().iterator();  
        while(it.hasNext()){  
            String _url = it.next();  
            if(_url.indexOf("?")!=-1){  
                _url = _url.substring(0, _url.indexOf("?"));  
            }  
            if(urlMatcher.match(_url,url)){
            	System.out.println("MySecurityMetadataSource:getAttributes()---------------需要的許可權是:"+resourceMap.get(_url));  
                return resourceMap.get(_url);  
            }
            	
        }
        Collection<ConfigAttribute> nouse = new ArrayList<ConfigAttribute>();
        nouse.add(new SecurityConfig("無相應許可權"));
        return nouse;
    }  
  
    @Override  
    public boolean supports(Class<?> arg0) {  
        System.out.println("MySecurityMetadataSource.supports()---------------------");  
        return true;  
    }  
      
}  

到這裡,我們還有一個疑問,就是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也簡單,當然有更好的可以替換掉。

@Component("myUserDetailsServiceImpl")
public class MyUserDetailsServiceImpl implements UserDetailsService{

	@Resource
	private UserMapper userMapper;
	
	@Override
	public UserDetails loadUserByUsername(String username)
			throws UsernameNotFoundException {
		System.out.println("username is " + username);  
        UserPO user = userMapper.getUserByUserName(username);
        if(user == null) {  
            throw new UsernameNotFoundException(username);  
        }  
        Collection<GrantedAuthority> grantedAuths = obtionGrantedAuthorities(user);
          System.out.println(user.getUsername());
        return new User(
                user.getUsername(),  
                user.getPassword(),   
                true, 
                true,
                true,
                true,
                grantedAuths);  
	}

	//取得使用者的許可權  
    private Set<GrantedAuthority> obtionGrantedAuthorities(UserPO user) {  
        Set<GrantedAuthority> authSet = new HashSet<GrantedAuthority>();  
        List<RolePO> roles = user.getRoles();  
          
        for(RolePO role : roles) {  
        	RolePO innerRole = userMapper.getRoleByRoleName(role.getRoleName());
            List<ResourcePO> tempRes = innerRole.getResources();
            for(ResourcePO res : tempRes) {  
                authSet.add(new GrantedAuthorityImpl(res.getResourceName()));  
           }  
        }  
        return authSet;  
    }  
    
   
}

到這裡,所有的許可權-角色-使用者資訊已經可以串起來了。再來梳理一下流程,啟動伺服器時,通過FilterInvocationSecurityMetadataSource獲得使用者的所有角色及許可權資訊,當用戶登陸時,通過MyUserDetailsServiceImpl中的loadUserByUsername獲得該登陸使用者所有的許可權,發出請求時,通過FilterInvocationSecurityMetadataSource的getAttributes(Object url)獲得需要的許可權名,最後在AccessDecisionManager中decide方法進行對比,如果使用者擁有的許可權名稱和該url需要的許可權名相同,那麼放行,否則認證失敗!清楚這些後,我們還需要一個filter,把上述流程串起來,就像提葡萄一樣~
/** 
 * @description 一個自定義的filter, 
 *  必須包含authenticationManager,accessDecisionManager,securityMetadataSource三個屬性, 
        我們的所有控制將在這三個類中實現 
 */ 
public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter{

	 private FilterInvocationSecurityMetadataSource fisMetadataSource;  
     
	    

		/* (non-Javadoc) 
	     * @see org.springframework.security.access.intercept.AbstractSecurityInterceptor#getSecureObjectClass() 
	     */  
	    @Override  
	    public Class<?> getSecureObjectClass() {  
	        return FilterInvocation.class;  
	    }  
	  
	    @Override  
	    public SecurityMetadataSource obtainSecurityMetadataSource() {  
	        return fisMetadataSource;  
	    }  
	  
	    @Override  
	    public void destroy() {}  
	      
	    @Override  
	    public void doFilter(ServletRequest request, ServletResponse response,  
	            FilterChain chain) throws IOException, ServletException {  
	        System.out.println("------------MyFilterSecurityInterceptor.doFilter()-----------開始攔截器了....");  
	        FilterInvocation fi = new FilterInvocation(request, response, chain);  
	        InterceptorStatusToken token = super.beforeInvocation(fi);  
	        
	        try {  
	            fi.getChain().doFilter(fi.getRequest(), fi.getResponse());  
	        } catch (Exception e) {  
	            e.printStackTrace();  
	        }finally{  
	            super.afterInvocation(token,null);  
	        }  
	        
	        System.out.println("------------MyFilterSecurityInterceptor.doFilter()-----------攔截器該方法結束了....");  
	    }  
	   
	    @Override  
	    public void init(FilterConfig config) throws ServletException {  
	          
	    }  
	      
	      
	    public void setFisMetadataSource(  
	            FilterInvocationSecurityMetadataSource fisMetadataSource) {  
	        this.fisMetadataSource = fisMetadataSource;  
	    }  
	    public FilterInvocationSecurityMetadataSource getFisMetadataSource() {
			return fisMetadataSource;
		}

}

如果全部照搬上邊的程式碼,到這裡就已經結束了。

但是昨天晚上遇到一個大坑,就是發現如果我在資料庫中配置了該使用者的相關許可權url後,使用者可以訪問,如果使用者沒有該url的許可權,該使用者依然可以訪問url,這是讓我無比吃驚,因為大部分都是參考網路的資料寫的,後來看了一下ss的原始碼,才發現可能是其他人寫錯了。這裡簡單說一下,因為單位電腦沒有ss的原始碼,主要問題出在MyFilterSecurityInterceptor中的doFilter方法:InterceptorStatusToken token = super.beforeInvocation(fi);  當ss在未匹配到url的許可權時,即MySecurityMetadataSource中的getAttributes返回的物件為空時,該方法beforeInvocation直接return null,而實際decide方法在下方並未執行。

 protected InterceptorStatusToken beforeInvocation(Object object) {

        if (!getSecureObjectClass().isAssignableFrom(object.getClass())) {
            .....
        }

        Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object);

        if (attributes == null || attributes.isEmpty()) {//此處判斷MySecurityMetadataSource中的getAttributes返回的物件
            if (rejectPublicInvocations) {
                throw new IllegalArgumentException("Secure object invocation " + object +
                        " was denied as public invocations are not allowed via this interceptor. "
                                + "This indicates a configuration error because the "
                                + "rejectPublicInvocations property is set to 'true'");
            }

            if (debug) {
                logger.debug("Public object - authentication not attempted");
            }

            publishEvent(new PublicInvocationEvent(object));

            return null; // no further work post-invocation
        }

        if (debug) {
            logger.debug("Secure object: " + object + "; Attributes: " + attributes);
        }

        if (SecurityContextHolder.getContext().getAuthentication() == null) {
            credentialsNotFound(messages.getMessage("AbstractSecurityInterceptor.authenticationNotFound",
                    "An Authentication object was not found in the SecurityContext"), object, attributes);
        }

        Authentication authenticated = authenticateIfRequired();//實際執行decide方法的地方

        // Attempt authorization
        try {
            this.accessDecisionManager.decide(authenticated, object, attributes);
        }
        catch (AccessDeniedException accessDeniedException) {
            publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated, accessDeniedException));

            throw accessDeniedException;
        }

        if (debug) {
            logger.debug("Authorization successful");
        }

        if (publishAuthorizationSuccess) {
            publishEvent(new AuthorizedEvent(object, attributes, authenticated));
        }

        // Attempt to run as a different user
        Authentication runAs = this.runAsManager.buildRunAs(authenticated, object, attributes);

        if (runAs == null) {
            if (debug) {
                logger.debug("RunAsManager did not change Authentication object");
            }

            // no further work post-invocation
            return new InterceptorStatusToken(SecurityContextHolder.getContext(), false, attributes, object);
        } else {
            if (debug) {
                logger.debug("Switching to RunAs Authentication: " + runAs);
            }

            SecurityContext origCtx = SecurityContextHolder.getContext();
            SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext());
            SecurityContextHolder.getContext().setAuthentication(runAs);

            // need to revert to token.Authenticated post-invocation
            return new InterceptorStatusToken(origCtx, true, attributes, object);
        }
    }


在我看的所有BLOG中,當匹配不到時,全部返回了Null,而當我追到 super.beforeInvocation(fi)原始碼中時,發現當getAttributes返回null後,ss就會跳過AccessDecisionManager的decide方法,導致未進行判斷!從而ss會讓使用者請求順利的通過。之後,查了一下ss官方英文文件,如下描述:

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:
 - if the passed object is not of a type supported by the SecurityMetadataSource implementation

紅色標出了,應當返回一個空的物件集合如果沒有相應許可權的時候。而其他blog文返回的是null,導致後續跳過了decide方法!所以我在MySecurityMetadataSource中的getAttributes中寫的是:

        Collection<ConfigAttribute> nouse = new ArrayList<ConfigAttribute>();
        nouse.add(new SecurityConfig("無相應許可權"));
        return nouse;
這樣當沒有許可權時,才可以正常攔截!現在博文抄來抄去,正確的還好,但凡有錯誤。。真是坑死人。

這裡發下幾個幫助比較大的供參考:

http://aokunsang.iteye.com/blog/1638558

http://blog.csdn.net/k10509806/article/details/6369131

和只允許登陸一次的具體方法,需要重寫UserPO中的hashCode和equal方法。

http://flashing.iteye.com/blog/823666