Spring Security 爲基於 J2EE 企業應用軟件提供了全面安全服務。安全主要包括兩個操作“認證”與“驗證”(有時候也會叫做權限控制)。“認證”是爲用戶建立一個其聲明的角色的過程,這個角色可以一個用戶、一個設備或者一個系統。“驗證”指的是一個用戶在你的應用中能夠執行某個操作。在到達授權判斷之前,角色已經在身份認證過程中建立了。
1.常用的權限控制方法
搜索網絡與整理相關的文檔,主要有以下幾個方法來使用 spring security 進行權限控制:
1)全部利用配置文件,將用戶、權限、資源硬編碼在 xml 文件中。
2)用戶和權限用數據庫存儲,而資源(url)和權限的對應採用硬編碼配置。
3)細分用戶和權限,並將用戶、角色、權限和資源均採用數據庫存儲,並且自定義過濾器,代替原有的FilterSecurityInterceptor過濾器,並分別實現AccessDecisionManager、InvocationSecurityMetadataSource 和 UserDetailsService,並在配置文件中進行相應的配置。
附:InvocationSecurityMetadataSource 將配置文件或數據庫中存儲的資源 url 提取出來加工成 url 和 權限列表的 Map 供 Security 使用,UserDetailsService 是提取用戶名和權限組成一個完整的(UserDetails) User 對象,該對象可以提供用戶的詳細信息,供 AuthentationManager 進行認證與授權使用。
2.配置
spring security 可以通過簡單的配置,以聲明式的方法加強應用的 url 訪問安全。它向 http 請求應用 servlet 過濾器來安全問題。你可以使用 spring security schema 中定義的 xml 元素 在 spring 的bean 配置文件中配置這些過濾器。同時,由於servlet 過濾器 必須在 web 部署描述符中註冊才能生效,所以你必須在 web.xml 中註冊一個 DelegatingFilerProxy 示例,這個 servlet 過濾器會將請求委派給spring 應用上下文中一個過濾器。
DelegatingFilterProxy所做的事情是代理Filter的方法,從application context裏獲得bean(這些bean就是Spring Security 中的核心部分,過濾器。這些過濾器被定義在了Spring容器中)。 這讓bean可以獲得spring web application context的生命週期支持,使配置較爲輕便。 bean必須實現javax.servlet.Filter接口,它必須和filter-name裏定義的名稱是一樣的。
<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 的配置文件中引入 spring security schema 命名空間,這需要修改原來的application.xml 文件,如下:<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:security="http://www.springframework.org/schema/security"
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">
<!-- 相關配置 -->
</beans>在實際項目的時候,爲了區分各種配置,常常會將安全配置單獨放到一個文件裏(如application-security.xml),同時會使用另外一種命名空間的形式(beans),如下:<?xml version="1.0" encoding="UTF-8"?>
<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-3.1.xsd">
<!-- 相關配置 -->
</beans:beans>接着往下的介紹,我們都使用 beans 命名空間.往下講解,將從最簡單的開始.首先spring security 允許通過 <http> 元素配置 web 應用安全性。假設你的 web 應用的安全需求是典型的,可以將改元素的 autoconfig 屬性設置爲 true ,這樣 spring security 將自動註冊和配置一下幾個基本的安全服務。
- 基於表單的登錄服務:提供包含用戶應用登錄表單的默認頁面。
- 註銷服務:提供一個映射到用於用戶退出應用的 url 的處理程序。
- http 基本驗證:處理 http 請求頭目標中存在的基本驗證憑據,還能用於遠程協議的 web 服務發出的驗證請求。
- 匿名登錄:爲匿名用戶指派一個角色並授予權限,可以將匿名用戶作爲常規用戶處理。
- Remember-me 支持:在多個瀏覽器會話中記憶用戶的身份,通常在用戶瀏覽器中存儲一個 Cookie 來實現。
- Servlet API 集成:允許通過標準 Servlet API 如 HttpServletRequest.isUserInRole() 和 HttpServletRequest.getUserPrincipal(),訪問 web 應用的安全信息。
註冊了這些安全服務,就可以指定需要特殊權限才能訪問的URL 模式。spring security 將根據你的配置進行安全檢查。用戶在訪問安全的 url 之前必須登錄到應用,除非這些 url 開放給匿名訪問。
配置如下:
<http auto-config='true'><intercept-url pattern="/**" access="ROLE_USER" /></http>這中設置表示我們會保護所有 url,只用擁有ROLE_USER 角色(權限)的用戶才能訪問。
<http> 元素所有 web 相關的命名空間的上級元素。在<intercept-url> 元素定義了pattern ,用來匹配進入請求的url,access 定義可訪問此 pattern 下url 的角色,這個一般都是一個逗號分隔的角色隊列。前綴“ROLE_”表示一個用戶應該擁有的權限比對。
上面配置中表示只有 ROLE_USER 才能訪問,那 ROLE_USER 應該在哪裏定義呢??下面的工作就是定義 ROLE_USER:
<!-- 配置認證管理器 -->
<authentication-manager><authentication-provider><user-service><user name="user" password="123456" authorities="ROLE_USER" /></user-service></authentication-provider></authentication-manager>如果用戶名爲user,密碼爲123456的用戶成功登錄了,它的角色是ROLE_USER ,那麼他可以訪問指定的資源。到此已經算是基本完成了,爲了測試,可以自己添加一個首頁,在訪問的時候,會重定向到一個登陸的頁面(由 spring security 框架裏過濾器DefaultLoginPageGeneratingFilter產生的)。
3、進階1
在上面的例子裏,簡單的演示了一下如何使用 spring security,不過其離實際使用還遠着。在實際開發的時候,我們需要自定義自己的登錄頁面,同時用戶名和密碼不是配置在xml 文件裏,而是寫在在數據庫裏。
第一步:指定登錄頁面
由於配置是spring security 自動完成驗證,所以我們需要遵循spring 在登錄表單頁面的 name,可以查看一個 spring security 的登錄頁面,如下:
<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><title>登錄頁面</title></head><body><h3>用戶登錄</h3><form action="/springAuthority/j_spring_security_check" method="post">user:<input type="text" name="j_username"/><br />password:<input type="password" name="j_password" /><input type="submit" value="登錄" /></form></body></html>
我們仿照來寫一個,如下:
<%@ 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><h3>用戶登錄</h3><form action="${pageContext.request.contextPath}/j_spring_security_check" method="post">user:<input type="text" name="j_username"/><br />password:<input type="password" name="j_password" /><input type="submit" value="登錄" /></form></body></html>
寫完登錄頁面之後,需要讓 spring security 知道哪一個是登錄頁面,所以得在配置文件裏進行配置,如下:
<http auto-config="true"><form-login login-page="/login.jsp" /> <!-- 指定登錄頁面 --><intercept-url pattern="/**" access="ROLE_USER" /></http>指定登錄頁面之後,還需要設置不攔截登錄頁面(“/**”表示攔截所有頁面),配置如下:
<!-- 不攔截登錄頁面,至於爲什麼加一個 *,是因爲請求這個頁面的時候可能會帶有一些參數 -->
<http pattern="/login.jsp*" security="none" /><http auto-config="true"><form-login login-page="/login.jsp" /> <!-- 指定登錄頁面 --><intercept-url pattern="/**" access="ROLE_USER" /></http>啓動瀏覽器訪問 index.jsp,會發現自動跳轉到 login.jsp 頁面上了。隨即會發現,當輸錯密碼之後,沒有錯誤的提示,怎麼辦??這時,可以藉助spring security 的國際化輸出,spring security 框架將所有的錯誤信息都定義成異常,並提供國際化的資源文件,這個資源文件在spring-security-core-xxx.jar文件中。這時我們需要配置文件中指定使用的資源文件,如下:<!-- 這裏定義的messageSource對象供spring security 框架輸出異常信息 -->
<bean id="messageSource"class="org.springframework.context.support.ReloadableResourceBundleMessageSource"><property name="basename" value="classpath:/org/springframework/security/messages_zh_CN" /></bean>
指定了資源文件之後,我們還需要在jsp文件裏輸出Spring Security 框架將拋出的異常對象放到了session範圍中,key是:SPRING_SECURITY_LAST_EXCEPTION,取出的是異常對象,所以還要調用getMessage()方法取出真正的錯誤信息。如下:
<h3>用戶登錄</h3>${sessionScope.SPRING_SECURITY_LAST_EXCEPTION.message} <!-- 輸出異常信息 -->
<form action="${pageContext.request.contextPath}/j_spring_security_check" method="post">user:<input type="text" name="j_username"/><br />password:<input type="password" name="j_password" /><input type="submit" value="登錄" /></form>
對發生的錯誤,還可以自定義輸出的提示信息,這裏可以參考spring security 原有的 messages_zh_CN.properties 來定義輸出的異常信息,範例如下,在 src 下定義個 創建一個 messages_zh_CN.properties 文件,指定輸出的異常信息,如下:
#你無權訪問該資源,請登錄AbstractUserDetailsAuthenticationProvider.badCredentials=\u4F60\u65E0\u6743\u8BBF\u95EE\u8BE5\u8D44\u6E90\uFF0C\u8BF7\u767B\u5F55
第二步:將權限保存到數據中
前面時間用戶名和密碼定義在 xml 配置文件中,那又該如何將這些信息存進數據庫裏呢??同樣地,從簡單談起,spring security 將表結構已經定義好了,可以參考發行文檔的附錄 A,也可以點擊 這裏 到官網查看。
<!-- 用戶表 -->
create table users(username varchar_ignorecase(50) not null primary key,password varchar_ignorecase(50) not null,enabled boolean not null); <!-- 是否禁用 -->
<!-- 權限表 -->
create table authorities (username varchar_ignorecase(50) not null,authority varchar_ignorecase(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','123456',1),('user','123456',1),('user2','123456',0);
INSERT INTO authorities VALUES('admin','ROLE_ADMIN'),('user','ROLE_USER'),('user2','ROLE_USER');
接着在 spring 的配置文件裏配置數據源,且需要加入數據庫驅動(這裏使用 mysql),如下:
<!-- 配置數據源 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"><property name="driverClassName" value="com.mysql.jdbc.Driver" /><property name="username" value="user" /><property name="password" value="root" /><property name="url"value="jdbc:mysql://localhost:3306/car?useUnicode=true&characterEncoding=utf8" /></bean>接着就是將替換掉原來的<user-service>,如下:
<!-- 配置認證管理器 -->
<authentication-manager><authentication-provider><jdbc-user-service data-source-ref="dataSource"/><!-- <user-service> <user name="user" password="123456" authorities="ROLE_USER" /> </user-service> -->
</authentication-provider></authentication-manager>現在再回頭看一下<http>元素(用來配置 web 應用安全)
<!-- 不攔截登錄頁面,至於爲什麼加一個 *,是因爲請求這個頁面的時候可能會帶有一些參數 -->
<http pattern="/login.jsp*" security="none" /><http auto-config="true"><form-login login-page="/login.jsp" /> <!-- 指定登錄頁面 --><intercept-url pattern="/**" access="ROLE_USER" /></http>假設要實現如下功能,
1)除了登錄頁面 login.jsp 可以直接訪問之外,其他頁面都需要權限才能進入。
2)index.jsp 頁面 ROLE_USER 和 ROLE_ADMIN 都可以訪問。
3)admin.jsp 頁面只用 ROLE_ADMIN 纔可以訪問。
配置如下:
<!-- 不攔截登錄頁面,至於爲什麼加一個 *,是因爲請求這個頁面的時候可能會帶有一些參數 -->
<http pattern="/login.jsp*" security="none" /><http auto-config="true"><form-login login-page="/login.jsp" /> <!-- 指定登錄頁面 --><intercept-url pattern="/admin.jsp" access="ROLE_ADMIN"/><intercept-url pattern="/**" access="ROLE_USER,ROLE_ADMIN" /></http>修改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>這是首頁,歡迎你!<br><a href="admin.jsp">訪問admin.jsp</a></body></html>新增admin.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>你好,管理員!!<br></body></html>修改基本滿足了我們要求,可 403 信息不太友好,我們可以選擇自定義 403 頁面,首先還是先新建一個403頁面403.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>403頁面</title></head><body>你的訪問被拒絕,無權訪問該資源<br></body></html>
接着就是在spring 的配置文件裏指定403頁面,如下:
<!-- 不攔截登錄頁面,至於爲什麼加一個 *,是因爲請求這個頁面的時候可能會帶有一些參數 -->
<http pattern="/login.jsp*" security="none" /><http auto-config="true" access-denied-page="/403.jsp"><form-login login-page="/login.jsp" /> <!-- 指定登錄頁面 --><intercept-url pattern="/admin.jsp" access="ROLE_ADMIN"/><intercept-url pattern="/**" access="ROLE_USER,ROLE_ADMIN" /></http>
4.獲取用戶信息
獲取用戶信息,一般來說有兩種方法,一種是通過java 代碼獲取,一種是通過spring security 標籤獲取。
第一種通過 java 代碼獲取。
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();if (principal instanceof UserDetails) {String username = ((UserDetails)principal).getUsername();} else {
String username = principal.toString();}
附加信息:
在spring security裏,指定默認的登錄頁面時,還可以設置一個默認的提交目標。意思就是如果在進行表單登陸之前, 沒有試圖去訪問一個被保護的資源, default-target-url 就會起作 用 。 這 是 用 戶 登 陸 後 會 跳 轉 到 的 URL , 默 認 是 "/" 。 你 也 可 以 把always-use-default-target 屬性配置成"true",這樣用戶就會一直跳轉到這一頁(無論登陸是“跳轉過來的”還是用戶特定進行登陸) 。 如果你的系統一直需要用戶從首頁進入, 就可以使用它了。配置如下:
<http auto-config="true" access-denied-page="/403.jsp"><!-- 當系統一直需要用戶從首頁進入時,可以設置always-use-default-target -->
<form-login login-page="/login.jsp" default-target-url="/index.jsp" always-use-default-target="true"/><intercept-url pattern="/admin.jsp" access="ROLE_ADMIN"/><intercept-url pattern="/**" access="ROLE_USER,ROLE_ADMIN" /></http>