使用Spring Method Security保護資料安全
1. 前言
大部分開發者導入Spring Security想必是因為它有許多強大且多元的驗證機制,並可能經常使用到頁面層級(Page-Level)的身分驗證,例如:可透過設定權限(一般使用者/系統管理員)來決定該段網址的請求是否被允許。
但是,若有細節尚未設定完全,則可能會被有心人士透過特殊方式破解權限進入該網址,進而取得防護層後方的資料。
而針對此問題 Spring Security 提供多個方法層級(Method-Level)的驗證以及資料過濾機制,能讓安全性防護能夠更加穩固,只要透過Annotation方法設定於Class Method上,即可簡單地使用。
目前Spring官方推薦使用,於方法層級的驗證有兩種設定方式:
@PreAuthorize :
於進入設定的Method 前 執行身分驗證動作,驗證成功則正常執行,若驗證失敗則底層拋出exception 以便開發者可定義後續流程。
@PostAuthorize :
於進入設定的Method 後 執行身分驗證動作,可針對回傳結果資料加上條件判斷,驗證成功則正常執行,若驗證失敗則底層拋出exception 。
於方法層級篩選資料使用的過濾器也是有兩種設定方式:
@PreFilter :
於進入設定的Method 前 執行資料篩選動作,可指定資料過濾條件及使用者權限,決定進入流程中的資料內容,只能針對一個集合參數(Collection) 做過濾條件設定。
@PostFilter :
於進入設定的Method 後 執行資料篩選動作,可指定資料過濾條件及使用者權限,在返回流程的資料集合(Collection)中移除特定資料。
2.目的
本文將藉由實作畫面,從Spring Boot 工具建立Spring Security專案、載入相關套件、服務邏輯建立、透過JSP查詢和修改功能畫面,以及四種簡單的情境,來說明如何使用 Method Security 相關 Annotation ,並於類別方法上加入設定,以達到安全性驗證和資料過濾功能。
3.實作前準備
3-1.新增一個Spring Starter Project
3-2.勾選Spring Security與Spring Web
3-3.載入相關Maven dependency
專案建立完成後,打開maven pom.xml檔案,並加入相關library ,其中本文章實作的部份會參考spring-security-core-5.2.1.RELEASE.jar
而它會參照 spring-boot-starter-security 這個 dependency 設定,所以必須要確認是否有被引入至 maven repository 中;
而其他設定還需要 spring-security-web 以及邏輯處理有用到spring core 、網頁JSP相關呈現,這邊也須載入spring-security-web 、
spring-core 以及JSP相關lib
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/>
</parent>
<groupId>com.owen.demo</groupId>
<artifactId>SpringSecurityMethodDemo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>SpringSecurityMethodDemo</name>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-taglibs</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<!-- JSTL -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
<!-- Need this to compile JSP -->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<scope>provided</scope>
</dependency>
<!-- Need this to compile JSP -->
<dependency>
<groupId>org.eclipse.jdt.core.compiler</groupId>
<artifactId>ecj</artifactId>
<version>4.6.1</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
3-4.設定 Security 相關Configuration 檔案
需繼承Spring 提供之 WebSecurityConfigurerAdapter 抽象類別,其中需注意的部分如下圖紅框處
@EnableGlobalMethodSecurity(prePostEnabled = true) 設定必須定義打開,才可使用本實作中方法層級的驗證
並於同檔案設定網站中可驗證登入的使用者(user/admin)、密碼、角色
以及定義使用者驗證登入前後、登入成功/失敗導向頁面設定
3-5.定義WEB MVC view 部分設定,使用JstlView 依照回傳字串組合前後綴詞找到指定JSP頁面並呈現
3-6.建立Controller 程式,定義頁面入口網址:/loginPage 以及登入成功後轉跳網址: /list 其中/list 入口為查詢員工列表清單,
如同上方3-1.所提到,可以允許訪問的權限有 ROLE_USER 或是 ROLE_ADMIN
定義修改資料頁面入口網址: /editEmp 這邊各別使用Http GET 與 POST 來區分進入修改資料頁面,或送出修改資料頁面
3-7.建立需要存取的資料物件Employee
3-8.建立查詢/更新資料的Interface EmployeeService 以及實作類別 EmployeeServiceImpl
3-9.建立相關頁面JSP (登入 , 登入失敗 , 尚未授權 ,員工列表 , 修改員工資料 , 修改成功 等頁面 , 這邊只列出檔案清單)
4.實際情境模擬並驗證測試
4-1. 情境一:使用@PreAuthorize 設定頁面訪問權限
經過上方準備步驟後,我們即可使用Spring Boot 啟動內建tomcat 執行測試網頁。
首先使用一般使用者 user 登入
登入可以看到員工名單頁面
按下修改連結後可以進入修改頁面
但其實一般使用者需定義不能進入修改資料才對(或是只能修改相同權限之資料),所以我們試著加入@PreAuthorize 設定於Controller程式中,於修改員工資料的入口 /editEmp上,將權限角色判斷為ADMIN時才允許通過驗證,阻擋一般使用者訪問修改資料的頁面
修改後重新啟動Spring Boot,再次使用user登入並進入修改頁,則會發現雖然url轉跳仍然是editEmp 但實際上已經被Spring Security攔截,並轉跳至尚未授權之頁面
4-2.情境二:使用@PostAuthorize 設定更新頁面資料存取權限
本次設定將Annotation放置於Service層Method上,@PostAuthorize 設定用來攔截執行該Method後返回物件資料前,可以用來判斷該資料並限制存取權限。如下圖: 設定該方法(更新員工資料前查詢)存取權限為ADMIN角色 或是 返回物件中權限欄位資料為該使用者,意指ADMIN角色可以修改全部員工資料,而一般使用者只能修改user權限之員工資料
首先使用user帳號做登入並測試
於第一筆權限為admin的員工資料並點選修改連結,則會被導頁至尚未授權頁面
點選第三筆權限為user的員工資料並點選修改連結,則可以正常進入修改畫面
接下來使用admin帳號做登入並測試,點選第一筆權限為admin的員工資料並點選修改連結,則可以正常進入修改頁面
點選第三筆權限為user的員工資料並點選修改連結,也可以正常進入修改頁面
4-3.情境三:使用@PreFilter 設定篩選新增特定條件資料
@PreFilter 設定代表可以攔截執行該Method所傳入參數(必須為Collection集合),並賦予權限或篩選特定條件。
首先於Controller新增一入口功能為一次建立多筆員工資料Method , 固定新增三筆員工資料(其中1位admin權限、2位user權限)
於Service也加入一次建立多筆員工資料的Method,並在上方加入@PreFilter 設定。如下圖: 設定規則為允許ADMIN權限 或 沒有ADMIN權限的人但可以新增非特定條件的物件資料;意指如果使用者admin執行此功能會一次新增三筆記錄,使用者user執行會是傳入篩選特定條件後的2筆非admin資料
使用admin登入並點選員工名單頁新增的功能連結:一次建立多筆員工資料
點選後跳轉回員工名單,加入的資料為下方三筆
接著使用user登入並點選新增連結
點選後跳轉回員工名單,加入的資料為下方兩筆,發現權限為admin的員工資料並未被加入名單中,表示上述設定的規則成立
4-4.情境四:使用@PostFilter 設定篩選回傳特定條件資料
@PostFilter 設定代表可以攔截執行該Method之後所回傳內容(必須為Collection集合),並賦予權限或篩選特定條件。
於Service層查詢員工名單列表Method上加入此設定。如下圖: 規則為允許ADMIN權限 或 沒有ADMIN權限的人但只能撈取特定條件的物件資料;
意指如果使用者admin執行此功能會查詢出全部資料,使用者user查詢會只顯示篩選特定條件後的2筆user資料
使用admin登入則會看見全部的員工名單資料
使用user登入則發現員工權限為admin的資料則不顯示
5.相關資料參考
Spring 官方文件 - https://www.baeldung.com/spring-security-method-security
API範例 -