Spring Security權限框架理論與簡單Case
Spring Security 提供了基於javaEE的企業應用軟件全面的安全服務。這裏特別強調支持使用Spring框架構件的項目,Spring框架是企業軟件開發javaEE方案的領導者。如果你還沒有使用Spring來開發企業應用程序,我們熱忱的鼓勵你仔細的看一看。熟悉Spring特別是一來註入原理兩幫助你更快更方便的使用Spring Security。
人們使用Spring Secruity的原因有很多,單大部分都發現了javaEE的Servlet規範或EJB規範中的安全功能缺乏典型企業應用場景所需的深度。提到這些規範,重要的是要認識到他們在WAR或EAR級別無法移植。因此如果你更換服務器環境,這裏有典型的大量工作去重新配置你的應用程序員安全到新的目標環境。使用Spring Security 解決了這些問題,也為你提供許多其他有用的,可定制的安全功能。
正如你可能知道的兩個應用程序的兩個主要區域是“認證”和“授權”(或者訪問控制)。這兩個主要區域是Spring Security 的兩個目標。“認證”,是建立一個他聲明的主題的過程(一個“主體”一般是指用戶,設備或一些可以在你的應用程序中執行動作的其他系統)。“授權”指確定一個主體是否允許在你的應用程序執行一個動作的過程。為了抵達需要授權的目的,主體的身份已經有認證過程建立。這個概念是通用的而不只在Spring Security中。
Spring Security主要的組件圖:
在身份驗證層,Spring Security 的支持多種認證模式。這些驗證絕大多數都是要麽由第三方提供,或由相關的標準組織,如互聯網工程任務組開發。另外Spring Security 提供自己的一組認證功能。具體而言,Spring Security 目前支持所有這些技術集成的身份驗證:
- HTTP BASIC 認證頭 (基於 IETF RFC-based 標準)
- HTTP Digest 認證頭 ( IETF RFC-based 標準)
- HTTP X.509 客戶端證書交換 ( IETF RFC-based 標準)
- LDAP (一個非常常見的方法來跨平臺認證需要, 尤其是在大型環境)
- Form-based authentication (用於簡單的用戶界面)
- OpenID 認證
- Authentication based on pre-established request headers (such as Computer Associates Siteminder) 根據預先建立的請求有進行驗證
- JA-SIG Central Authentication Service (CAS,一個開源的SSO系統 )
- Transparent authentication context propagation for Remote Method Invocation (RMI) and HttpInvoker (Spring遠程協議)
- Automatic "remember-me" authentication (你可以勾選一個框以避免預定的時間段再認證)
- Anonymous authentication (讓每一個未經驗證的訪問自動假設為一個特定的安全標識)
- Run-as authentication (在一個訪問應該使用不同的安全標識時非常有用)
- Java Authentication and Authorization Service (JAAS)
- JEE container autentication (所以如果願你以可以任然使用容器管理的認證)
- Kerberos
- Java Open Source Single Sign On (JOSSO) *
- OpenNMS Network Management Platform *
- AppFuse *
- AndroMDA *
- Mule ESB *
- Direct Web Request (DWR) *
- Grails *
- Tapestry *
- JTrac *
- Jasypt *
- Roller *
- Elastic Path *
- Atlassian Crowd *
- Your own authentication systems (see below)
Spring Security5.x官方文檔地址如下:
https://docs.spring.io/spring-security/site/docs/5.0.7.RELEASE/reference/htmlsingle/
https://docs.spring.io/spring-security/site/docs/5.0.7.RELEASE/api/
Spring Security常用的11個權限攔截器
SecurityContextPersistenceFilter:
- 這個過濾器位於頂端,是第一個起作用的過濾器
- 驗證用戶session是否存在,存在則放到SecurityContextHolder中,不存在則創建後到SecurityContextHolder中
- 另一個作用是在過濾器執行完畢後清空SecurityContextHolder中的內容
LogoutFilter:
- 在用戶發出註銷請求時,清除用戶的session以及SecurityContextHolder中的內容
AbstractAuthenticationProcessingFilter:
- 處理from表單登錄的過濾器
DefaultLoginPageGeneratingFilter:
- 用來生成一個默認的登錄頁面
BasicAuthenticationFilter:
- 用於Basic驗證
SecurityContextHolderAwareRequestFilter:
- 用於包裝用戶的請求
- 目的是為後續的程序提供一些額外的數據
RememberMeAuthenticationFilter:
- 當用戶cookie中存在RememberMe標記時,會根據標記自動實現用戶登錄,並創建SecurityContext,授予相應的權限
AnonymousAuthenticationFilter:
- 保證操作統一性,當用戶沒有登錄時,默認為用戶分配匿名用戶的權限,可以選擇關閉匿名用戶
ExceptionTranslationFilter:
- 處理FilterSecurityInterceptor中所拋出的異常,然後將請求重定向到相應的頁面,或響應錯誤信息。也就是作為一個處理全局異常的Filter
SessionManagementFilter:
- 用於防禦會話偽造×××,會銷毀用戶的session,並重新生成一個session
FilterSecurityInterceptor:
- 用戶的權限控制都包含在這裏
- 如果用戶未登陸就會拋出用戶未登陸的異常
- 如果用戶已登錄但是沒有訪問當前資源的權限,就會拋出拒絕訪問異常
- 如果用戶已登錄並具有訪問當前資源的權限,則放行
以上就是Spring Security常用的11個權限攔截器,那麽這些攔截器是按什麽樣的順序執行的呢?這就需要先了解一下FilterChainProxy這個過濾器鏈代理類了:
- FilterChainProxy可以按照指定的順序調用一組Filter,使這組Filter既能完成驗證授權的本職工作,又能享用Spring IOC的功能,來方便的得到其他依賴的資源
基於SpringBoot的SpringSecurity環境快速搭建
打開IDEA,創建一個SpringBoot項目:
勾選相應的模塊:
在項目中新建一個config包,在該包下創建 SpringSecurityConfig 配置類,用於配置Spring Security的攔截規則。代碼如下:
package org.zero.security.securitydemo.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
/**
* @program: security-demo
* @description: Spring Security 配置類
* @author: 01
* @create: 2018-08-29 23:20
**/
@Configuration
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// 定義一個簡單的訪問規則
http.authorizeRequests()
.antMatchers("/").permitAll() // 允許任意訪問根路徑
.anyRequest().authenticated() // 除根路徑以外的請求都需要驗證
.and().logout().permitAll() // 允許任意訪問註銷路徑
.and().formLogin(); // 允許表單登錄
// 禁用默認的csrf驗證
http.csrf().disable();
}
@Override
public void configure(WebSecurity web) throws Exception {
// 不攔截靜態資源的訪問
web.ignoring().antMatchers("/js/**", "/css/**", "/images/**");
}
}
然後新建一個controller包,在該包中創建 DemoController 控制器類,用於開啟一些接口進行測試。代碼如下:
package org.zero.security.securitydemo.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @program: security-demo
* @description: spring security demo
* @author: 01
* @create: 2018-08-29 23:14
**/
@RestController
public class DemoController {
@GetMapping("/")
public String demo(){
return "Hello Spring Security";
}
@GetMapping("/hello")
public String hello(){
return "Hello World";
}
}
啟動項目,訪問根目錄,正常輸出了相應的字符串:
而訪問/hello
,就會跳轉到登錄頁面,需要進行驗證,這就代表SpringSecurity的配置:
基於SpringSecurity權限管理Case實操
Case1、簡單的登錄:
SpringSecurity自帶有一套基於內存的驗證,這樣我們僅需要實現簡單的登錄功能的時候,就不需要額外去創建數據庫了。在 SpringSecurityConfig 類中,加入如下方法:
@Configuration
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication(). // 使用基於內存的認證方式
passwordEncoder(new BCryptPasswordEncoder()). // 設置密碼的加密方式
withUser("admin"). // 設置用戶名稱
password(new BCryptPasswordEncoder().encode("123456")). // 設置密碼
roles("ADMIN"); // 自定義該用戶的角色
}
...
}
重啟項目,當訪問受控制的資源時,就會跳轉到如下登錄頁面,輸入設定好的用戶名和密碼:
訪問成功:
訪問logout接口可以退出登錄:
Case2、有指定的角色,每個角色有指定的權限:
即便是簡單的登錄,也可能會遇到有一些資源需要管理員角色才能訪問。所以我們來看看如何限定一個資源只能被管理員用戶訪問。在configure方法中,增加一個普通用戶,代碼如下:
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().
passwordEncoder(new BCryptPasswordEncoder()).
withUser("admin").
password(new BCryptPasswordEncoder().encode("123456")).
roles("ADMIN");
auth.inMemoryAuthentication().
passwordEncoder(new BCryptPasswordEncoder()).
withUser("user").
password(new BCryptPasswordEncoder().encode("user")).
roles("USER");
}
在DemoController類中增加一個接口,並指定這個接口只能被admin角色的用戶訪問。代碼如下:
@RestController
// 開啟驗證
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class DemoController {
// 指定該接口只能被ADMIN角色的用戶訪問,ROLE_這個前綴是固定要寫的
@PreAuthorize("hasRole(‘ROLE_ADMIN‘)")
@GetMapping("/roleAuth")
public String role(){
return "admin auth";
}
...
}
重啟項目,登錄非admin角色的用戶:
訪問roleAuth接口,會返回403錯誤:
登錄admin用戶,訪問roleAuth接口成功:
@PreAuthorize裏的表達式可以使用 and 、or這種運算符,例如:
@PreAuthorize("hasRole(‘ROLE_ADMIN‘) or hasRole(‘ROLE_ROOT‘)")
除了@PreAuthorize註解外,還有:
- @PostAuthorize:方法執行完後再進行角色驗證
- @PreFilter:方法執行前進行驗證,用於過濾集合類型的參數或返回值
- @PostFilter:方法執行後進行驗證,用於過濾集合類型的參數或返回值
Case3、自定義密碼加密:
我們可以自定義自己的加密方式去做密碼的加密及匹配,我這裏使用MD5作為演示。首先創建一個 MyPasswordEncoder 類並實現 PasswordEncoder 接口。具體代碼如下:
package org.zero.security.securitydemo.encoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.util.DigestUtils;
/**
* @program: security-demo
* @description: 自定義密碼加密器
* @author: 01
* @create: 2018-09-07 21:43
**/
public class MyPasswordEncoder implements PasswordEncoder {
@Override
public String encode(CharSequence charSequence) {
// 使用Spring自帶的工具進行MD5加密
return DigestUtils.md5DigestAsHex(charSequence.toString().getBytes());
}
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
// 驗證密碼加密後是否一致
return encode(rawPassword).equals(encodedPassword);
}
}
使用我們自己自定義的密碼加密器,修改configure方法的代碼如下:
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().
passwordEncoder(new MyPasswordEncoder()).
withUser("admin").
password(new MyPasswordEncoder().encode("123456")).
roles("ADMIN");
auth.inMemoryAuthentication().
passwordEncoder(new MyPasswordEncoder()).
withUser("user").
password(new MyPasswordEncoder().encode("user")).
roles("USER");
}
Case4、參數驗證:
通過@PreAuthorize
註解,我們可以在方法執行前,進行權限參數的驗證。例如我要驗證id小於時,且username參數的值和當前登錄的用戶名一致。代碼如下:
@PreAuthorize("#id<10 and principal.username.equals(#username)")
@GetMapping("/check_info")
public String checkInfo(Integer id, String username) {
return "success";
}
如果參數是一個對象也可以進行校驗,代碼如下:
@PreAuthorize("#user.username.equals(‘admin‘)")
@GetMapping("/check_user")
public String checkUser(User user) {
return "success";
}
總結
優點:
- 提供了一套安全框架,而且這個框架是可以用的
- 提供了很多用戶認證功能,實現相關接口即可,節約大量開發工作
- 基於Spring,使得它易於集成到Spring項目中,且封裝了許多方法
缺點:
- 配置文件多,角色被 “編碼” 到配置文件或源文件中,RBAC不明顯
- 對於系統中用戶、角色、權限之間的關系,沒有可操作的界面
- 大數據量的情況下,幾乎不可用
Spring Security權限框架理論與簡單Case