第 1 章 一個簡單的HelloWorld
Spring Security中可以使用Acegi-1.x時代的普通配置方式,也可以使用從2.0時代才出現的命名空間配置方式,實際上這兩者實現的功能是完全一致的,只是新的命名空間配置方式可以把原來需要幾百行的配置壓縮成短短的幾十行。我們的教程中都會使用命名空間的方式進行配置,凡事務求最簡。
<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 Security的檢測,這從第一時間把沒有授權的請求排除在系統之外,保證系統資源的安全。關於過濾器配置的更多講解可以參考http://www.family168.com/tutorial/jsp/html/jsp-ch-07.html#jsp-ch-07-03-01。
在applicationContext.xml中使用Spring Security提供的命名空間進行配置。
spring 2.0的舊的命名空間
<?xml version="1.0" encoding="UTF-8"?> <beans:beans xmlns="http://www.springframework.org/schema/security"1 xmlns:beans="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-2.0.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-2.0.4.xsd"> <http auto-config='true'>2 <intercept-url pattern="/admin.jsp" access="ROLE_ADMIN" />3 <intercept-url pattern="/**" access="ROLE_USER" /> </http> <authentication-provider> <user-service> <user name="admin" password="admin" authorities="ROLE_USER, ROLE_ADMIN" />4 <user name="user" password="user" authorities="ROLE_USER" /> </user-service> </authentication-provider> </beans:beans>
使用spring securety3.0以上的命名空間
<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" 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.xsd"> <http auto-config="true">2 <intercept-url pattern="/admin.jsp" access="ROLE_ADMIN" />3 <intercept-url pattern="/**" access="ROLE_USER" /> <form-login/> </http> <authentication-provider> <user-service> <user name="admin" password="admin" authorities="ROLE_USER, ROLE_ADMIN" />4 <user name="user" password="user" authorities="ROLE_USER" /> </user-service> </authentication-provider> </beans:beans>
聲明在xml中使用Spring Security提供的命名空間。 |
|
http部分配置如何攔截用戶請求。auto-config='true'將自動配置幾種常用的權限控制機制,包括form, anonymous, rememberMe。 |
|
我們利用intercept-url來判斷用戶需要具有何種權限才能訪問對應的url資源,可以在pattern中指定一個特定的url資源,也可以使用通配符指定一組類似的url資源。例子中定義的兩個intercepter-url,第一個用來控制對/admin.jsp的訪問,第二個使用了通配符/**,說明它將控制對系統中所有url資源的訪問。 在實際使用中,Spring Security采用的是一種就近原則,就是說當用戶訪問的url資源滿足多個intercepter-url時,系統將使用第一個符合條件的intercept-url進行權限控制。在我們這個例子中就是,當用戶訪問/admin.jsp時,雖然兩個intercept-url都滿足要求,但因為第一個intercept-url排在上面,所以Spring Security會使用第一個intercept-url中的配置處理對/admin.jsp的請求,也就是說,只有那些擁有了ROLE_ADMIN權限的用戶才能訪問/admin.jsp。 access指定的權限部分比較有趣,大家可以注意到這些權限標示符都是以ROLE_開頭的,實際上這與Spring Security中的Voter機制有着千絲萬縷的聯系,只有包含了特定前綴的字符串才會被Spring Security處理。目前來說我們只需要記住這一點就可以了,在教程以后的部分中我們會詳細講解Voter的內容。 |
|
user-service中定義了兩個用戶,admin和user。為了簡便起見,我們使用明文定義了兩個用戶對應的密碼,這只是為了當前演示的方便,之后的例子中我們會使用Spring Security提供的加密方式,避免用戶密碼被他人竊取。 最最重要的部分是authorities,這里定義了這個用戶登陸之后將會擁有的權限,它與上面intercept-url中定義的權限內容一一對應。每個用戶可以同時擁有多個權限,例子中的admin用戶就擁有ROLE_ADMIN和ROLE_USER兩種權限,這使得admin用戶在登陸之后可以訪問ROLE_ADMIN和ROLE_USER允許訪問的所有資源。 與之對應的是,user用戶就只擁有ROLE_USER權限,所以他只能訪問ROLE_USER允許訪問的資源,而不能訪問ROLE_ADMIN允許訪問的資源 |
因為Spring Security是建立在Spring的基礎之上的,所以web.xml中除了需要配置我們剛剛提到的過濾器,還要加上加載Spring的相關配置。最終得到的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_2_5.xsd" id="WebApp_ID" version="2.5"> <display-name>spring_security_1</display-name> <context-param> <param-name>contextConfigLocation</param-name> <!-- 應用上下文配置文件 --> <!-- 兩種位置寫法 --> <!-- springMvc和spring security 兩個配置文件 --> <!-- <param-value>/WEB-INF/spring-servlet.xml</param-value> --> <param-value>classpath:spring-servlet2.xml,classpath:applicationContext-security.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- spring securit start --> <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 securit start --> <!-- 配置spring核心servlet --> <servlet> <servlet-name>spring</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <!-- url-pattern配置為/ 攔截 --> <servlet-mapping> <servlet-name>spring</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
演示不同權限的用戶登陸之后可以訪問不同的資源,我們為項目添加了兩個jsp文件,admin.jsp和index.jsp。其中admin.jsp只有那些擁有ROLE_ADMIN權限的用戶才能訪問,而index.jsp只允許那些擁有ROLE_USER權限的用戶才能訪問。
最終我們的整個項目會變成下面這樣:
等到項目啟動完成后。打開瀏覽器訪問http://localhost:8080/spring_security_1/就可以看到登陸頁面。
這個簡陋的頁面是Spring Security自動生成的,一來為了演示的方便,二來避免用戶自己編寫登陸頁面時犯錯,Spring Security為了避免可能出現的風險,連測試用的登錄頁面都自動生成出來了。在這里我們就省去編寫登陸頁面的步驟,直接使用默認生成的登錄頁面進行演示吧。
如果輸入的是正確的用戶名和密碼,比如user/user,系統在登陸成功后會默認跳轉到index.jsp。
index。jsp的頁面
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>登錄首頁</title> </head> <body> <span color="red">登錄成功!</span> </body> </html>
直接打開http://localhost:8080/spring_security_1/admin.jsp
很遺憾,user用戶是無法訪問/admin.jsp這個url資源的,這在上面的配置文件中已經有過深入的討論。我們在這里再簡要重復一遍:user用戶擁有ROLE_USER權限,但是/admin.jsp資源需要用戶擁有ROLE_ADMIN權限才能訪問,所以當user用戶視圖訪問被保護的/admin.jsp時,Spring Security會在中途攔截這一請求,返回拒絕訪問頁面。
為了正常訪問admin.jsp,使用admin/admin登陸系統,然后再次點擊admin.jsp鏈接就會顯示出admin.jsp中的內容。
根據我們之前的配置,admin用戶擁有ROLE_ADMIN和ROLE_USER兩個權限,因為他擁有ROLE_USER權限,所以可以訪問/index.jsp,因為他擁有ROLE_ADMIN權限,所以他可以訪問/admin.jsp。
至此,我們很高興的宣布,咱們已經正式完成,並運行演示了一個最簡單的由Spring Security保護的web系統,下一步我們會深入討論Spring Security為我們提供的其他保護功能,多姿多彩的特性。
第 2 章 使用數據庫管理用戶權限
上一章節中,我們把用戶信息和權限信息放到了xml文件中,這是為了演示如何使用最小的配置就可以使用Spring Security,而實際開發中,用戶信息和權限信息通常是被保存在數據庫中的,為此Spring Security提供了通過數據庫獲得用戶權限信息的方式。
為了從數據庫中獲取用戶權限信息,我們所需要的僅僅是修改配置文件中的authentication-provider部分。
將上一章配置文件中的user-service替換為jdbc-user-service,替換內容如下所示:
<authentication-provider> <jdbc-user-service data-source-ref="dataSource"/> <!-- <user-service> <user name="admin" password="admin" authorities="ROLE_USER, ROLE_ADMIN" /> <user name="user" password="user" authorities="ROLE_USER" /> </user-service> --> </authentication-provider>
現在只要再為jdbc-user-service提供一個dataSource就可以讓Spring Security使用數據庫中的權限信息了。在此我們使用spring創建一個演示用的dataSource實現,這個dataSource會連接到mysql數據庫,從中獲取用戶權限信息。[1]
<beans:bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <beans:property name="driverClassName" value="com.mysql.jdbc.Driver"/> <beans:property name="url" value="jdbc:mysql://localhost/spring_security?useUnicode=true&characterEncoding=utf-8"/> <beans:property name="username" value="cqyusp_dev"/> <beans:property name="password" value="cqyusp_dev"/> </beans:bean>
最終的配置文件如下所示:
<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" 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.xsd"> <http auto-config="true"> <intercept-url pattern="/admin.jsp" access="ROLE_ADMIN" /> <intercept-url pattern="/**" access="ROLE_USER" /> <form-login/> </http> <authentication-manager> <authentication-provider> <jdbc-user-service data-source-ref="dataSource"/> <!-- <user-service> <user name="admin" password="admin" authorities="ROLE_USER, ROLE_ADMIN" /> <user name="user" password="user" authorities="ROLE_USER" /> </user-service> --> </authentication-provider> </authentication-manager> <beans:bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <beans:property name="driverClassName" value="com.mysql.jdbc.Driver"/> <beans:property name="url" value="jdbc:mysql://localhost/spring_security?useUnicode=true&characterEncoding=utf-8"/> <beans:property name="username" value="cqyusp_dev"/> <beans:property name="password" value="cqyusp_dev"/> </beans:bean> </beans:beans>
Spring Security默認情況下需要兩張表,用戶表和權限表。以下是mysql中的建表語句
CREATE TABLE users( username VARCHAR(50) NOT NULL PRIMARY KEY, PASSWORD VARCHAR(50) NOT NULL, enabled BOOLEAN NOT NULL ); CREATE TABLE authorities ( username VARCHAR(50) NOT NULL, authority VARCHAR(50) NOT NULL, CONSTRAINT fk_authorities_users FOREIGN KEY(username) REFERENCES users(username) ); CREATE UNIQUE INDEX ix_auth_username ON authorities (username,authority); INSERT INTO users(username,PASSWORD,enabled) VALUES('admin','admin',TRUE); INSERT INTO users(username,PASSWORD,enabled) VALUES('user','user',TRUE); INSERT INTO authorities(username,authority) VALUES('admin','ROLE_ADMIN'); INSERT INTO authorities(username,authority) VALUES('admin','ROLE_USER'); INSERT INTO authorities(username,authority) VALUES('user','ROLE_USER');
users:用戶表。包含username用戶登錄名,password登陸密碼,enabled用戶是否被禁用三個字段。 其中username用戶登錄名為主鍵。 |
|
authorities:權限表。包含username用戶登錄名,authorities對應權限兩個字段。 其中username字段與users用戶表的主鍵使用外鍵關聯。 |
|
對authorities權限表的username和authority創建唯一索引,提高查詢效率。 |
Spring Security會在初始化時,從這兩張表中獲得用戶信息和對應權限,將這些信息保存到緩存中。其中users表中的登錄名和密碼用來控制用戶的登錄,而權限表中的信息用來控制用戶登陸后是否有權限訪問受保護的系統資源。
上述sql中,我們創建了兩個用戶admin和user,其中admin擁有ROLE_ADMIN和ROLE_USER權限,而user只擁有ROLE_USER權限。這和我們上一章中的配置相同,因此本章實例的效果也和上一章完全相同,這里就不再贅述了
使用user/user1登錄,成功!
[1] javax.sql.DataSource是一個用來定義數據庫連接池的統一接口。當我們想調用任何實現了javax.sql.DataSource接口的連接池,只需要調用接口提供的getConnection()就可以獲得連接池中的jdbc連接。javax.sql.DataSource可以屏蔽連接池的不同實現,我們使用的連接池即可能由第三方包單獨提供,也可能是由j2ee容器統一管理提供的。
第 3 章 自定義數據庫表結構
Spring Security默認提供的表結構太過簡單了,其實就算默認提供的表結構很復雜,也無法滿足所有企業內部對用戶信息和權限信息管理的要求。基本上每個企業內部都有一套自己的用戶信息管理結構,同時也會有一套對應的權限信息體系,如何讓Spring Security在這些已有的數據結構之上運行呢?
假設我們實際使用的表結構如下所示:
-- 角色 CREATE TABLE role( id BIGINT, NAME VARCHAR(50), descn VARCHAR(200) ); ALTER TABLE role ADD CONSTRAINT pk_role PRIMARY KEY(id); ALTER TABLE role MODIFY id BIGINT AUTO_INCREMENT; -- 用戶 CREATE TABLE USER( id BIGINT, username VARCHAR(50), PASSWORD VARCHAR(50), STATUS INTEGER, descn VARCHAR(200) ); ALTER TABLE USER ADD CONSTRAINT pk_user PRIMARY KEY(id); ALTER TABLE USER MODIFY id BIGINT AUTO_INCREMENT; -- 用戶角色連接表 CREATE TABLE user_role( user_id BIGINT, role_id BIGINT ); ALTER TABLE user_role ADD CONSTRAINT pk_user_role PRIMARY KEY(user_id, role_id); ALTER TABLE user_role ADD CONSTRAINT fk_user_role_user FOREIGN KEY(user_id) REFERENCES USER(id); ALTER TABLE user_role ADD CONSTRAINT fk_user_role_role FOREIGN KEY(role_id) REFERENCES role(id); INSERT INTO USER(id,username,PASSWORD,STATUS,descn) VALUES(1,'admin','admin',1,'管理員'); INSERT INTO USER(id,username,PASSWORD,STATUS,descn) VALUES(2,'user','user',1,'用戶'); INSERT INTO role(id,NAME,descn) VALUES(1,'ROLE_ADMIN','管理員角色'); INSERT INTO role(id,NAME,descn) VALUES(2,'ROLE_USER','用戶角色'); INSERT INTO user_role(user_id,role_id) VALUES(1,1); INSERT INTO user_role(user_id,role_id) VALUES(1,2); INSERT INTO user_role(user_id,role_id) VALUES(2,2);
上述共有三張表,其中user用戶表,role角色表為保存用戶權限數據的主表,user_role為關聯表。user用戶表,role角色表之間為多對多關系,就是說一個用戶可以有多個角色。ER圖如下所示:
現在我們要在這樣的數據結構基礎上使用Spring Security,Spring Security所需要的數據只是為了處理兩種情況,一是判斷登錄用戶是否合法,二是判斷登陸的用戶是否有權限訪問受保護的系統資源。
我們所要做的工作就是在現有數據結構的基礎上,為Spring Security提供這兩種數據。
select username,password,status as enabled from user where username=?
用戶登陸之后,系統需要獲得該用戶的所有權限,根據用戶已被賦予的權限來判斷哪些系統資源可以被用戶訪問,哪些資源不允許用戶訪問。
以下SQL就可以獲得當前用戶所擁有的權限
select u.username,r.name as authority from user u join user_role ur on u.id=ur.user_id join role r on r.id=ur.role_id where u.username=?
將這兩條SQL語句配置到xml中,就可以讓Spring Security從我們自定義的表結構中提取數據了。最終配置文件如下所示:
<authentication-provider> <jdbc-user-service data-source-ref="dataSource" 1users-by-username-query="select username,password,status as enabled from user where username=?" 2authorities-by-username-query="select u.username,r.name as authority from user u join user_role ur on u.id=ur.user_id join role r on r.id=ur.role_id where u.username=?"/> </authentication-provider>
users-by-username-query為根據用戶名查找用戶,系統通過傳入的用戶名查詢當前用戶的登錄名,密碼和是否被禁用這一狀態。 |
|
authorities-by-username-query為根據用戶名查找權限,系統通過傳入的用戶名查詢當前用戶已被授予的所有權限。 |