天天看點

Spring 3之MVC & Security簡單整合開發(三)

本文接[url=http://sarin.iteye.com/blog/830831]上一篇[/url]繼續深入研究Security架構。

Security對資料庫驗證使用者有兩種方式,上文提到的是它預設支援的資料庫表結構,但基本上用于實際是不現實的,因為我們的資料庫都有自己的業務邏輯,是以現在來看看怎麼在我們自己的資料庫上進行Security架構的使用者驗證整合,這裡給出一個比較通用的資料庫權限設計結構:

[img]http://dl.iteye.com/upload/attachment/360418/8ef53c2b-469b-3b0f-8743-03f3f6d5a61d.png[/img]

假設我們的資料表名稱為b_user和b_userrole,它們的結構如下:

CREATE TABLE `b_user` (
  `ID` int(11) NOT NULL AUTO_INCREMENT,
  `USERNAME` varchar(20) NOT NULL,
  `PASSWORD` varchar(32) NOT NULL,
  PRIMARY KEY (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8
CREATE TABLE `b_userrole` (
  `ID` int(11) NOT NULL,
  `USERID` int(11) NOT NULL,
  `ROLE` varchar(15) NOT NULL,
  PRIMARY KEY (`ID`),
  KEY `FK_USERID_USERROLE` (`USERID`),
  CONSTRAINT `FK_USERID_USERROLE` FOREIGN KEY (`USERID`) REFERENCES `b_user` (`ID`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB DEFAULT CHARSET=utf8
           

那麼表名,字段名和結構都和Security架構預設的不比對,隻好通過SQL語句來讓Security架構識别了,在配置檔案的資料庫驗證部分,我們可以這麼來寫:

在jdbc-user-service中,我們啟用了兩個屬性,其中放置的是SQL語句,就是我們自定義的使用者驗證方式,将我們的資料庫設計和Security架構相比對,這裡的角色一定是在攔截url标簽中配置過的,否則Security架構不能識别使用者身份。

啟動應用程式,發現這和原來的驗證效果是一樣的,這就是自定義的資料庫驗證方式了。也非常簡單,就是用SQL語句查詢結果比對Security架構,不過這可能要和自己應用的資料庫設計做出調整,盡量做到最小調整。

這裡補充一點使用者驗證方式的配置,我們已經使用了在配置檔案裡配置使用者和資料庫驗證,一種是支援為數不多的使用者,一種是支援資料庫大量查詢的。對于前者,将配置資訊寫在XML檔案中,和Security架構的配置資訊粘在一起,不利于維護。其實Security架構也支援屬性檔案的配置,我們可以這麼來寫:

<security:authentication-manager>
	<security:authentication-provider>
		<security:user-service properties="/WEB-INF/users.properties"/>
	</security:authentication-provider>
</security:authentication-manager>
           

這裡把使用者資訊都寫在了users.properties裡,我們來看這個屬性配置檔案:

admin=123,ROLE_ADMIN,ROLE_USER
user1=123,ROLE_USER
user2=123,enabled,ROLE_USER
           

這裡面名/值對的形式排列的,值的字段比較多,我們來逐個解釋。admin/user1是使用者名,不用多說,等号後面第一位是密碼,這裡沒有加密。第二位是狀态,這是可選的,預設是enabled,第三位以後就是使用者所擁有的角色了,這麼使用和前面的效果也是相同的。

使用者驗證部分基本就這麼多内容,這裡沒有涉及到LDAP相關部分。下面是通路控制的說明,通路控制是Security架構的另一大特性,可以對其進行自定義的擴充,設計符合我們業務邏輯的控制。這比URL攔截又深入了一步,可以過濾的東西又多了。

設計到通路控制,要引入一個概念,誰來決定能否通路,進而進行控制。Security架構中的通路控制管理有三種方案:至少有一個同意通路,全部同意通路,全部棄權或都同意通路(也就是沒有拒絕的)。如何同意?投票産生!Security架構一個可配置的元素就出來了,那就是投票器了。和現實的投票一樣,分同意,反對和棄權三類。

下面我們應用第一類通路控制管理:至少有一個投票器同意通路,在配置檔案中這麼來設定:

<bean id="accessDecisionManager"
	class="org.springframework.security.access.vote.AffirmativeBased">
	<property name="decisionVoters">
		<list>
			<bean class="org.springframework.security.access.vote.RoleVoter" />
			<bean class="org.springframework.security.access.vote.AuthenticatedVoter" />
		</list>
	</property>
</bean>
           

上面是預設需要的認證投票,下面就是我們定制的内容了,用來滿足特定需要。在消息釋出應用中,有這樣一個需求,在伺服器上登入的使用者,給可以删除消息的權限,也就是說不用管理者賬戶登入,也能删除。那麼我們就需要對通路進行控制。在伺服器本身登入的使用者的IP應該是本地位址127.0.0.1,那麼隻要IP是它的允許删除,我們來自定義一個投票器來進行投票:

package org.ourpioneer.board.security;
import java.util.Collection;
import org.springframework.security.access.AccessDecisionVoter;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.WebAuthenticationDetails;
public class IPAddressVoter implements AccessDecisionVoter {
	public static final String IP_PREFIX = "IP_";
	public static final String IP_LOCAL_HOST = "IP_LOCAL_HOST";
	public boolean supports(ConfigAttribute attribute) {
		return attribute.getAttribute() != null
				&& attribute.getAttribute().startsWith(IP_PREFIX);
	}
	public boolean supports(Class<?> clazz) {
		return true;
	}
	public int vote(Authentication authentication, Object object,
			Collection<ConfigAttribute> attributes) {
		if (!(authentication.getDetails() instanceof WebAuthenticationDetails)) {
			return ACCESS_DENIED;
		}
		WebAuthenticationDetails details = (WebAuthenticationDetails) authentication
				.getDetails();
		String address = details.getRemoteAddress();
		int result = ACCESS_ABSTAIN;
		for (ConfigAttribute config : attributes) {
			result = ACCESS_DENIED;
			if (IP_LOCAL_HOST.equals(config.getAttribute())) {
				if (address.equals("127.0.0.1")
						|| address.equals("0:0:0:0:0:0:0:1")) {
					return ACCESS_GRANTED;
				}
			}
		}
		return result;
	}
}
           

IP位址投票器僅處理屬性開頭是IP的通路,而支援通路的隻能是IP_LOCAL_HOST通路屬性。如果通路者的IP是127.0.0.1或者0:0:0:0:0:0:0:1的可以通路,其餘的拒絕通路。在配置檔案的投票器list中再加入這個類:

<bean class="org.ourpioneer.board.security.IPAddressVoter" />
           

之後還要在URL攔截屬性中修改配置IP_LOCAL_HOST屬性的通路權限:

<security:intercept-url pattern="/messageDelete.htm"	access="ROLE_ADMIN,IP_LOCAL_HOST" />
           

而且在http中還要配置通路決定管理器,否則是不能識别到IP_LOCAL_HOST的:

<security:http access-decision-manager-ref="accessDecisionManager">
           

此時,在本地用user1使用者登入,也具有了删除權限,可以删除文章了。這就是投票器的簡單應用了。下面是方法調用安全,這是非常細粒度的安全控制,可以作用于類的方法,那麼也就是說,對一塊業務邏輯有權限的使用者組,可能允許你能添加而不能删除,他能修改而不能添加和删除,這都是可以實作的,因為這已經細化到了方法之上了,一個類的某一個方法給你授權通路,其餘方法就通路不到,細化到一個功能點上的通路,安全性有很大的提升。先看看對控制器方法的安全通路,這個配置相對簡單,在配置檔案中,把安全配置檔案和controller的聲明放在一起:

<context:component-scan base-package="org.ourpioneer.board.web" />
<security:global-method-security
		jsr250-annotations="enabled" secured-annotations="enabled" />
           

這樣才能對controller的方法進行控制。不過對方法實行安全控制之後,就沒有必要對URL進行攔截了,http配置中的url攔截就都可以去掉了,僅留下登入和退出的就可以了:

<security:http access-decision-manager-ref="accessDecisionManager">
	<security:form-login login-page="/login.jsp"
		login-processing-url="/login" default-target-url="/messageList.htm"
			authentication-failure-url="/login.jsp?error=true" />
	<security:logout logout-success-url="/login.jsp" />
</security:http>
           

雖然這裡加入了access-decision-manager-ref="accessDecisionManager",但是對方法的安全不是這裡做的,是以這樣的話使用user1登入就沒有對消息的删除權限了,那麼怎麼能恢複呢?很簡單,在global-method-security中加入它就可以了。這就完成了對控制器方法的配置,那麼因為前面都是注解實作的,是以在方法上配置注解就行了,前面代碼很全,這裡給出一個示例:

@RequestMapping(method = RequestMethod.GET)
@Secured( { "ROLE_ADMIN", "IP_LOCAL_HOST" })
public String messageDelete(
	@RequestParam(required = true, value = "messageId") Long messageId,
			Model model) {
	Message message = messageBoardService.findMessageById(messageId);
	messageBoardService.deleteMeesage(message);
	model.addAttribute("messages", messageBoardService.listMessages());
	return "redirect:messageList.htm";
}
           

隻要在注解方法上表明可通路的權限就能實作攔截了。當然在Service上實作攔截同理可得,隻是需要注意一下注解聲明的所在配置檔案,否則可能無效,這确實有點不爽。方法攔截除了注解,還有嵌入配置方式和切點配置方式,這兩種都是正常做法,參考官方文檔就可以了。

最後一點是V層的攔截,這在前面已經提到了,使用的是Security架構的标簽庫實作的。可用的标簽和屬性,直接參考官方文檔即可,使用也很友善,這裡就不過多說明了。

Spring 3的MVC和Security架構的簡單整合就介紹完了,沒有過深内容,都是基礎應用,如果想深入了解,官方文檔是最佳的學習資料。本文的示例代碼請直接下載下傳,需要Maven環境來支援,關于Maven環境的配置,可以參考[url=http://sarin.iteye.com/blog/784275]Maven建構Java Web開發環境[/url]的介紹。

歡迎交流,希望對使用者有用。

繼續閱讀