Spring Security的基本組件SecurityContextHolder詳解
Spring Security 中最基本的組件應該是SecurityContextHolder了。
這是一個工具類,只提供一些靜態(tài)方法。這個工具類的目的是用來保存應用程序中當前使用人的安全上下文。
SecurityContextHolder的工作原理
缺省工作模式MODE_THREADLOCAL
我們知道,一個應用同時可能有多個使用者,每個使用者對應不同的安全上下文,那么SecurityContextHolder是怎么保存這些安全上下文的呢 ?
缺省情況下,SecurityContextHolder使用了ThreadLocal機制來保存每個使用者的安全上下文。
這意味著,只要針對某個使用者的邏輯執(zhí)行都是在同一個線程中進行,即使不在各個方法之間以參數(shù)的形式傳遞其安全上下文,各個方法也能通過SecurityContextHolder工具獲取到該安全上下文。
只要在處理完當前使用者的請求之后注意清除ThreadLocal中的安全上下文,這種使用ThreadLocal的方式是很安全的。當然在Spring Security中,這些工作已經(jīng)被Spring Security自動處理,開發(fā)人員不用擔心這一點。
這里提到的SecurityContextHolder基于ThreadLocal的工作方式天然很適合Servlet Web應用,因為缺省情況下根據(jù)Servlet規(guī)范,一個Servlet request的處理不管經(jīng)歷了多少個Filter,自始至終都由同一個線程來完成。
注意 :
這里講的是一個Servlet request的處理不管經(jīng)歷了多少個Filter,自始至終都由同一個線程來完成;而對于同一個使用者的不同Servlet request,它們在服務端被處理時,使用的可不一定是同一個線程(存在由同一個線程處理的可能性但不確保)。
其他工作模式
有一些應用并不適合使用ThreadLocal模式,那么還能不能使用SecurityContextHolder了呢?
答案是可以的。SecurityContextHolder還提供了其他工作模式。
比如有些應用,像Java Swing客戶端應用,它就可能希望JVM中所有的線程使用同一個安全上下文。此時我們可以在啟動階段將SecurityContextHolder配置成全局策略MODE_GLOBAL。
還有其他的一些應用會有自己的線程創(chuàng)建,并且希望這些新建線程也能使用創(chuàng)建者的安全上下文。
這種效果,可以通過將SecurityContextHolder配置成MODE_INHERITABLETHREADLOCAL策略達到。
使用SecurityContextHolder
獲取當前用戶信息
在SecurityContextHolder中保存的是當前訪問者的信息。
Spring Security使用一個Authentication對象來表示這個信息。
一般情況下,我們都不需要創(chuàng)建這個對象,在登錄過程中,Spring Security已經(jīng)創(chuàng)建了該對象并幫我們放到了SecurityContextHolder中。
從SecurityContextHolder中獲取這個對象也是很簡單的。比如,獲取當前登錄用戶的用戶名,可以這樣 :
// 獲取安全上下文對象,就是那個保存在 ThreadLocal 里面的安全上下文對象
// 總是不為null(如果不存在,則創(chuàng)建一個authentication屬性為null的empty安全上下文對象)
SecurityContext securityContext = SecurityContextHolder.getContext();
// 獲取當前認證了的 principal(當事人),或者 request token (令牌)
// 如果沒有認證,會是 null,該例子是認證之后的情況
Authentication authentication = securityContext.getAuthentication()
// 獲取當事人信息對象,返回結(jié)果是 Object 類型,但實際上可以是應用程序自定義的帶有更多應用相關信息的某個類型。
// 很多情況下,該對象是 Spring Security 核心接口 UserDetails 的一個實現(xiàn)類,你可以把 UserDetails 想像
// 成我們數(shù)據(jù)庫中保存的一個用戶信息到 SecurityContextHolder 中 Spring Security 需要的用戶信息格式的
// 一個適配器。
Object principal = authentication.getPrincipal();
if (principal instanceof UserDetails) {
String username = ((UserDetails)principal).getUsername();
} else {
String username = principal.toString();
}
修改SecurityContextHolder的工作模式
綜上所述,SecurityContextHolder可以工作在以下三種模式之一:
MODE_THREADLOCAL(缺省工作模式)MODE_GLOBALMODE_INHERITABLETHREADLOCAL
???修改SecurityContextHolder的工作模式有兩種方法 :
SecurityContextHolder會自動從該系統(tǒng)屬性中嘗試獲取被設定的工作模式
程序化方式主動設置工作模式的方法
- 設置一個系統(tǒng)屬性(
system.properties) :spring.security.strategy; - 調(diào)用
SecurityContextHolder靜態(tài)方法setStrategyName()
SecurityContextHolder源碼
本文源代碼基于 Spring Security Core 4.x.x
package org.springframework.security.core.context;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import java.lang.reflect.Constructor;
/**
* 將一個給定的SecurityContext綁定到當前執(zhí)行線程。
*
* This class provides a series of static methods that delegate to an instance of
* org.springframework.security.core.context.SecurityContextHolderStrategy. The
* purpose of the class is to provide a convenient way to specify the strategy that should
* be used for a given JVM. This is a JVM-wide setting, since everything in this class is
* static to facilitate ease of use in calling code.
*
* To specify which strategy should be used, you must provide a mode setting. A mode
* setting is one of the three valid MODE_ settings defined as
* static final fields, or a fully qualified classname to a concrete
* implementation of
* org.springframework.security.core.context.SecurityContextHolderStrategy that
* provides a public no-argument constructor.
*
* There are two ways to specify the desired strategy mode String. The first
* is to specify it via the system property keyed on #SYSTEM_PROPERTY. The second
* is to call #setStrategyName(String) before using the class. If neither approach
* is used, the class will default to using #MODE_THREADLOCAL, which is backwards
* compatible, has fewer JVM incompatibilities and is appropriate on servers (whereas
* #MODE_GLOBAL is definitely inappropriate for server use).
*
* @author Ben Alex
*
*/
public class SecurityContextHolder {
// ~ Static fields/initializers
// =====================================================================================
// 三種工作模式的定義,每種工作模式對應一種策略
public static final String MODE_THREADLOCAL = "MODE_THREADLOCAL";
public static final String MODE_INHERITABLETHREADLOCAL = "MODE_INHERITABLETHREADLOCAL";
public static final String MODE_GLOBAL = "MODE_GLOBAL";
// 類加載時首先嘗試從環(huán)境屬性中獲取所指定的工作模式
public static final String SYSTEM_PROPERTY = "spring.security.strategy";
private static String strategyName = System.getProperty(SYSTEM_PROPERTY);
private static SecurityContextHolderStrategy strategy;
// 初始化計數(shù)器,初始為0,
// 1. 類加載過程中會被初始化一次,此值變?yōu)?
// 2. 此后每次調(diào)用 setStrategyName 會對新的策略對象執(zhí)行一次初始化,相應的該值會增1
private static int initializeCount = 0;
static {
initialize();
}
// ~ Methods
// =====================================================================================
/**
* Explicitly clears the context value from the current thread.
*/
public static void clearContext() {
strategy.clearContext();
}
/**
* Obtain the current SecurityContext.
*
* @return the security context (never null)
*/
public static SecurityContext getContext() {
return strategy.getContext();
}
/**
* Primarily for troubleshooting purposes, this method shows how many times the class
* has re-initialized its SecurityContextHolderStrategy.
*
* @return the count (should be one unless you've called
* #setStrategyName(String) to switch to an alternate strategy.
*/
public static int getInitializeCount() {
return initializeCount;
}
private static void initialize() {
if (!StringUtils.hasText(strategyName)) {
// Set default, 設置缺省工作模式/策略 MODE_THREADLOCAL
strategyName = MODE_THREADLOCAL;
}
if (strategyName.equals(MODE_THREADLOCAL)) {
strategy = new ThreadLocalSecurityContextHolderStrategy();
}
else if (strategyName.equals(MODE_INHERITABLETHREADLOCAL)) {
strategy = new InheritableThreadLocalSecurityContextHolderStrategy();
}
else if (strategyName.equals(MODE_GLOBAL)) {
strategy = new GlobalSecurityContextHolderStrategy();
}
else {
// Try to load a custom strategy
try {
Class<?> clazz = Class.forName(strategyName);
Constructor<?> customStrategy = clazz.getConstructor();
strategy = (SecurityContextHolderStrategy) customStrategy.newInstance();
}
catch (Exception ex) {
ReflectionUtils.handleReflectionException(ex);
}
}
initializeCount++;
}
/**
* Associates a new SecurityContext with the current thread of execution.
*
* @param context the new SecurityContext (may not be null)
*/
public static void setContext(SecurityContext context) {
strategy.setContext(context);
}
/**
* Changes the preferred strategy. Do NOT call this method more than once for
* a given JVM, as it will re-initialize the strategy and adversely affect any
* existing threads using the old strategy.
*
* @param strategyName the fully qualified class name of the strategy that should be
* used.
*/
public static void setStrategyName(String strategyName) {
SecurityContextHolder.strategyName = strategyName;
initialize();
}
/**
* Allows retrieval of the context strategy. See SEC-1188.
*
* @return the configured strategy for storing the security context.
*/
public static SecurityContextHolderStrategy getContextHolderStrategy() {
return strategy;
}
/**
* Delegates the creation of a new, empty context to the configured strategy.
*/
public static SecurityContext createEmptyContext() {
return strategy.createEmptyContext();
}
public String toString() {
return "SecurityContextHolder[strategy='" + strategyName + "'; initializeCount="
+ initializeCount + "]";
}
}
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
java實用型-高并發(fā)下RestTemplate的正確使用說明
這篇文章主要介紹了java實用型-高并發(fā)下RestTemplate的正確使用說明,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-10-10
springboot實現(xiàn)學生管理系統(tǒng)
這篇文章主要為大家詳細介紹了springboot實現(xiàn)學生管理系統(tǒng),文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-07-07
在JDK和Eclipse下如何編寫和運行Java Applet
本文主要介紹了在JDK和Eclipse的環(huán)境下如何編寫和運行Java Applet,圖文方式,適合初學者學習。2015-09-09
在SpringBoot項目中優(yōu)雅的連接多臺Redis的操作方法
本文將基于一個實際案例,詳細介紹如何在Spring Boot中優(yōu)雅地配置和使用多個Redis實例,解決常見的注入歧義問題,并提供一個通用的Redis工具類來簡化操作,本文給大家介紹的非常詳細,感興趣的朋友一起看看吧2025-09-09

