簡介
對于一個典型的Web應用,完善的認證和授權機制是必不可少的,在SpringFramework中,Juergen Hoeller提供的范例JPetStore給了一些這方面的介紹,但還遠遠不夠,Acegi是一個專門為SpringFramework提供安全機制的項目,全稱為Acegi Security System for Spring,當前版本為0.5.1,就其目前提供的功能,應該可以滿足絕大多數應用的需求。
本文的主要目的是希望能夠說明如何在基于Spring構架的Web應用中使用Acegi,而不是詳細介紹其中的每個接口、每個類。注意,即使對已經存在的Spring應用,通過下面介紹的步驟,也可以馬上享受到Acegi提供的認證和授權。
基礎工作
在你的Web應用的lib中添加Acegi下載包中的acegi-security.jar
web.xml
實現認證和授權的最常用的方法是通過filter,Acegi亦是如此,通常Acegi需要在web.xml添加以下5個filter:
<filter> ? <filter-name>Acegi Channel Processing Filter</filter-name> ? <filter-class>net.sf.acegisecurity.util.FilterToBeanProxy</filter-class> ? <init-param> ??? <param-name>targetClass</param-name> ??? <param-value>net.sf.acegisecurity.securechannel.ChannelProcessingFilter</param-value> ? </init-param> </filter> <filter> ? <filter-name>Acegi Authentication Processing Filter</filter-name> ? <filter-class>net.sf.acegisecurity.util.FilterToBeanProxy</filter-class> ? <init-param> ??? <param-name>targetClass</param-name> ??? <param-value>net.sf.acegisecurity.ui.webapp.AuthenticationProcessingFilter</param-value> ? </init-param> </filter> <filter> ? <filter-name>Acegi HTTP BASIC Authorization Filter</filter-name> ? <filter-class>net.sf.acegisecurity.util.FilterToBeanProxy</filter-class> ? <init-param> ??? <param-name>targetClass</param-name> ??? <param-value>net.sf.acegisecurity.ui.basicauth.BasicProcessingFilter</param-value> ? </init-param> </filter> <filter> ? <filter-name>Acegi Security System for Spring Auto Integration Filter</filter-name> ? <filter-class>net.sf.acegisecurity.ui.AutoIntegrationFilter</filter-class> </filter> <filter> ? <filter-name>Acegi HTTP Request Security Filter</filter-name> ? <filter-class>net.sf.acegisecurity.util.FilterToBeanProxy</filter-class> ? <init-param> ??? <param-name>targetClass</param-name> ??? <param-value>net.sf.acegisecurity.intercept.web.SecurityEnforcementFilter</param-value> ? </init-param> </filter>
|
最先引起迷惑的是net.sf.acegisecurity.util.FilterToBeanProxy,Acegi自己的文檔上解釋是:“What??FilterToBeanProxy does is delegate the Filter's methods through to a bean which is obtained from the Spring application context. This enables the bean to benefit from the Spring application context lifecycle support and configuration flexibility.”,如希望深究的話,去看看源代碼應該不難理解。
再下來就是添加filter-mapping了:
<filter-mapping> ? <filter-name>Acegi Channel Processing Filter</filter-name> ? <url-pattern>/*</url-pattern> </filter-mapping> <filter-mapping> ? <filter-name>Acegi Authentication Processing Filter</filter-name> ? <url-pattern>/*</url-pattern> </filter-mapping> <filter-mapping> ? <filter-name>Acegi HTTP BASIC Authorization Filter</filter-name> ? <url-pattern>/*</url-pattern> </filter-mapping> <filter-mapping> ? <filter-name>Acegi Security System for Spring Auto Integration Filter</filter-name> ? <url-pattern>/*</url-pattern> </filter-mapping> <filter-mapping> ? <filter-name>Acegi HTTP Request Security Filter</filter-name> ? <url-pattern>/*</url-pattern> </filter-mapping>
|
? 這里,需要注意以下三點:? 1) 這幾個filter的順序是不能更改的,順序不對將無法正常工作;? 2) 如果你的應用不需要安全傳輸,如https,則將"Acegi Channel Processing Filter"相關內容注釋掉即可;? 3) 如果你的應用不需要Spring提供的遠程訪問機制,如Hessian and Burlap,將"Acegi HTTP BASIC Authorization Filter"相關內容注釋掉即可。#p# applicationContext.xml 接下來就是要添加applicationContext.xml中的內容了,從剛才FilterToBeanFactory的解釋可以看出,真正的filter都在Spring的applicationContext中管理: 1)首先,你的數據庫中必須具有保存用戶名和密碼的table,Acegi要求table的schema必須如下: CREATE TABLE users ( ??? username VARCHAR(50) NOT NULL PRIMARY KEY, ??? password VARCHAR(50) NOT NULL, ??? enabled BIT NOT NULL ); CREATE TABLE authorities ( ??? username VARCHAR(50) NOT NULL, ??? authority VARCHAR(50) NOT NULL ); CREATE UNIQUE INDEX ix_auth_username ON authorities ( username, authority ); ALTER TABLE authorities ADD CONSTRAINT fk_authorities_users foreign key (username) REFERENCES users (username);
|
2)添加訪問你的數據庫的datasource和Acegi的jdbcDao,如下: <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> ? <property name="driverClassName"><value>${jdbc.driverClassName}</value></property> ? <property name="url"><value>${jdbc.url}</value></property> ? <property name="username"><value>${jdbc.username}</value></property> ? <property name="password"><value>${jdbc.password}</value></property> </bean> <bean id="jdbcDaoImpl" class="net.sf.acegisecurity.providers.dao.jdbc.JdbcDaoImpl"> ? <property name="dataSource"><ref bean="dataSource"/></property> </bean>
|
3)添加DaoAuthenticationProvider: <bean id="daoAuthenticationProvider" class="net.sf.acegisecurity.providers.dao.DaoAuthenticationProvider"> ? <property name="authenticationDao"><ref bean="authenticationDao"/></property> ? <property name="userCache"><ref bean="userCache"/></property> </bean> <bean id="userCache" class="net.sf.acegisecurity.providers.dao.cache.EhCacheBasedUserCache"> ? <property name="minutesToIdle"><value>5</value></property> </bean>
|
如果你需要對密碼加密,則在daoAuthenticationProvider中加入:<property name="passwordEncoder"><ref bean="passwordEncoder"/></property>,Acegi提供了幾種加密方法,詳細情況可看包net.sf.acegisecurity.providers.encoding 4)添加authenticationManager: <bean id="authenticationManager" class="net.sf.acegisecurity.providers.ProviderManager"> ? <property name="providers"> ??? <list> ????? <ref bean="daoAuthenticationProvider"/> ??? </list> ?? </property> </bean>
|
5)添加accessDecisionManager: ?<bean id="accessDecisionManager" class="net.sf.acegisecurity.vote.AffirmativeBased"> ? <property name="allowIfAllAbstainDecisions"> ??? <value>false</value> ? </property> ? <property name="decisionVoters"> ??? <list><ref bean="roleVoter"/></list> ? </property> </bean> <bean id="roleVoter" class="net.sf.acegisecurity.vote.RoleVoter"/>
|
6)添加authenticationProcessingFilterEntryPoint: <bean id="authenticationProcessingFilterEntryPoint" class="net.sf.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint"> ? <property name="loginFormUrl"><value>/acegilogin.jsp</value></property> ? <property name="forceHttps"><value>false</value></property> </bean>
|
其中acegilogin.jsp是登陸頁面,一個最簡單的登錄頁面如下: <%@ taglib prefix='c' uri='http://java.sun.com/jstl/core' %> <%@ page import="net.sf.acegisecurity.ui.AbstractProcessingFilter" %> <%@ page import="net.sf.acegisecurity.AuthenticationException" %> <html> ? <head> ??? <title>Login</title> ? </head> ? <body> ??? <h1>Login</h1> ??? <form action="<c:url value='j_acegi_security_check'/>" method="POST"> ????? <table> ??????? <tr><td>User:</td><td><input type='text' name='j_username'></td></tr> ??????? <tr><td>Password:</td><td><input type='password' name='j_password'></td></tr> ??????? <tr><td colspan='2'><input name="submit" type="submit"></td></tr> ??????? <tr><td colspan='2'><input name="reset" type="reset"></td></tr> ????? </table> ??? </form> ? </body> </html>
|
7)添加filterInvocationInterceptor: <bean id="filterInvocationInterceptor" class="net.sf.acegisecurity.intercept.web.FilterSecurityInterceptor"> ? <property name="authenticationManager"> ??? <ref bean="authenticationManager"/> ? </property> ? <property name="accessDecisionManager"> ??? <ref bean="accessDecisionManager"/> ? </property> ? <property name="objectDefinitionSource"> ??? <value> ????? CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON ????? \A/sec/administrator.*\Z=ROLE_SUPERVISOR ????? \A/sec/user.*\Z=ROLE_TELLER ??? </value> ? </property> </bean>
|
這里請注意,要objectDefinitionSource中定義哪些頁面需要權限訪問,需要根據自己的應用需求進行修改,我上面給出的定義的意思是這樣的: #p# a. CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON意思是在比較請求路徑時全部轉換為小寫? b. \A/sec/administrator.*\Z=ROLE_SUPERVISOR意思是只有權限為ROLE_SUPERVISOR才能訪問/sec/administrator*的頁面? c. \A/sec/user.*\Z=ROLE_TELLER意思是只有權限為ROLE_TELLER的用戶才能訪問/sec/user*的頁面 8)添加securityEnforcementFilter: <bean id="securityEnforcementFilter" class="net.sf.acegisecurity.intercept.web.SecurityEnforcementFilter"> ? <property name="filterSecurityInterceptor"> ??? <ref bean="filterInvocationInterceptor"/> ? </property> ? <property name="authenticationEntryPoint"> ??? <ref bean="authenticationProcessingFilterEntryPoint"/> ? </property> </bean>
|
9)添加authenticationProcessingFilter: <bean id="authenticationProcessingFilter" class="net.sf.acegisecurity.ui.webapp.AuthenticationProcessingFilter"> ? <property name="authenticationManager"> ??? <ref bean="authenticationManager"/> ? </property> ? <property name="authenticationFailureUrl"> ??? <value>/loginerror.jsp</value> ? </property> ? <property name="defaultTargetUrl"> ??? <value>/</value> ? </property> ? <property name="filterProcessesUrl"> ??? <value>/j_acegi_security_check</value> ? </property> </bean>
|
其中authenticationFailureUrl是認證失敗的頁面。 10)如果需要一些頁面通過安全通道的話,添加下面的配置: <bean id="channelProcessingFilter" class="net.sf.acegisecurity.securechannel.ChannelProcessingFilter"> ? <property name="channelDecisionManager"> ??? <ref bean="channelDecisionManager"/> ? </property> ? <property name="filterInvocationDefinitionSource"> ??? <value> ????? CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON ????? \A/sec/administrator.*\Z=REQUIRES_SECURE_CHANNEL ????? \A/acegilogin.jsp.*\Z=REQUIRES_SECURE_CHANNEL ????? \A/j_acegi_security_check.*\Z=REQUIRES_SECURE_CHANNEL ????? \A.*\Z=REQUIRES_INSECURE_CHANNEL ??? </value> ? </property> </bean> <bean id="channelDecisionManager" class="net.sf.acegisecurity.securechannel.ChannelDecisionManagerImpl"> ? <property name="channelProcessors"> ??? <list> ????? <ref bean="secureChannelProcessor"/> ????? <ref bean="insecureChannelProcessor"/> ??? </list> ? </property> </bean> <bean id="secureChannelProcessor" class="net.sf.acegisecurity.securechannel.SecureChannelProcessor"/> <bean id="insecureChannelProcessor" class="net.sf.acegisecurity.securechannel.InsecureChannelProcessor"/>
|
缺少了什么? Acegi目前提供了兩種“secure object”,分別對頁面和方法進行安全認證管理,我這里介紹的只是利用FilterSecurityInterceptor對訪問頁面的權限控制,除此之外,Acegi還提供了另外一個Interceptor——MethodSecurityInterceptor,它結合runAsManager可實現對對象中的方法的權限控制,使用方法可參看Acegi自帶的文檔和contact范例。 最后要說的 本來以為只是說明如何使用Acegi而已,應該非常簡單,但真正寫起來才發現想要條理清楚的理順所有需要的bean還是很困難的,但愿我沒有遺漏太多東西,如果我的文章有什么遺漏或錯誤的話,還請參看Acegi自帶的quick-start范例,但請注意,這個范例是不能直接拿來用的。 |