spring security

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 頁面上了。
image
隨即會發現,當輸錯密碼之後,沒有錯誤的提示,怎麼辦??這時,可以藉助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>

 image

對發生的錯誤,還可以自定義輸出的提示信息,這裏可以參考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);

 

 

模型如下:

image

插入數據:

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&amp;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>
      配置完成之後,看下各種效果,如下:
image image image
修改基本滿足了我們要求,可 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>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章