spring security的原理及教程

spring security使用分類:

如何使用spring security,相信百度過的都知道,總共有四種用法,從簡到深爲:1、不用數據庫,全部數據寫在配置文件,這個也是官方文檔裏面的demo;2、使用數據庫,根據spring security默認實現代碼設計數據庫,也就是說數據庫已經固定了,這種方法不靈活,而且那個數據庫設計得很簡陋,實用性差;3、spring security和Acegi不同,它不能修改默認filter了,但支持插入filter,所以根據這個,我們可以插入自己的filter來靈活使用;4、暴力手段,修改源碼,前面說的修改默認filter只是修改配置文件以替換filter而已,這種是直接改了裏面的源碼,但是這種不符合OO設計原則,而且不實際,不可用。

本文面向讀者:

因爲本文準備介紹第三種方法,所以面向的讀者是已經具備了spring security基礎知識的。不過不要緊,讀者可以先看一下這個教程,看完應該可以使用第二種方法開發了。

spring security的簡單原理:

使用衆多的攔截器對url攔截,以此來管理權限。但是這麼多攔截器,筆者不可能對其一一來講,主要講裏面核心流程的兩個。
首先,權限管理離不開登陸驗證的,所以登陸驗證攔截器AuthenticationProcessingFilter要講;
還有就是對訪問的資源管理吧,所以資源管理攔截器AbstractSecurityInterceptor要講;
但攔截器裏面的實現需要一些組件來實現,所以就有了AuthenticationManager、accessDecisionManager等組件來支撐。
    現在先大概過一遍整個流程,用戶登陸,會被AuthenticationProcessingFilter攔截,調用AuthenticationManager的實現,而且AuthenticationManager會調用ProviderManager來獲取用戶驗證信息(不同的Provider調用的服務不同,因爲這些信息可以是在數據庫上,可以是在LDAP服務器上,可以是xml配置文件上等),如果驗證通過後會將用戶的權限信息封裝一個User放到spring的全局緩存SecurityContextHolder中,以備後面訪問資源時使用。
訪問資源(即授權管理),訪問url時,會通過AbstractSecurityInterceptor攔截器攔截,其中會調用FilterInvocationSecurityMetadataSource的方法來獲取被攔截url所需的全部權限,在調用授權管理器AccessDecisionManager,這個授權管理器會通過spring的全局緩存SecurityContextHolder獲取用戶的權限信息,還會獲取被攔截的url和被攔截url所需的全部權限,然後根據所配的策略(有:一票決定,一票否定,少數服從多數等),如果權限足夠,則返回,權限不夠則報錯並調用權限不足頁面。
    雖然講得好像好複雜,讀者們可能有點暈,不過不打緊,真正通過代碼的講解在後面,讀者可以看完後面的代碼實現,再返回看這個簡單的原理,可能會有不錯的收穫。

spring security使用實現(基於spring security3.1.4):

javaEE的入口:web.xml:
[html] view plain copy
 print?
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"  
  3.     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  4.     xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">   
  5.      <!--加載Spring XML配置文件 -->  
  6.     <context-param>  
  7.         <param-name>contextConfigLocation</param-name>  
  8.         <param-value> classpath:securityConfig.xml           </param-value>  
  9.     </context-param>   
  10.       <!-- Spring Secutiry3.1的過濾器鏈配置 -->  
  11.     <filter>  
  12.         <filter-name>springSecurityFilterChain</filter-name>  
  13.         <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>  
  14.     </filter>  
  15.     <filter-mapping>  
  16.         <filter-name>springSecurityFilterChain</filter-name>  
  17.         <url-pattern>/*</url-pattern>  
  18.     </filter-mapping>   
  19.        <!-- Spring 容器啓動監聽器 -->  
  20.     <listener>  
  21.         <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>  
  22.     </listener>     
  23.       <!--系統歡迎頁面 -->  
  24.     <welcome-file-list>  
  25.         <welcome-file>index.jsp</welcome-file>  
  26.     </welcome-file-list>  
  27. </web-app>  

上面那個配置不用多說了吧
直接上spring security的配置文件securityConfig.xml:
[html] view plain copy
 print?
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <b:beans xmlns="http://www.springframework.org/schema/security"  
  3.     xmlns:b="http://www.springframework.org/schema/beans"  
  4.     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  5.     xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd  
  6.                         http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd">  
  7.   
  8.   <!--登錄頁面不過濾 -->  
  9.     <http pattern="/login.jsp" security="none" />  
  10.     <http access-denied-page="/accessDenied.jsp">  
  11.         <form-login login-page="/login.jsp" />  
  12.         <!--訪問/admin.jsp資源的用戶必須具有ROLE_ADMIN的權限 -->  
  13.         <!-- <intercept-url pattern="/admin.jsp" access="ROLE_ADMIN" /> -->  
  14.         <!--訪問/**資源的用戶必須具有ROLE_USER的權限 -->  
  15.         <!-- <intercept-url pattern="/**" access="ROLE_USER" /> -->  
  16.         <session-management>  
  17.             <concurrency-control max-sessions="1"  
  18.                 error-if-maximum-exceeded="false" />  
  19.         </session-management>  
  20.         <!--增加一個filter,這點與 Acegi是不一樣的,不能修改默認的filter了, 這個filter位於FILTER_SECURITY_INTERCEPTOR之前 -->  
  21.         <custom-filter ref="myFilter" before="FILTER_SECURITY_INTERCEPTOR" />  
  22.     </http>  
  23.     <!--一個自定義的filter,必須包含 authenticationManager,accessDecisionManager,securityMetadataSource三個屬性,   
  24.         我們的所有控制將在這三個類中實現,解釋詳見具體配置 -->  
  25.     <b:bean id="myFilter"  
  26.         class="com.erdangjiade.spring.security.MyFilterSecurityInterceptor">  
  27.         <b:property name="authenticationManager" ref="authenticationManager" />  
  28.         <b:property name="accessDecisionManager" ref="myAccessDecisionManagerBean" />  
  29.         <b:property name="securityMetadataSource" ref="securityMetadataSource" />  
  30.     </b:bean>  
  31.     <!--驗證配置,認證管理器,實現用戶認證的入口,主要實現UserDetailsService接口即可 -->  
  32.     <authentication-manager alias="authenticationManager">  
  33.         <authentication-provider user-service-ref="myUserDetailService">  
  34.             <!--如果用戶的密碼採用加密的話 <password-encoder hash="md5" /> -->  
  35.         </authentication-provider>  
  36.     </authentication-manager>  
  37.     <!--在這個類中,你就可以從數據庫中讀入用戶的密碼,角色信息,是否鎖定,賬號是否過期等 -->  
  38.     <b:bean id="myUserDetailService" class="com.erdangjiade.spring.security.MyUserDetailService" />  
  39.     <!--訪問決策器,決定某個用戶具有的角色,是否有足夠的權限去訪問某個資源 -->  
  40.     <b:bean id="myAccessDecisionManagerBean"  
  41.         class="com.erdangjiade.spring.security.MyAccessDecisionManager">  
  42.     </b:bean>  
  43.     <!--資源源數據定義,將所有的資源和權限對應關係建立起來,即定義某一資源可以被哪些角色訪問 -->  
  44.     <b:bean id="securityMetadataSource"  
  45.         class="com.erdangjiade.spring.security.MyInvocationSecurityMetadataSource" />   
  46.             
  47.  </b:beans>  

其實所有配置都在<http></http>裏面,首先這個版本的spring security不支持了filter=none的配置了,改成了獨立的<http pattern="/login.jsp" security="none"/>,裏面你可以配登陸頁面、權限不足的返回頁面、註銷頁面等,上面那些配置,我註銷了一些資源和權限的對應關係,筆者這裏不需要在這配死它,可以自己寫攔截器來獲得資源與權限的對應關係。
session-management是用來防止多個用戶同時登陸一個賬號的。
    最重要的是筆者自己寫的攔截器myFilter(終於講到重點了),首先這個攔截器會加載在FILTER_SECURITY_INTERCEPTOR之前(配置文件上有說),最主要的是這個攔截器裏面配了三個處理類,第一個是authenticationManager,這個是處理驗證的,這裏需要特別說明的是:這個類不單隻這個攔截器用到,還有驗證攔截器AuthenticationProcessingFilter也用到 了,而且實際上的登陸驗證也是AuthenticationProcessingFilter攔截器調用authenticationManager來處理的,我們這個攔截器只是爲了拿到驗證用戶信息而已(這裏不太清楚,因爲authenticationManager筆者設了斷點,用戶登陸後再也沒調用這個類了,而且調用這個類時不是筆者自己寫的那個攔截器調用的,看了spring技術內幕這本書才知道是AuthenticationProcessingFilter攔截器調用的)。
securityMetadataSource這個用來加載資源與權限的全部對應關係的,並提供一個通過資源獲取所有權限的方法。
accessDecisionManager這個也稱爲授權器,通過登錄用戶的權限信息、資源、獲取資源所需的權限來根據不同的授權策略來判斷用戶是否有權限訪問資源。

authenticationManager類可以有許多provider(提供者)提供用戶驗證信息,這裏筆者自己寫了一個類myUserDetailService來獲取用戶信息。
MyUserDetailService:
[java] view plain copy
 print?
  1. package com.erdangjiade.spring.security;  
  2.   
  3. import java.util.ArrayList;  
  4. import java.util.Collection;  
  5.   
  6. import org.springframework.dao.DataAccessException;  
  7. import org.springframework.security.core.GrantedAuthority;  
  8. import org.springframework.security.core.authority.GrantedAuthorityImpl;  
  9. import org.springframework.security.core.userdetails.User;  
  10. import org.springframework.security.core.userdetails.UserDetails;  
  11. import org.springframework.security.core.userdetails.UserDetailsService;  
  12. import org.springframework.security.core.userdetails.UsernameNotFoundException;  
  13.   
  14. public class MyUserDetailService implements UserDetailsService {   
  15.       
  16.     //登陸驗證時,通過username獲取用戶的所有權限信息,  
  17.     //並返回User放到spring的全局緩存SecurityContextHolder中,以供授權器使用  
  18.     public UserDetails loadUserByUsername(String username)   
  19.             throws UsernameNotFoundException, DataAccessException {     
  20.         Collection<GrantedAuthority> auths=new ArrayList<GrantedAuthority>();   
  21.           
  22.         GrantedAuthorityImpl auth2=new GrantedAuthorityImpl("ROLE_ADMIN");   
  23.         GrantedAuthorityImpl auth1=new GrantedAuthorityImpl("ROLE_USER");   
  24.           
  25.         if(username.equals("lcy")){   
  26.             auths=new ArrayList<GrantedAuthority>();   
  27.             auths.add(auth1);  
  28.             auths.add(auth2);        
  29.         }       
  30.           
  31.         User user = new User(username, "lcy"truetruetruetrue, auths);   
  32.         return user;    
  33.         }   
  34.     }   

其中UserDetailsService接口是spring提供的,必須實現的。別看這個類只有一個方法,而且這麼簡單,其中內涵玄機。
讀者看到這裏可能就大感疑惑了,不是說好的用數據庫嗎?對,但別急,等筆者慢慢給你們解析。
首先,筆者爲什麼不用數據庫,還不是爲了讀者們測試方便,並簡化spring security的流程,讓讀者抓住主線,而不是還要煩其他事(導入數據庫,配置數據庫,寫dao等)。
這裏筆者只是用幾個數據模擬了從數據庫中拿到的數據,也就是說ROLE_ADMIN、ROLE_USER、lcy(第一個是登陸賬號)、lcy(第二個是密碼)是從數據庫拿出來的,這個不難實現吧,如果需要數據庫時,讀者可以用自己寫的dao通過參數username來查詢出這個用戶的權限信息(或是角色信息,就是那個ROLE_*,對必須是ROLE_開頭的,不然spring security不認賬的,其實是spring security裏面做了一個判斷,必須要ROLE_開頭,讀者可以百度改一下),再返回spring自帶的數據模型User即可。
這個寫應該比較清晰、靈活吧,總之數據讀者們通過什麼方法獲取都行,只要返回一個User對象就行了。(這也是筆者爲什麼要重寫這個類的原因)

    通過MyUserDetailService拿到用戶信息後,authenticationManager對比用戶的密碼(即驗證用戶),然後這個AuthenticationProcessingFilter攔截器就過咯。

下面要說的是另外一個攔截器,就是筆者自己寫的攔截器MyFilterSecurityInterceptor:
[java] view plain copy
 print?
  1. package com.erdangjiade.spring.security;  
  2.   
  3. import java.io.IOException;  
  4.   
  5. import javax.servlet.Filter;  
  6. import javax.servlet.FilterChain;  
  7. import javax.servlet.FilterConfig;  
  8. import javax.servlet.ServletException;  
  9. import javax.servlet.ServletRequest;  
  10. import javax.servlet.ServletResponse;  
  11.   
  12. import org.springframework.security.access.SecurityMetadataSource;  
  13. import org.springframework.security.access.intercept.AbstractSecurityInterceptor;  
  14. import org.springframework.security.access.intercept.InterceptorStatusToken;  
  15. import org.springframework.security.web.FilterInvocation;  
  16. import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;  
  17.   
  18. public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor  implements Filter {    
  19.       
  20.     //配置文件注入  
  21.     private FilterInvocationSecurityMetadataSource securityMetadataSource;  
  22.       
  23.     //登陸後,每次訪問資源都通過這個攔截器攔截  
  24.     public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {   
  25.         FilterInvocation fi = new FilterInvocation(request, response, chain);   
  26.         invoke(fi);    
  27.         }  
  28.       
  29.     public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() {    
  30.         return this.securityMetadataSource;    
  31.         }     
  32.       
  33.     public Class<? extends Object> getSecureObjectClass() {   
  34.         return FilterInvocation.class;      
  35.         }    
  36.       
  37.     public void invoke(FilterInvocation fi) throws IOException, ServletException {  
  38.         //fi裏面有一個被攔截的url  
  39.         //裏面調用MyInvocationSecurityMetadataSource的getAttributes(Object object)這個方法獲取fi對應的所有權限  
  40.         //再調用MyAccessDecisionManager的decide方法來校驗用戶的權限是否足夠  
  41.         InterceptorStatusToken token = super.beforeInvocation(fi);  
  42.         try {  
  43.             //執行下一個攔截器  
  44.             fi.getChain().doFilter(fi.getRequest(), fi.getResponse());     
  45.             } finally {   
  46.                 super.afterInvocation(token, null);    
  47.             }     
  48.         }    
  49.     public SecurityMetadataSource obtainSecurityMetadataSource() {   
  50.         return this.securityMetadataSource;     
  51.         }   
  52.     public void setSecurityMetadataSource(  
  53.             FilterInvocationSecurityMetadataSource newSource)  
  54.     {   
  55.         this.securityMetadataSource = newSource;   
  56.     }   
  57.     public void destroy() {    
  58.           
  59.     }     
  60.     public void init(FilterConfig arg0) throws ServletException {    
  61.           
  62.     }    
  63. }  

繼承AbstractSecurityInterceptor、實現Filter是必須的。
首先,登陸後,每次訪問資源都會被這個攔截器攔截,會執行doFilter這個方法,這個方法調用了invoke方法,其中fi斷點顯示是一個url(可能重寫了toString方法吧,但是裏面還有一些方法的),最重要的是beforeInvocation這個方法,它首先會調用MyInvocationSecurityMetadataSource類的getAttributes方法獲取被攔截url所需的權限,在調用MyAccessDecisionManager類decide方法判斷用戶是否夠權限。弄完這一切就會執行下一個攔截器。


再看一下這個MyInvocationSecurityMetadataSource的實現:
[java] view plain copy
 print?
  1. package com.erdangjiade.spring.security;  
  2.   
  3. import java.util.ArrayList;  
  4. import java.util.Collection;  
  5. import java.util.HashMap;  
  6. import java.util.Iterator;  
  7. import java.util.Map;  
  8.   
  9. import org.springframework.security.access.ConfigAttribute;  
  10. import org.springframework.security.access.SecurityConfig;  
  11. import org.springframework.security.web.FilterInvocation;  
  12. import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;  
  13.   
  14. import com.erdangjiade.spring.security.tool.AntUrlPathMatcher;  
  15. import com.erdangjiade.spring.security.tool.UrlMatcher;  
  16.   
  17. public class MyInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {   
  18.     private UrlMatcher urlMatcher = new AntUrlPathMatcher();   
  19.     private static Map<String, Collection<ConfigAttribute>> resourceMap = null;  
  20.       
  21.     //tomcat啓動時實例化一次  
  22.     public MyInvocationSecurityMetadataSource() {  
  23.         loadResourceDefine();    
  24.         }     
  25.     //tomcat開啓時加載一次,加載所有url和權限(或角色)的對應關係  
  26.     private void loadResourceDefine() {  
  27.         resourceMap = new HashMap<String, Collection<ConfigAttribute>>();   
  28.         Collection<ConfigAttribute> atts = new ArrayList<ConfigAttribute>();   
  29.         ConfigAttribute ca = new SecurityConfig("ROLE_USER");  
  30.         atts.add(ca);   
  31.         resourceMap.put("/index.jsp", atts);    
  32.         Collection<ConfigAttribute> attsno =new ArrayList<ConfigAttribute>();  
  33.         ConfigAttribute cano = new SecurityConfig("ROLE_NO");  
  34.         attsno.add(cano);  
  35.         resourceMap.put("/other.jsp", attsno);     
  36.         }    
  37.       
  38.     //參數是要訪問的url,返回這個url對於的所有權限(或角色)  
  39.     public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {   
  40.         // 將參數轉爲url      
  41.         String url = ((FilterInvocation)object).getRequestUrl();     
  42.         Iterator<String>ite = resourceMap.keySet().iterator();   
  43.         while (ite.hasNext()) {           
  44.             String resURL = ite.next();    
  45.             if (urlMatcher.pathMatchesUrl(resURL, url)) {   
  46.                 return resourceMap.get(resURL);           
  47.                 }         
  48.             }   
  49.         return null;      
  50.         }    
  51.     public boolean supports(Class<?>clazz) {   
  52.             return true;    
  53.             }   
  54.     public Collection<ConfigAttribute> getAllConfigAttributes() {   
  55.         return null;    
  56.         }  
  57.     }  

實現FilterInvocationSecurityMetadataSource接口也是必須的。
首先,這裏也是模擬了從數據庫中獲取信息。
其中loadResourceDefine方法不是必須的,這個只是加載所有的資源與權限的對應關係並緩存起來,避免每次獲取權限都訪問數據庫(提高性能),然後getAttributes根據參數(被攔截url)返回權限集合。
這種緩存的實現其實有一個缺點,因爲loadResourceDefine方法是放在構造器上調用的,而這個類的實例化只在web服務器啓動時調用一次,那就是說loadResourceDefine方法只會調用一次,如果資源和權限的對應關係在啓動後發生了改變,那麼緩存起來的就是髒數據,而筆者這裏使用的就是緩存數據,那就會授權錯誤了。但如果資源和權限對應關係是不會改變的,這種方法性能會好很多。
現在說回有數據庫的靈活實現,讀者看到這,可能會說,這還不簡單,和上面MyUserDetailService類一樣使用dao靈活獲取數據就行啦。
如果讀者這樣想,那只想到了一半,想一下spring的機制(依賴注入),dao需要依賴注入吧,但這是在啓動時候,那個dao可能都還沒加載,所以這裏需要讀者自己寫sessionFactory,自己寫hql或sql,對,就在loadResourceDefine方法裏面寫(這個應該會寫吧,基礎來的)。那如果說想用第二種方法呢(就是允許資源和權限的對應關係改變的那個),那更加簡單,根本不需要loadResourceDefine方法了,直接在getAttributes方法裏面調用dao(這個是加載完,後來纔會調用的,所以可以使用dao),通過被攔截url獲取數據庫中的所有權限,封裝成Collection<ConfigAttribute>返回就行了。(靈活、簡單)
注意:接口UrlMatcher和實現類AntUrlPathMatcher是筆者自己寫的,這本來是spring以前版本有的,現在沒有了,但是覺得好用就用會來了,直接上代碼(讀者也可以自己寫正則表達式驗證被攔截url和緩存或數據庫的url是否匹配):
[java] view plain copy
 print?
  1. package com.erdangjiade.spring.security.tool;  
  2.   
  3. public interface UrlMatcher{  
  4.     Object compile(String paramString);  
  5.     boolean pathMatchesUrl(Object paramObject, String paramString);  
  6.     String getUniversalMatchPattern();   
  7.     boolean requiresLowerCaseUrl();  
  8. }  

[java] view plain copy
 print?
  1. package com.erdangjiade.spring.security.tool;   
  2. import org.springframework.util.AntPathMatcher;  
  3. import org.springframework.util.PathMatcher;   
  4.   
  5.   public class AntUrlPathMatcher implements UrlMatcher {    
  6.       private boolean requiresLowerCaseUrl;  
  7.       private PathMatcher pathMatcher;   
  8.       public AntUrlPathMatcher()   {   
  9.           this(true);   
  10.         
  11.   }    
  12.       public AntUrlPathMatcher(boolean requiresLowerCaseUrl)   
  13.       {    
  14.           this.requiresLowerCaseUrl = true;  
  15.       this.pathMatcher = new AntPathMatcher();   
  16.       this.requiresLowerCaseUrl = requiresLowerCaseUrl;  
  17.       }   
  18.         
  19.       public Object compile(String path) {   
  20.           if (this.requiresLowerCaseUrl) {   
  21.               return path.toLowerCase();    
  22.               }     
  23.           return path;    
  24.       }    
  25.         
  26.       public void setRequiresLowerCaseUrl(boolean requiresLowerCaseUrl){  
  27.             
  28.           this.requiresLowerCaseUrl = requiresLowerCaseUrl;   
  29.       }   
  30.         
  31.       public boolean pathMatchesUrl(Object path, String url) {   
  32.           if (("/**".equals(path)) || ("**".equals(path))) {  
  33.               return true;       
  34.               }    
  35.             
  36.           return this.pathMatcher.match((String)path, url);   
  37.       }   
  38.         
  39.       public String getUniversalMatchPattern() {  
  40.           return"/**";    
  41.       }  
  42.         
  43.       public boolean requiresLowerCaseUrl() {   
  44.           return this.requiresLowerCaseUrl;    
  45.       }    
  46.         
  47.       public String toString() {    
  48.           return super.getClass().getName() + "[requiresLowerCase='"   
  49.       + this.requiresLowerCaseUrl + "']";    
  50.       }  
  51.   }  




然後MyAccessDecisionManager類的實現:
[java] view plain copy
 print?
  1. package com.erdangjiade.spring.security;  
  2.   
  3. import java.util.Collection;  
  4. import java.util.Iterator;  
  5.   
  6. import org.springframework.security.access.AccessDecisionManager;  
  7. import org.springframework.security.access.AccessDeniedException;  
  8. import org.springframework.security.access.ConfigAttribute;  
  9. import org.springframework.security.access.SecurityConfig;  
  10. import org.springframework.security.authentication.InsufficientAuthenticationException;  
  11. import org.springframework.security.core.Authentication;  
  12. import org.springframework.security.core.GrantedAuthority;  
  13.   
  14. public class MyAccessDecisionManager implements AccessDecisionManager {  
  15.       
  16.     //檢查用戶是否夠權限訪問資源  
  17.     //參數authentication是從spring的全局緩存SecurityContextHolder中拿到的,裏面是用戶的權限信息  
  18.     //參數object是url  
  19.     //參數configAttributes所需的權限  
  20.     public void decide(Authentication authentication, Object object,      
  21.             Collection<ConfigAttribute> configAttributes)   
  22.                     throws AccessDeniedException, InsufficientAuthenticationException {  
  23.         if(configAttributes == null){   
  24.             return;         
  25.         }    
  26.           
  27.         Iterator<ConfigAttribute> ite=configAttributes.iterator();  
  28.         while(ite.hasNext()){  
  29.             ConfigAttribute ca=ite.next();    
  30.             String needRole=((SecurityConfig)ca).getAttribute();  
  31.             for(GrantedAuthority ga : authentication.getAuthorities()){   
  32.                 if(needRole.equals(ga.getAuthority())){    
  33.                       
  34.                     return;                
  35.         }              
  36.     }        
  37. }   
  38.         //注意:執行這裏,後臺是會拋異常的,但是界面會跳轉到所配的access-denied-page頁面  
  39.         throw new AccessDeniedException("no right");     
  40. }     
  41.     public boolean supports(ConfigAttribute attribute) {   
  42.         return true;  
  43.     }    
  44.     public boolean supports(Class<?>clazz) {  
  45.         return true;   
  46.         }   
  47.     }  

接口AccessDecisionManager也是必須實現的。
decide方法裏面寫的就是授權策略了,筆者的實現是,沒有明說需要權限的(即沒有對應的權限的資源),可以訪問,用戶具有其中一個或多個以上的權限的可以訪問。這個就看需求了,需要什麼策略,讀者可以自己寫其中的策略邏輯。通過就返回,不通過拋異常就行了,spring security會自動跳到權限不足頁面(配置文件上配的)。

就這樣,整個流程過了一遍。


剩下的頁面代碼

本來想給這個demo的源碼出來的,但是筆者覺得,通過這個教程一步一步讀下來,並自己敲一遍代碼,會比直接運行一遍demo印象更深刻,並且更容易理解裏面的原理。
而且我的源碼其實都公佈出來了:
login.jsp:
[html] view plain copy
 print?
  1. <%@page language="java" import="java.util.*" pageEncoding="UTF-8"%>  
  2. <!DOCTYPEhtmlPUBLIC"-//W3C//DTD HTML 4.01 Transitional//EN">  
  3. <html>  
  4. <head>  
  5. <title>登錄</title>  
  6. </head>  
  7. <body>  
  8.     <form action ="j_spring_security_check" method="POST">  
  9.     <table>  
  10.         <tr>  
  11.             <td>用戶:</td>  
  12.             <td><input type ='text' name='j_username'></td>  
  13.         </tr>  
  14.         <tr>  
  15.             <td>密碼:</td>  
  16.             <td><input type ='password' name='j_password'></td>  
  17.         </tr>  
  18.         <tr>  
  19.             <td><input name ="reset" type="reset"></td>  
  20.             <td><input name ="submit" type="submit"></td>  
  21.         </tr>  
  22.     </table>  
  23.     </form>  
  24. </body>  
  25. </html>  

index.jsp:
[html] view plain copy
 print?
  1. <%@page language="java" import="java.util.*" pageEncoding="UTF-8"%>   
  2. <%@taglib prefix="sec" uri="http://www.springframework.org/security/tags"%>   
  3. <!DOCTYPEHTMLPUBLIC"-//W3C//DTD HTML 4.01 Transitional//EN">  
  4.    
  5. <html>  
  6.    
  7. <head>  
  8.    
  9. <title>My JSP 'index.jsp' starting page</title>   
  10. </head>  
  11.    
  12. <body>  
  13.       <h3>這是首頁</h3>歡迎  
  14.     <sec:authentication property ="name"/> !  
  15.       
  16.     <br>   
  17.     <a href="admin.jsp">進入admin頁面</a>   
  18.     <a href="other.jsp">進入其它頁面</a>   
  19. </body>  
  20.    
  21.   
  22. </html>  
  23.    
admin.jsp:
[html] view plain copy
 print?
  1. <%@page language="java" import="java.util.*" pageEncoding="utf-8"%>  
  2. <!DOCTYPEHTMLPUBLIC"-//W3C//DTD HTML 4.01 Transitional//EN">  
  3. <html>  
  4. <head>  
  5. <title>My JSP 'admin.jsp' starting page</title>  
  6. </head>  
  7. <body>  
  8.     歡迎來到管理員頁面.  
  9.     <br>  
  10. </body>  
  11. </html>  

accessDenied.jsp:
[html] view plain copy
 print?
  1. <%@page language="java" import="java.util.*" pageEncoding="utf-8"%>  
  2. <!DOCTYPEHTMLPUBLIC"-//W3C//DTD HTML 4.01 Transitional//EN">  
  3. <html>  
  4. <head>  
  5. <title>My JSP 'admin.jsp' starting page</title>  
  6. </head>  
  7. <body>  
  8.     歡迎來到管理員頁面.  
  9.     <br>  
  10. </body>  
  11. </html>  

other.jsp:
[html] view plain copy
 print?
  1. <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>  
  2. <%  
  3. String path = request.getContextPath();  
  4. String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";  
  5. %>  
  6.   
  7. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">  
  8. <html>  
  9.   <head>  
  10.     <base href="<%=basePath%>">  
  11.       
  12.     <title>My JSP 'other.jsp' starting page</title>  
  13.       
  14.     <meta http-equiv="pragma" content="no-cache">  
  15.     <meta http-equiv="cache-control" content="no-cache">  
  16.     <meta http-equiv="expires" content="0">      
  17.     <meta http-equiv="keywords" content="keyword1,keyword2,keyword3">  
  18.     <meta http-equiv="description" content="This is my page">  
  19.     <!-- 
  20.     <link rel="stylesheet" type="text/css" href="styles.css"> 
  21.     -->  
  22.   
  23.   </head>  
  24.     
  25.   <body>  
  26.     <h3>這裏是Other頁面</h3>  
  27.   </body>  
  28. </html>  


項目圖:



最後的話:

雖然筆者沒給讀者們demo,但是所有源碼和jar包都在這個教程裏面,爲什麼不直接給?筆者的目的是讓讀者跟着教程敲一遍代碼,使印象深刻(相信做這行的都知道,同樣一段代碼,看過和敲過的區別是多麼的大),所以不惜如此來強迫大家了。
由於筆者有經常上csdn博客的習慣,所以讀者有什麼不懂的(或者指教的),筆者盡力解答。


補充:

(2014年11月21日第一次補充):

第一點:

    MyUserDetailService這個類負責的是隻是獲取登陸用戶的詳細信息(包括密碼、角色等),不負責和前端傳過來的密碼對比,只需返回User對象,後會有其他類根據User對象對比密碼的正確性(框架幫我們做)。

第二點:

    記得MyInvocationSecurityMetadataSource這個類是負責的是獲取角色與url資源的所有對應關係,並根據url查詢對應的所有角色。
    今天爲一個項目搭安全架構時,第一,發現上面MyInvocationSecurityMetadataSource這個類的代碼有個bug
            上面的代碼中,將所有的對應關係緩存到resourceMap,key是url,value是這個url對應所有角色。 
            getAttributes方法中,只要匹配到一個url就返回這個url對應所有角色,不再匹配後面的url,問題來了,當url有交集時,就有可能漏掉一些角色了:如有兩個 url ,第一個是 /** ,第二個是 /role1/index.jsp ,第一個當然需要很高的權限了(因爲能匹配所有 url ,即可以訪問所有 url ),假設它需要的角色是 ROLE_ADMIN (不是一般人擁有的),第二個所需的角色是 ROLE_1 。    當我用 ROLE_1 這個角色訪問 /role1/index.jsp 時,在getAttributes方法中,當先迭代了 /** 這個url,它就能匹配 /role1/index.jsp 這個url,並直接返回 /** 這個url對應的所有角色(在這,也就ROLE_ADMIN)給MyAccessDecisionManager這個投票類,  MyAccessDecisionManager這個類中再對比 用戶的角色 ROLE_1 ,就會發現不匹配。    最後,明明可以有權訪問的 url ,卻不能訪問了。
  
    第二,之前不是說緩存所有對應關係,需要讀者自己寫sessionFactory(因爲在實例化這個類時,配置的sessionFactory可能還沒實例化或dao還沒加載好),既然這樣,那筆者可以不在構造方法中加載對應關係,可以在第一次調用getAttributes方法時再加載(用靜態變量緩存起來,第二次就不用再加載了,     注:其實這樣不是很嚴謹,不過筆者這裏的對應關係是不變的,單例性不需很強,更嚴謹的請參考筆者另一篇博文設計模式之單件模式)。

    修改過的MyInvocationSecurityMetadataSource類:
[java] view plain copy
 print?
  1. package com.lcy.bookcrossing.springSecurity;  
  2.   
  3. import java.util.ArrayList;  
  4. import java.util.Collection;  
  5. import java.util.HashMap;  
  6. import java.util.HashSet;  
  7. import java.util.Iterator;  
  8. import java.util.List;  
  9. import java.util.Map;  
  10. import java.util.Set;  
  11.   
  12. import javax.annotation.Resource;  
  13.   
  14. import org.springframework.security.access.ConfigAttribute;  
  15. import org.springframework.security.access.SecurityConfig;  
  16. import org.springframework.security.web.FilterInvocation;  
  17. import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;  
  18.   
  19. import com.lcy.bookcrossing.bean.RoleUrlResource;  
  20. import com.lcy.bookcrossing.dao.IRoleUrlResourceDao;  
  21. import com.lcy.bookcrossing.springSecurity.tool.AntUrlPathMatcher;  
  22. import com.lcy.bookcrossing.springSecurity.tool.UrlMatcher;  
  23.   
  24. public class MyInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {   
  25.     private UrlMatcher urlMatcher = new AntUrlPathMatcher();   
  26. //  private static Map<String, Collection<ConfigAttribute>> resourceMap = null;  
  27.       
  28.     //將所有的角色和url的對應關係緩存起來  
  29.     private static List<RoleUrlResource> rus = null;  
  30.       
  31.     @Resource  
  32.     private IRoleUrlResourceDao roleUrlDao;  
  33.       
  34.     //tomcat啓動時實例化一次  
  35.     public MyInvocationSecurityMetadataSource() {  
  36. //      loadResourceDefine();    
  37.         }     
  38.     //tomcat開啓時加載一次,加載所有url和權限(或角色)的對應關係  
  39.     /*private void loadResourceDefine() { 
  40.         resourceMap = new HashMap<String, Collection<ConfigAttribute>>();  
  41.         Collection<ConfigAttribute> atts = new ArrayList<ConfigAttribute>();  
  42.         ConfigAttribute ca = new SecurityConfig("ROLE_USER"); 
  43.         atts.add(ca);  
  44.         resourceMap.put("/index.jsp", atts);   
  45.         Collection<ConfigAttribute> attsno =new ArrayList<ConfigAttribute>(); 
  46.         ConfigAttribute cano = new SecurityConfig("ROLE_NO"); 
  47.         attsno.add(cano); 
  48.         resourceMap.put("/other.jsp", attsno);    
  49.         }  */  
  50.       
  51.     //參數是要訪問的url,返回這個url對於的所有權限(或角色)  
  52.     public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {   
  53.         // 將參數轉爲url      
  54.         String url = ((FilterInvocation)object).getRequestUrl();     
  55.           
  56.         //查詢所有的url和角色的對應關係  
  57.         if(rus == null){  
  58.         rus = roleUrlDao.findAll();  
  59.         }  
  60.           
  61.         //匹配所有的url,並對角色去重  
  62.         Set<String> roles = new HashSet<String>();  
  63.         for(RoleUrlResource ru : rus){  
  64.             if (urlMatcher.pathMatchesUrl(ru.getUrlResource().getUrl(), url)) {   
  65.                         roles.add(ru.getRole().getRoleName());  
  66.                 }       
  67.         }  
  68.         Collection<ConfigAttribute> cas = new ArrayList<ConfigAttribute>();   
  69.         for(String role : roles){  
  70.             ConfigAttribute ca = new SecurityConfig(role);  
  71.             cas.add(ca);   
  72.         }  
  73.         return cas;  
  74.           
  75.         /*Iterator<String> ite = resourceMap.keySet().iterator();  
  76.         while (ite.hasNext()) {          
  77.             String resURL = ite.next();   
  78.             if (urlMatcher.pathMatchesUrl(resURL, url)) {  
  79.                 return resourceMap.get(resURL);          
  80.                 }        
  81.             }  
  82.         return null;    */  
  83.         }    
  84.     public boolean supports(Class<?>clazz) {   
  85.             return true;    
  86.             }   
  87.     public Collection<ConfigAttribute> getAllConfigAttributes() {   
  88.         return null;    
  89.         }  
  90.     }  

    以上代碼,在getAttributes方法中緩存起所有的對應關係(可以使用依賴注入了),並匹配所有 url ,對角色進行去重(因爲多個url可能有重複的角色),這樣就能修復那個bug了。



(2014年12月10日第二次補充):

        這次補充不是修上面的bug,而是添加新功能
        我們知道,上面的實現的登陸界面只能傳遞兩個參數(j_username,j_password),而且是固定的。
        總是有一個項目需求,我們的角色(ROLE_)不是很多,只需在登陸界面選擇一種角色就行了,那麼如何將角色類型傳遞到spring security呢,現在筆者對配置文件再修改修改:
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <b:beans xmlns="http://www.springframework.org/schema/security"  
  3.     xmlns:b="http://www.springframework.org/schema/beans"  
  4.     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  5.     xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd  
  6.                         http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd">  
  7.   
  8.   
  9.  <!-- 配置不需要安全管理的界面 -->  
  10.      <http pattern="/jsp/css/**" security="none"></http>  
  11.      <http pattern="/jsp/js/**" security="none"></http>  
  12.      <http pattern="/jsp/images/**" security="none"></http>  
  13.      <http pattern="/login.jsp" security="none" />  
  14.      <http pattern="/accessDenied.jsp" security="none" />  
  15.          <http pattern="/index.jsp" security="none" />  
  16.   
  17.   
  18.         <http use-expressions='true' entry-point-ref="myAuthenticationEntryPoint" access-denied-page="/accessDenied.jsp">  
  19.                   
  20.                 <!-- 使用自己自定義的登陸認證過濾器 --><!-- 這裏一定要註釋掉,因爲我們需要重寫它的過濾器 -->  
  21.                 <!-- <form-login login-page="/login.jsp"  
  22.                 authentication-failure-url="/accessDenied.jsp"      
  23.         default-target-url="/index.jsp"  
  24.                  /> -->  
  25.                 <!--訪問/admin.jsp資源的用戶必須具有ROLE_ADMIN的權限 -->  
  26.                 <!-- <intercept-url pattern="/admin.jsp" access="ROLE_ADMIN" /> -->  
  27.                 <!--訪問/**資源的用戶必須具有ROLE_USER的權限 -->  
  28.                 <!-- <intercept-url pattern="/**" access="ROLE_USER" /> -->  
  29.                 <session-management>  
  30.                         <concurrency-control max-sessions="1"  
  31.                                 error-if-maximum-exceeded="false" />  
  32.                 </session-management>  
  33.                   
  34.                 <!-- 認證和授權 --><!-- 重寫登陸認證的過濾器,使我們可以拿到任何參數  -->  
  35.                 <custom-filter ref="myAuthenticationFilter" position="FORM_LOGIN_FILTER"  />  
  36.                 <custom-filter ref="myFilter" before="FILTER_SECURITY_INTERCEPTOR" />  
  37.                   
  38.                  <!-- 登出管理 -->  
  39.         <logout invalidate-session="true" logout-url="/j_spring_security_logout" />  
  40.           
  41.         </http>  
  42.           
  43.         <!-- 未登錄的切入點 --><!-- 需要有個切入點 -->  
  44.     <b:bean id="myAuthenticationEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">  
  45.         <b:property name="loginFormUrl" value="/login.jsp"></b:property>  
  46.     </b:bean>  
  47.           
  48.         <!-- 登錄驗證器:用戶有沒有登錄的資格 --><!-- 這個就是重寫的認證過濾器 -->  
  49.     <b:bean id="myAuthenticationFilter" class="com.lcy.springSecurity.MyAuthenticationFilter">  
  50.         <b:property name="authenticationManager" ref="authenticationManager" />  
  51.         <b:property name="filterProcessesUrl" value="/j_spring_security_check" />  
  52.         <b:property name="authenticationSuccessHandler">  
  53.             <b:bean class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">  
  54.                 <b:property name="defaultTargetUrl" value="/index.jsp" />  
  55.             </b:bean>  
  56.         </b:property>  
  57.         <b:property name="authenticationFailureHandler">  
  58.             <b:bean class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">  
  59.                 <b:property name="defaultFailureUrl" value="/accessDenied.jsp" />  
  60.             </b:bean>  
  61.         </b:property>  
  62.     </b:bean>  
  63.           
  64.           
  65.         <!--一個自定義的filter,必須包含 authenticationManager,accessDecisionManager,securityMetadataSource三個屬性,我們的所有控制將在這三個類中實現,解釋詳見具體配置 -->  
  66.         <b:bean id="myFilter"  
  67.                 class="com.lcy.springSecurity.MyFilterSecurityInterceptor">  
  68.                 <b:property name="authenticationManager" ref="authenticationManager" />  
  69.                 <b:property name="accessDecisionManager" ref="myAccessDecisionManagerBean" />  
  70.                 <b:property name="securityMetadataSource" ref="securityMetadataSource" />  
  71.         </b:bean>  
  72.         <!--驗證配置,認證管理器,實現用戶認證的入口,主要實現UserDetailsService接口即可 -->  
  73.         <authentication-manager alias="authenticationManager">  
  74.                 <authentication-provider user-service-ref="myUserDetailService">  
  75.                         <!--如果用戶的密碼採用加密的話 <password-encoder hash="md5" /> -->  
  76.                         <!-- <password-encoder hash="md5" /> -->  
  77.                 </authentication-provider>  
  78.         </authentication-manager>  
  79.         <!--在這個類中,你就可以從數據庫中讀入用戶的密碼,角色信息,是否鎖定,賬號是否過期等 -->  
  80.         <b:bean id="myUserDetailService" class="com.lcy.springSecurity.MyUserDetailService" />  
  81.         <!--訪問決策器,決定某個用戶具有的角色,是否有足夠的權限去訪問某個資源 -->  
  82.         <b:bean id="myAccessDecisionManagerBean"  
  83.                 class="com.lcy.springSecurity.MyAccessDecisionManager">  
  84.         </b:bean>  
  85.         <!--資源源數據定義,將所有的資源和權限對應關係建立起來,即定義某一資源可以被哪些角色訪問 -->  
  86.         <b:bean id="securityMetadataSource"  
  87.                 class="com.lcy.springSecurity.MyInvocationSecurityMetadataSource" />   
  88.             
  89.  </b:beans>  

    我現在的項目需要的是,角色只要管理員、教師、學生,所以MyAuthenticationFilter(重寫的認證過濾器):
  1. package com.lcy.springSecurity;  
  2.   
  3. import javax.annotation.Resource;  
  4. import javax.servlet.http.HttpServletRequest;  
  5. import javax.servlet.http.HttpServletResponse;  
  6.   
  7. import org.springframework.security.authentication.AuthenticationServiceException;  
  8. import org.springframework.security.authentication.BadCredentialsException;  
  9. import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;  
  10. import org.springframework.security.core.Authentication;  
  11. import org.springframework.security.core.AuthenticationException;  
  12. import org.springframework.security.core.context.SecurityContextHolder;  
  13. import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;  
  14.   
  15. import com.lcy.dao.IAdminDao;  
  16. import com.lcy.dao.IStudentDao;  
  17. import com.lcy.dao.ITeacherDao;  
  18. import com.lcy.entity.Admin;  
  19. import com.lcy.entity.Student;  
  20. import com.lcy.entity.Teacher;  
  21.   
  22. public class MyAuthenticationFilter extends UsernamePasswordAuthenticationFilter {  
  23.           
  24.         private static final String USERNAME = "username";  
  25.         private static final String PASSWORD = "password";  
  26.   
  27.         @Resource  
  28.         private IStudentDao studentdao;  
  29.         @Resource  
  30.         private ITeacherDao teacherdao;  
  31.         @Resource  
  32.         private IAdminDao admindao;  
  33.           
  34.         @Override  
  35.         public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {  
  36.                 if (!request.getMethod().equals("POST")) {  
  37.                         throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());  
  38.                 }  
  39.   
  40.                 String username = obtainUsername(request);  
  41.                 String password = obtainPassword(request);  
  42.                 String roletype = request.getParameter("roletype");  
  43.                   
  44.                 username = username.trim();  
  45.                   
  46.                 UsernamePasswordAuthenticationToken authRequest = null;  
  47.                   
  48.                 if(!"".equals(roletype) || roletype != null){  
  49.                         if("student".equals(roletype)){  
  50.                                 Student stu = studentdao.findById(username);  
  51.                                   
  52.                                 //通過session把用戶對象設置到session中  
  53.                                 request.getSession().setAttribute("session_user", stu);  
  54.                                   
  55.                                 //將角色標誌在username上  
  56.                                 username = "stu"+username;  
  57.                                   
  58.                                 try {  
  59.                                         if (stu == null || !stu.getPassword().equals(password)) {  
  60.                                                 BadCredentialsException exception = new BadCredentialsException("用戶名或密碼不匹配");  
  61.                                                 throw exception;  
  62.                                         }  
  63.                                 } catch (Exception e) {  
  64.                                         BadCredentialsException exception = new BadCredentialsException("沒有此用戶");  
  65.                                         throw exception;  
  66.                                 }  
  67.                                   
  68.                                   
  69.                         }else if("teacher".equals(roletype)){  
  70.                                 Teacher tea = teacherdao.findById(username);  
  71.                                   
  72.                                 //通過session把用戶對象設置到session中  
  73.                                 request.getSession().setAttribute("session_user", tea);  
  74.                                   
  75.                                 //將角色標誌在username上  
  76.                                 username = "tea"+username;  
  77.                                   
  78.                                 try {  
  79.                                         if (tea == null || !tea.getPassword().equals(password)) {  
  80.                                                 BadCredentialsException exception = new BadCredentialsException("用戶名或密碼不匹配");  
  81.                                                 throw exception;  
  82.                                         }  
  83.                                 } catch (Exception e) {  
  84.                                         BadCredentialsException exception = new BadCredentialsException("沒有此用戶");  
  85.                                         throw exception;  
  86.                                 }  
  87.                                   
  88.                         }else if("admin".equals(roletype)){  
  89.                                 Admin adm = admindao.findById(username);  
  90.                                   
  91.                                 //通過session把用戶對象設置到session中  
  92.                                 request.getSession().setAttribute("session_user", adm);  
  93.                                   
  94.                                 //將角色標誌在username上  
  95.                                 username = "adm"+username;  
  96.                                 try {  
  97.                                         if (adm == null || !password.equals(adm.getPassword())) {  
  98.                                                 BadCredentialsException exception = new BadCredentialsException("用戶名或密碼不匹配");  
  99.                                                 throw exception;  
  100.                                         }  
  101.                                 } catch (Exception e) {  
  102.                                         BadCredentialsException exception = new BadCredentialsException("沒有此用戶");  
  103.                                         throw exception;  
  104.                                 }  
  105.                                   
  106.                         }else{  
  107.                                 BadCredentialsException exception = new BadCredentialsException("系統錯誤:沒有對應的角色!");  
  108.                                 throw exception;  
  109.                         }  
  110.                 }  
  111.   
  112.                   
  113.                 //實現驗證  
  114.                 authRequest = new UsernamePasswordAuthenticationToken(username, password);  
  115.                 //允許設置用戶詳細屬性  
  116.                 setDetails(request, authRequest);  
  117.                 //運行  
  118.                 return this.getAuthenticationManager().authenticate(authRequest);  
  119.         }  
  120.   
  121.         @Override  
  122.         protected String obtainUsername(HttpServletRequest request) {  
  123.                 Object obj = request.getParameter(USERNAME);  
  124.                 return null == obj ? "" : obj.toString();  
  125.         }  
  126.   
  127.         @Override  
  128.         protected String obtainPassword(HttpServletRequest request) {  
  129.                 Object obj = request.getParameter(PASSWORD);  
  130.                 return null == obj ? "" : obj.toString();  
  131.         }  
  132.           
  133.         @Override  
  134.         protected void setDetails(HttpServletRequest request, UsernamePasswordAuthenticationToken authRequest) {  
  135.                 super.setDetails(request, authRequest);  
  136.         }  
  137. }  

    筆者自己斷點可知,執行完上面那個認證過濾器,纔會執行MyUserDetailService。
        注:因爲 MyUserDetailService類中的loadUserByUsername(String username) 方法只能接收一個參數username,而且這個username是從認證過濾器那裏傳過來的,所以筆者就通過username順帶傳遞角色類型過來,如上面認證過濾器,將角色類型拼在username中。到MyUserDetailService類在解開。(如有更好的方法,請評論告知,謝謝)

    MyUserDetailService:
  1. package com.lcy.springSecurity;  
  2.   
  3. import java.util.ArrayList;  
  4. import java.util.Collection;  
  5.   
  6. import javax.annotation.Resource;  
  7.   
  8. import org.springframework.dao.DataAccessException;  
  9. import org.springframework.security.core.GrantedAuthority;  
  10. import org.springframework.security.core.authority.SimpleGrantedAuthority;  
  11. import org.springframework.security.core.userdetails.User;  
  12. import org.springframework.security.core.userdetails.UserDetails;  
  13. import org.springframework.security.core.userdetails.UserDetailsService;  
  14. import org.springframework.security.core.userdetails.UsernameNotFoundException;  
  15.   
  16. import com.lcy.dao.IAdminDao;  
  17. import com.lcy.dao.IStudentDao;  
  18. import com.lcy.dao.ITeacherDao;  
  19. import com.lcy.entity.Admin;  
  20. import com.lcy.entity.Student;  
  21. import com.lcy.entity.Teacher;  
  22.   
  23. public class MyUserDetailService implements UserDetailsService {   
  24.         @Resource  
  25.         private IStudentDao studentdao;  
  26.         @Resource  
  27.         private ITeacherDao teacherdao;  
  28.         @Resource  
  29.         private IAdminDao admindao;  
  30.           
  31.         //登陸驗證時,通過username獲取用戶的所有權限信息,  
  32.         //並返回User放到spring的全局緩存SecurityContextHolder中,以供授權器使用  
  33.         public UserDetails loadUserByUsername(String username)   
  34.                         throws UsernameNotFoundException, DataAccessException {     
  35.                 Collection<GrantedAuthority> auths= new ArrayList<GrantedAuthority>();  
  36.                 //獲取角色標誌  
  37.                 String roletype = username.substring(0,3);  
  38.                 username = username.substring(3);  
  39.                 String password = "";  
  40.                   
  41.                 if("stu".equals(roletype)){  
  42.                         Student stu = studentdao.findById(username);  
  43.                         password = stu.getPassword();  
  44.                         auths.add(new SimpleGrantedAuthority("ROLE_STU"));  
  45.                 }else if("tea".equals(roletype)){  
  46.                         Teacher tea = teacherdao.findById(username);  
  47.                         password = tea.getPassword();  
  48.                         auths.add(new SimpleGrantedAuthority("ROLE_TEA"));  
  49.                 }else if("adm".equals(roletype)){  
  50.                         Admin adm = admindao.findById(username);  
  51.                         password = adm.getPassword();  
  52.                         auths.add(new SimpleGrantedAuthority("ROLE_ADM"));  
  53.                 }  
  54.                   
  55.                 User user = new User(username, password, truetruetruetrue, auths);   
  56.                 return user;    
  57.                 }   
  58.         }   

原文地址:http://blog.csdn.net/u012367513/article/details/38866465

讓我們一起遨遊在代碼的海洋裏!

發佈了107 篇原創文章 · 獲贊 42 · 訪問量 21萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章