【JavaEE】SSH+Spring Security整合及example


前文為止,SSH的基本框架都已經搭建出來了,現在,在這基礎上再加上權限控制,也就是Spring Security框架,和前文的順序一樣,先看看需要加哪些庫。

1. pom.xml

Spring Security只需要加上自己的庫就可以,先定義一個版本的屬性:

<properties>
    <spring.version>4.0.4.RELEASE</spring.version>
    <hibernate.version>4.3.5.Final</hibernate.version>
    <spring-security.version>3.2.4.RELEASE</spring-security.version>
</properties>

然后加入spring-security的包:

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-config</artifactId>
    <version>${spring-security.version}</version>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-taglibs</artifactId>
    <version>${spring-security.version}</version>
</dependency>

當然還有spring-security-core和spring-security-web等必須的包,不過都會被作為依賴導入。

2. web.xml

Spring Security是作為過濾器控制權限的,所以,要在web.xml中配置這個過濾器:

<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配置的地方和hibernate的配置一樣,可以加在applicationContext.xml中,但是hibernate要單獨弄一個infrastructure.xml,Security也單獨寫一個配置文件,叫做applicationContext-security.xml,因為這個的名字和前面的非常像,可以合並在一起來配置contextConfigLocation:

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath*:/META-INF/applicationContext*.xml,
                classpath:/META-INF/infrastructure.xml</param-value>
</context-param>

classpath后面的*代表有多個文件,后面的*是一個通配符,所以凡是/META-INF/下所有applicationContext開頭的xml文件都會被讀取。

3. User.java/Role.java

這里先不說applicationContext-security.xml,先來看看需要哪些數據表,首先創建一個權限類Role(org.zhangfc.demo4ssh.domain.Role):

@Entity
@Table
public class Role implements Serializable { private static final long serialVersionUID = -7425304725239042741L; private int id; private String role; @Id public int getId() { return id; } public void setId(int id) { this.id = id; } public String getRole() { return role; } public void setRole(String role) { this.role = role; } }

權限表就兩個字段,id和一個權限名,接着修改User表:

@Entity
@Table
public class User implements Serializable { private static final long serialVersionUID = 172643386440351811L; private int id; private String username; private String password; private Role role; @Id @GeneratedValue public int getId() { return id; } @Size(min=6) public String getUsername() { return username; }public String getPassword() { return password; } @ManyToOne public Role getRole() { return role; } // setter method of id / username / password / role
}

為了節省篇幅,我刪掉了所有的setter方法,這兒增加了兩項,一個id,還有一個多對一的外鍵指向Role表。把properties/hibernate.properties中的hibernate.hbm2dll.auto屬性設置為update,並把HomeController的home方法插入新用戶的代碼注釋掉或刪掉,首先把這個程序運行起來,讓程序創建role表並更新user表,然后編輯數據表(我用的navicat),在role表中加入下面的兩條記錄:

之后修改user表,之前的數據不會被清空,而是新添加了兩個空字段:

這樣就有了兩個可登陸的賬號,下面就來寫一下配置文件。

4. applicationContext-security.xml

先貼下所有的配置再做解釋

<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.2.xsd
                        http://www.springframework.org/schema/security
                        http://www.springframework.org/schema/security/spring-security-3.2.xsd">
 
    <http auto-config="true">
        <intercept-url pattern="/json**" access="ROLE_ADMIN,ROLE_USER" />
        <intercept-url pattern="/admin**" access="ROLE_ADMIN" />
        <form-login 
            login-page="/" 
            default-target-url="/"
            authentication-failure-url="/?login=error" />
            
        <logout logout-success-url="/" />
    </http>
 
    <authentication-manager>
        <authentication-provider>
            <!-- <password-encoder hash="md5">
                <salt-source user-property="email"/>
            </password-encoder> -->
            <jdbc-user-service data-source-ref="dataSource"
                users-by-username-query="select username, password, 1 from user where username = ?" 
                authorities-by-username-query="select u.username, r.role from user u left join role r on u.role_id=r.id where username = ?" 
            />
        </authentication-provider>
    </authentication-manager>
    
</beans:beans>

先看這個authentication-manager,這是一個認證管理器,用戶名密碼的認證就是它來干的,Spring security有一套默認的規則,個人認為也沒太大必要去改這個默認規則,那就是對根目錄下/j_spring_security_check的網絡請求,會被作為登陸請求,並獲取j_username和j_password作為參數進行用戶名密碼的匹配,這時候spring會把j_username交給一個provider,這個provider的任務就是根據這個用戶名返回一個包含用戶名、密碼、權限(權限可以是數組)的對象。在這個地方,我用了spring security自己的authentication-provider,引用前面在配置hibernate的配置的數據源,並分別執行兩條sql語句去根據用戶名查詢密碼、當前用戶是否enable及該用戶權限。

登錄成功之后,所有的網絡請求,都會根據當前用戶的role去最前面的intercept-url上去匹配(json和admin后面的兩個**,第一個代表通配路徑,第二個代表通配子目錄),如果當前用戶的權限是ROLE_USER,但是對應url的access域中沒有ROLE_USER或者IS_AUTHENTICATED_ANONYMOUSLY(表示允許匿名訪問),那么就不能訪問,這時候就會跳轉到下面設置的login-page去讓用戶登錄,當然,登錄成功之前所有的請求也會經過這個鏈,所以千萬不要把登陸界面設置什么訪問權限,要不就死循環了,登錄時如果登錄成功了會跳轉到default-target-url設置的地址,如果登錄失敗了,則會跳到authentication-failure-url上去,現在我是設置的無論怎么樣都跳回根目錄。

如果是注銷,也有一個默認的規則,就是訪問根目錄下的j_spring_security_logout就可以了,注銷之后頁面會跳轉到logout-success-url。

5. index.jsp

我們訪問根目錄會訪問HomeController的home方法,之后進入index.jsp上,所以這里就編輯pages/index.jsp(pages是WEB-INF的同級目錄),加上登錄用的表單:

<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags"%>

<c:set var="base" value="${pageContext.request.contextPath }/" scope="session"/>
<sec:authentication property="principal" var="auth" scope="session" />

<html>
<body>
<h2>Hello World!</h2>
<h2>${auth }</h2>
<form action="${base }j_spring_security_check" method="post">
    用戶名:<input type="text" name="j_username" /><br/>
    密碼:<input type="password" name="j_password" /><br/>
    <input type="submit" value="登錄" />
</form>
<a href="${base }j_spring_security_logout">注銷</a>
</body>
</html>

運行程序,訪問項目根目錄:

我在index.jsp中使用security的標簽獲得了一個叫做principal的對象,這個對象在未登錄的時候是一個字符串,我把它打印了出來,可以看到值是"anonymousUser"。登錄的時候是一個用戶對象(不是我們自己定義的那個domain.User,而是spring的一個UserDetails對象),這個一會登錄之后再看。

用zhangsan/123456登錄,點登錄之后看到,原來打印anonymousUser的地方變成了一個對象的描述:

org.springframework.security.core.userdetails.User@aa9c3074: Username: zhangsan; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_ADMIN

這時候如果把index.jsp里的${auth}改成${auth.username}就可以打印出zhangsan。接下來訪問:http://localhost:8080/demo4ssh-security/json和http://localhost:8080/demo4ssh-security/admin都是可以正確訪問的,因為zhangsan是個ROLE_ADMIN,這兩個目錄都有訪問權限,但是,根據前面的配置ROLE_USER是不能訪問admin的,下面再次進入根目錄,點注銷之后用wangwu/234567登錄(注銷之后如果是打印${auth.username}就會拋異常,因為auth這個對象沒有username這個域了),登錄之后發現http://localhost:8080/demo4ssh-security/json依舊可以正常訪問,http://localhost:8080/demo4ssh-security/admin卻不能訪問了:

Spring Security的基本配置就是這樣了,相比較前面的幾個而言,Spring Security這么基礎的配置基本沒什么大用,現在誰還能用明文存密碼,用加鹽的方式MD5加密是好配置的(我寫的是用email作為鹽,但是出於簡單考慮,我這個demo的user表里沒有沒有email,所以需要用MD5的時候注意一下),applicationContext-security.xml中有一段被我注釋掉的配置就是干這個的,但是,更多的密碼存儲要比這個還要復雜,而且,沒有訪問權限也不能返回這么個界面,還是那句話,我現在只想用最簡單的方式搭出一個能用的框架,至於這些細節,只要基本框架能跑了就是小意思了,這些問題還是留待以后再寫。

源碼下載


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM