本文項目參照http://www.blogjava.net/SpartaYew/archive/2011/05/19/SpingSecurity3.html的第三種方法實現,並簡寫博客中一部分內容。
本文項目采用spring security3.1 + Mybatis3.2 + oracle10
Spring Security3提供了靈活的擴展方法。具體應該擴展哪些類呢? 或者到底Spring Security3工作的流程如何,你不妨參看下面一篇文章,就會獲得
一些啟示,網址為:http://www.blogjava.net/youxia/archive/2008/12/07/244883.html , 哈哈,謝謝分享。
還有一個地址很有價值, http://wenku.baidu.com/view/4ec7e324ccbff121dd368364.html ,我就參考着上面的介紹擴展了4個類。
不過我得提一下,原文的作者為了考驗你的耐性和自信心,故意在代碼里面賣了幾點小小的關子,因此若是完全按照作者的原文代碼裝配起來的權限系統,是不會那么順利地工作的,天下似乎真是沒有不花費力氣的午餐!在裝配完成后,我也是經過九九八十一難的折磨,在用戶、角色、權限、資源的
“天下黃河九曲十八彎”里面盤旋迂回,終於到達了成功的彼岸。至此才對Spring Security有了更深層次的理解,更加佩服作者的良苦用心。 哈哈。
並擴展了User類以增加其相關的各類其他信息(如Email,職務,所在單位id等)。
相關的代碼如下(包含5個關鍵類)(修改過作者原先代碼):
package com.springmvc.security; import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import org.springframework.security.access.SecurityMetadataSource; import org.springframework.security.access.intercept.AbstractSecurityInterceptor; import org.springframework.security.access.intercept.InterceptorStatusToken; import org.springframework.security.web.FilterInvocation; import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource; public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter { private FilterInvocationSecurityMetadataSource securityMetadataSource; public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { FilterInvocation fi = new FilterInvocation(request, response, chain); invoke(fi); } public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() { return this.securityMetadataSource; } public Class<? extends Object> getSecureObjectClass() { return FilterInvocation.class; } public void invoke(FilterInvocation fi) throws IOException, ServletException { InterceptorStatusToken token = super.beforeInvocation(fi); try { fi.getChain().doFilter(fi.getRequest(), fi.getResponse()); } finally { super.afterInvocation(token, null); } } @Override public SecurityMetadataSource obtainSecurityMetadataSource() { return this.securityMetadataSource; } public void setSecurityMetadataSource( FilterInvocationSecurityMetadataSource securityMetadataSource) { this.securityMetadataSource = securityMetadataSource; } public void destroy() { } public void init(FilterConfig filterconfig) throws ServletException { } }
package com.springmvc.security; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import javax.annotation.Resource; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.SecurityConfig; import org.springframework.security.web.FilterInvocation; import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource; import org.springframework.stereotype.Service; import com.springmvc.dao.TestDao; public class MyInvocationSecurityMetadataSourceService implements FilterInvocationSecurityMetadataSource { // @Resource(name = "testDao") private TestDao testDao; private static Map<String, Collection<ConfigAttribute>> resourceMap = null; public MyInvocationSecurityMetadataSourceService(TestDao testDao) { //使用注解方式的話,只能在構造函數執行完成后才能獲得實例 this.testDao = testDao; loadResourceDefine(); } // 在Web服務器啟動時,提取系統中的所有權限 private void loadResourceDefine() { List<String> query = this.testDao.getAllAuthorityName(); /* * 應當是資源為key, 權限為value。 資源通常為url, 權限就是那些以ROLE_為前綴的角色。 一個資源可以由多個權限來訪問。 * sparta */ resourceMap = new HashMap<String, Collection<ConfigAttribute>>(); for (String auth : query) { ConfigAttribute ca = new SecurityConfig(auth); List<String> query1 = this.testDao.getResource(auth); for (String res : query1) { String url = res; /* * 判斷資源文件和權限的對應關系,如果已經存在相關的資源url,則要通過該url為key提取出權限集合,將權限增加到權限集合中。 * sparta */ if (resourceMap.containsKey(url)) { Collection<ConfigAttribute> value = resourceMap.get(url); value.add(ca); resourceMap.put(url, value); } else { Collection<ConfigAttribute> atts = new ArrayList<ConfigAttribute>(); atts.add(ca); resourceMap.put(url, atts); } } } } @Override public Collection<ConfigAttribute> getAllConfigAttributes() { return null; } // 根據URL,找到相關的權限配置。 @Override public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException { // object 是一個URL,被用戶請求的url。 String url = ((FilterInvocation) object).getRequestUrl(); System.out.println("url" + url); int firstQuestionMarkIndex = url.indexOf("?"); if (firstQuestionMarkIndex != -1) { url = url.substring(0, firstQuestionMarkIndex); } Iterator<String> ite = resourceMap.keySet().iterator(); while (ite.hasNext()) { String resURL = ite.next(); if (url.equals(resURL)) { return resourceMap.get(resURL); } } return null; } @Override public boolean supports(Class<?> arg0) { return true; } }
package com.springmvc.security; import java.util.ArrayList; import java.util.Collection; import java.util.List; import javax.annotation.Resource; import org.springframework.dao.DataAccessException; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import com.springmvc.dao.TestDao; public class MyUserDetailsService implements UserDetailsService { @Resource(name="testDao") private TestDao testDao; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException { System.out.println("username" + username); List<GrantedAuthority> auths = new ArrayList<GrantedAuthority>(); List<String> authorityName = this.testDao.getAuthorityName(username); for(String roleName : authorityName) { System.out.println(roleName); SimpleGrantedAuthority authority = new SimpleGrantedAuthority(roleName); auths.add(authority); } String pwd = this.testDao.getPWD(username); return new User(username,pwd,true,true,true,true,auths); } }
package com.springmvc.security; import java.util.Collection; import java.util.Iterator; import org.springframework.security.access.AccessDecisionManager; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.SecurityConfig; import org.springframework.security.authentication.InsufficientAuthenticationException; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; /** * AccessdecisionManager在Spring security中是很重要的。 * * 在驗證部分簡略提過了,所有的Authentication實現需要保存在一個GrantedAuthority對象數組中。 這就是賦予給主體的權限。 * GrantedAuthority對象通過AuthenticationManager 保存到 * Authentication對象里,然后從AccessDecisionManager讀出來,進行授權判斷。 * * Spring Security提供了一些攔截器,來控制對安全對象的訪問權限,例如方法調用或web請求。 * 一個是否允許執行調用的預調用決定,是由AccessDecisionManager實現的。 這個 AccessDecisionManager * 被AbstractSecurityInterceptor調用, 它用來作最終訪問控制的決定。 * 這個AccessDecisionManager接口包含三個方法: * * void decide(Authentication authentication, Object secureObject, * List<ConfigAttributeDefinition> config) throws AccessDeniedException; boolean * supports(ConfigAttribute attribute); boolean supports(Class clazz); * * 從第一個方法可以看出來,AccessDecisionManager使用方法參數傳遞所有信息,這好像在認證評估時進行決定。 * 特別是,在真實的安全方法期望調用的時候,傳遞安全Object啟用那些參數。 比如,讓我們假設安全對象是一個MethodInvocation。 * 很容易為任何Customer參數查詢MethodInvocation, * 然后在AccessDecisionManager里實現一些有序的安全邏輯,來確認主體是否允許在那個客戶上操作。 * 如果訪問被拒絕,實現將拋出一個AccessDeniedException異常。 * * 這個 supports(ConfigAttribute) 方法在啟動的時候被 * AbstractSecurityInterceptor調用,來決定AccessDecisionManager * 是否可以執行傳遞ConfigAttribute。 supports(Class)方法被安全攔截器實現調用, * 包含安全攔截器將顯示的AccessDecisionManager支持安全對象的類型。 */ public class MyAccessDecisionManager implements AccessDecisionManager { public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException { if (configAttributes == null) { return; } Iterator<ConfigAttribute> ite = configAttributes.iterator(); while (ite.hasNext()) { ConfigAttribute ca = ite.next(); String needRole = ((SecurityConfig) ca).getAttribute(); System.out.println("authentication size" + authentication.getAuthorities().size()); // ga 為用戶所被賦予的權限。 needRole 為訪問相應的資源應該具有的權限。 for (GrantedAuthority ga : authentication.getAuthorities()) { if (needRole.trim().equals(ga.getAuthority().trim())) { return; } } } throw new AccessDeniedException(""); } public boolean supports(ConfigAttribute attribute) { return true; } public boolean supports(Class<?> clazz) { return true; } }
數據庫的SQL及預置數據(簡化了數據):
create table SYS_AUTHORITIES ( AUTHORITY_ID VARCHAR2(32) not null, AUTHORITY_NAME VARCHAR2(40), AUTHORITY_DESC VARCHAR2(100), ENABLED NUMBER(1), ISSYS NUMBER(1), MODULE VARCHAR2(4) ) create table SYS_AUTHORITIES_RESOURCES ( ID NUMBER(13) not null, AUTHORITY_ID VARCHAR2(32), RESOURCE_ID VARCHAR2(32), ENABLED NUMBER(1) ) create table SYS_RESOURCES ( RESOURCE_ID VARCHAR2(32) not null, RESOURCE_NAME VARCHAR2(100), RESOURCE_DESC VARCHAR2(100), RESOURCE_TYPE VARCHAR2(40), RESOURCE_STRING VARCHAR2(200), PRIORITY NUMBER(1), ENABLED NUMBER(1), ISSYS NUMBER(1), MODULE VARCHAR2(4) ) create table SYS_ROLES ( ROLE_ID VARCHAR2(32) not null, ROLE_NAME VARCHAR2(40), ROLE_DESC VARCHAR2(100), ENABLED NUMBER(1), ISSYS NUMBER(1), MODULE VARCHAR2(4) ) create table SYS_ROLES_AUTHORITIES ( ID NUMBER(13) not null, ROLE_ID VARCHAR2(32), AUTHORITY_ID VARCHAR2(32), ENABLED NUMBER(1) ) create table SYS_USERS ( USER_ID VARCHAR2(32) not null, USER_ACCOUNT VARCHAR2(30), USER_NAME VARCHAR2(40), USER_PASSWORD VARCHAR2(100), USER_DESC VARCHAR2(100), ENABLED NUMBER(1), ISSYS NUMBER(1), USER_DEPT VARCHAR2(20), USER_DUTY VARCHAR2(10), SUB_SYSTEM VARCHAR2(30) ) create table SYS_USERS_ROLES ( ID NUMBER(13) not null, USER_ID VARCHAR2(32), ROLE_ID VARCHAR2(32), ENABLED NUMBER(1) ) insert into SYS_AUTHORITIES (AUTHORITY_ID, AUTHORITY_NAME, AUTHORITY_DESC, ENABLED, ISSYS, MODULE) values ('auth_admin', 'auth_admin', null, null, null, null); insert into SYS_AUTHORITIES (AUTHORITY_ID, AUTHORITY_NAME, AUTHORITY_DESC, ENABLED, ISSYS, MODULE) values ('auth_lxb', 'auth_lxb', null, null, null, null); insert into SYS_AUTHORITIES (AUTHORITY_ID, AUTHORITY_NAME, AUTHORITY_DESC, ENABLED, ISSYS, MODULE) values ('auth_user', 'auth_user', null, null, null, null); insert into SYS_AUTHORITIES_RESOURCES (ID, AUTHORITY_ID, RESOURCE_ID, ENABLED) values (1, 'auth_admin', 'admin', 1); insert into SYS_AUTHORITIES_RESOURCES (ID, AUTHORITY_ID, RESOURCE_ID, ENABLED) values (2, 'auth_lxb', 'lxb', null); insert into SYS_AUTHORITIES_RESOURCES (ID, AUTHORITY_ID, RESOURCE_ID, ENABLED) values (3, 'auth_user', 'user', null); insert into SYS_RESOURCES (RESOURCE_ID, RESOURCE_NAME, RESOURCE_DESC, RESOURCE_TYPE, RESOURCE_STRING, PRIORITY, ENABLED, ISSYS, MODULE) values ('admin', 'admin', null, null, '/admin/admin.jsp', null, null, null, null); insert into SYS_RESOURCES (RESOURCE_ID, RESOURCE_NAME, RESOURCE_DESC, RESOURCE_TYPE, RESOURCE_STRING, PRIORITY, ENABLED, ISSYS, MODULE) values ('lxb', 'lxb', null, null, '/lxb.lxb.jsp', null, null, null, null); insert into SYS_RESOURCES (RESOURCE_ID, RESOURCE_NAME, RESOURCE_DESC, RESOURCE_TYPE, RESOURCE_STRING, PRIORITY, ENABLED, ISSYS, MODULE) values ('user', 'user', null, null, '/user/user.jsp', null, null, null, null); insert into SYS_ROLES (ROLE_ID, ROLE_NAME, ROLE_DESC, ENABLED, ISSYS, MODULE) values ('role_admin', 'role_admin', null, null, null, null); insert into SYS_ROLES (ROLE_ID, ROLE_NAME, ROLE_DESC, ENABLED, ISSYS, MODULE) values ('role_lxb', 'role_lxb', null, null, null, null); insert into SYS_ROLES (ROLE_ID, ROLE_NAME, ROLE_DESC, ENABLED, ISSYS, MODULE) values ('role_user', 'role_user', null, null, null, null); insert into SYS_ROLES_AUTHORITIES (ID, ROLE_ID, AUTHORITY_ID, ENABLED) values (1, 'role_admin', 'auth_admin', 1); insert into SYS_ROLES_AUTHORITIES (ID, ROLE_ID, AUTHORITY_ID, ENABLED) values (2, 'role_lxb', 'auth_lxb', 1); insert into SYS_ROLES_AUTHORITIES (ID, ROLE_ID, AUTHORITY_ID, ENABLED) values (3, 'role_user', 'auth_user', 1); insert into SYS_USERS (USER_ID, USER_ACCOUNT, USER_NAME, USER_PASSWORD, USER_DESC, ENABLED, ISSYS, USER_DEPT, USER_DUTY, SUB_SYSTEM) values ('admin', 'admin', 'admin', 'admin', null, null, null, null, null, null); insert into SYS_USERS (USER_ID, USER_ACCOUNT, USER_NAME, USER_PASSWORD, USER_DESC, ENABLED, ISSYS, USER_DEPT, USER_DUTY, SUB_SYSTEM) values ('lxb', 'lxb', 'lxb', 'lxb', null, null, null, null, null, null); insert into SYS_USERS (USER_ID, USER_ACCOUNT, USER_NAME, USER_PASSWORD, USER_DESC, ENABLED, ISSYS, USER_DEPT, USER_DUTY, SUB_SYSTEM) values ('user', 'user', 'user', 'user', null, null, null, null, null, null); insert into SYS_USERS_ROLES (ID, USER_ID, ROLE_ID, ENABLED) values (1, 'admin', 'role_admin', 1); insert into SYS_USERS_ROLES (ID, USER_ID, ROLE_ID, ENABLED) values (2, 'lxb', 'role_lxb', 1); insert into SYS_USERS_ROLES (ID, USER_ID, ROLE_ID, ENABLED) values (3, 'user', 'role_user', 1);
web.xml配置
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0"> <display-name>springsecurity</display-name> <welcome-file-list> <welcome-file>index.html</welcome-file> <welcome-file>index.htm</welcome-file> <welcome-file>index.jsp</welcome-file> <welcome-file>default.html</welcome-file> <welcome-file>default.htm</welcome-file> <welcome-file>default.jsp</welcome-file> </welcome-file-list> <context-param> <param-name>contextConfigLocation</param-name> <param-value> classpath:applicationContext.xml classpath:applicationContext-security.xml </param-value> </context-param> <!-- 定義spring security代理Filter --> <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> <!-- - Loads the root application context of this web app at startup. --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> </web-app>
applicationContext.xml配置
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util" xmlns:jee="http://www.springframework.org/schema/jee" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd" default-lazy-init="true" default-autowire="byName"> <context:component-scan base-package="com.springmvc"/> <context:annotation-config/> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/> <property name="url" value="jdbc:oracle:thin:@127.0.0.1:1521:ORCL"/> <property name="username" value="scott"/> <property name="password" value="scott"/> </bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="configLocation" value="classpath:myBatis.xml"/> <property name="mapperLocations"> <list> <value>classpath:mapper/*.xml</value> </list> </property> <property name="transactionFactory"> <bean class="org.mybatis.spring.transaction.SpringManagedTransactionFactory"/> </property> </bean> <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate"> <constructor-arg index="0" ref="sqlSessionFactory"/> </bean> <bean id="sqlSessionForBatch" class="org.mybatis.spring.SqlSessionTemplate"> <constructor-arg index="0" ref="sqlSessionFactory"/> <constructor-arg index="1" value="BATCH"/> </bean> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <bean id="transactionProxy" abstract="true" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> <property name="transactionManager" ref="transactionManager" /> <property name="transactionAttributes"> <props> <prop key="insert*"> PROPAGATION_REQUIRED,-SQLException </prop> <prop key="update*"> PROPAGATION_REQUIRED,-SQLException </prop> <prop key="delete*"> PROPAGATION_REQUIRED,-SQLException </prop> <prop key="clone*"> PROPAGATION_REQUIRED,-SQLException </prop> <prop key="execute*"> PROPAGATION_REQUIRED,-SQLException </prop> <prop key="*">PROPAGATION_REQUIRED,readOnly</prop> </props> </property> </bean> </beans>
applicationContext-security.xml配置
<?xml version="1.0" encoding="UTF-8"?> <b:beans xmlns="http://www.springframework.org/schema/security" xmlns:b="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd"> <!-- 不要過濾圖片等靜態資源,其中**代表可以跨越目錄,*不可以跨越目錄。 --> <!-- <http pattern="/**/*.jpg" security="none" /> <http pattern="/**/*.png" security="none" /> <http pattern="/**/*.gif" security="none" /> <http pattern="/**/*.css" security="none" /> <http pattern="/**/*.js" security="none" /> --> <http pattern="/login.jsp" security="none" /> <!-- <http pattern="index.html" security="none"/> --> <http pattern="/jsp/forgotpassword.jsp" security="none" /> <http auto-config="true" access-denied-page="/accessDenied.jsp"> <form-login login-page="/login.jsp" authentication-failure-url="/login.jsp?error=true" /> <!-- "記住我"功能,采用持久化策略(將用戶的登錄信息存放在數據庫表中) --> <remember-me data-source-ref="dataSource" /> <!-- 檢測失效的sessionId,超時時定位到另外一個URL --> <session-management invalid-session-url="/sessionTimeout.jsp" /> <custom-filter ref="myFilter" before="FILTER_SECURITY_INTERCEPTOR"/> </http> <!-- 一個自定義的filter,必須包含authenticationManager, accessDecisionManager,securityMetadataSource三個屬性。 --> <b:bean id="myFilter" class="com.springmvc.security.MyFilterSecurityInterceptor"> <b:property name="authenticationManager" ref="authenticationManager"/> <b:property name="accessDecisionManager" ref="myAccessDecisionManager"/> <b:property name="securityMetadataSource" ref="mySecurityMetadataSource"/> </b:bean> <!-- 注意能夠為authentication-manager 設置alias別名 --> <authentication-manager alias="authenticationManager"> <authentication-provider user-service-ref="userDetailsManager"> <!-- <password-encoder ref="passwordEncoder"> <salt-source user-property="username" /> </password-encoder> --> </authentication-provider> </authentication-manager> <!-- 事件監聽:實現了 ApplicationListener監聽接口,包括AuthenticationCredentialsNotFoundEvent 事件, AuthorizationFailureEvent事件,AuthorizedEvent事件, PublicInvocationEvent事件 --> <b:bean class="org.springframework.security.authentication.event.LoggerListener" /> <!-- 用戶詳細信息管理:數據源、用戶緩存(通過數據庫管理用戶、角色、權限、資源)。 --> <b:bean id="userDetailsManager" class="com.springmvc.security.MyUserDetailsService"/> <!-- 訪問決策器,決定某個用戶具有的角色,是否有足夠的權限去訪問某個資源。 --> <b:bean id="myAccessDecisionManager" class="com.springmvc.security.MyAccessDecisionManager"> </b:bean> <!-- 資源源數據定義,將所有的資源和權限對應關系建立起來,即定義某一資源可以被哪些角色去訪問。 --> <b:bean id="mySecurityMetadataSource" class="com.springmvc.security.MyInvocationSecurityMetadataSourceService"> <b:constructor-arg ref="testDao"/> </b:bean> </b:beans>
主要內容大概是這些,剩下的參考上傳的項目,添加缺省的內容,
啟動服務,打開瀏覽器,輸入http://localhost:8080/ThirdSpringSecurity/user/user.jsp,由於有權限限制,會跳轉到登錄頁面,輸入user賬號和密碼(沒有采用密文)登錄后就可以到user.jsp頁面,如果將url改為http://localhost:8080/ThirdSpringSecurity/admin/admin.jsp,會跳轉到權限不足的界面。
,驗證及授權的過程如下:
1、當Web服務器啟動時,通過Web.xml中對於Spring Security的配置,加載過濾器鏈,那么在加載MyFilterSecurityInterceptor類時,會注入MyInvocationSecurityMetadataSourceService、MyUserDetailsService、MyAccessDecisionManager類。
2、該MyInvocationSecurityMetadataSourceService類在執行時會提取數據庫中所有的用戶權限,形成權限列表;
並循環該權限列表,通過每個權限再從數據庫中提取出該權限所對應的資源列表,並將資源(URL)作為key,權限列表作為value,形成Map結構的數據。
3、當用戶登錄時,AuthenticationManager進行響應,通過用戶輸入的用戶名和密碼,然后再根據用戶定義的密碼算法和鹽值等進行計算並和數據庫比對,
當正確時通過驗證。此時MyUserDetailsService進行響應,根據用戶名從數據庫中提取該用戶的權限列表,組合成UserDetails供Spring Security使用。
4、當用戶點擊某個功能時,觸發MyAccessDecisionManager類,該類通過decide方法對用戶的資源訪問進行攔截。
用戶點擊某個功能時,實際上是請求某個URL或Action, 無論.jsp也好,.action或.do也好,在請求時無一例外的表現為URL。
還記得第2步時那個Map結構的數據嗎? 若用戶點擊了"login.action"這個URL之后,那么這個URL就跟那個Map結構的數據中的key對比,若兩者相同,
則根據該url提取出Map結構的數據中的value來,這說明:若要請求這個URL,必須具有跟這個URL相對應的權限值。這個權限有可能是一個單獨的權限,
也有可能是一個權限列表,也就是說,一個URL有可能被多種權限訪問。
那好,我們在MyAccessDecisionManager類的decide這個方法里,將通過URL取得的權限列表進行循環,然后跟第3步中登錄的用戶所具有的權限進行比對,若相同,則表明該用戶具有訪問該資源的權利。 不大明白吧? 簡單地說, 在數據庫中我們定義了訪問“LOGIN”這個URL必須是具有ROLE_ADMIN權限的人來訪問,那么,登錄用戶恰恰具有該ROLE_ADMIN權限,兩者的比對過程中,就能夠返回TRUE,可以允許該用戶進行訪問。就這么簡單!
不過在第2步的時候,一定要注意,MyInvocationSecurityMetadataSoruceService類的loadResourceDefine()方法中,形成以URL為key,權限列表為value的Map時,
要注意key和Value的對應性,避免Value的不正確對應形成重復,這樣會導致沒有權限的人也能訪問到不該訪問到的資源。
還有getAttributes()方法,要有 url.indexOf("?")這樣的判斷,要通過判斷對URL特別是Action問號之前的部分進行匹配,防止用戶請求的帶參數的URL與你數據庫中定義的URL不匹配,造成訪問拒絕!