Spring Security:安全上下文持有者SecurityContextHold

家电维修 2023-07-16 19:16www.caominkang.com家电维修技术

SecurityContextHolder

将给定的SecurityContext与当前执行线程相关联,此类提供了一系列委托给SecurityContextHolderStrategy实例的静态方法,此类的目的是提供一种方便的方法来指定应该用于给定JVM的策略,这是JVM范围的设置,因为此类中的所有内容都是static修饰,方便调用。

要指定使用哪种策略,必须提供模式设置,模式设置是定义为static final字段的三个有效设置之一,或者是提供公共无参构造方法的SecurityContextHolderStrategy具体实现的完全限定类名(会通过反射进行创建)。有两种方法可以指定所需的策略模式,第一种是通过键入SYSTEM_PROPERTY的系统属性来指定它,第二种是在使用类之前调用setStrategyName(String)进行设置。如果这两种方法都没有使用,则该类将默认使用MODE_THREADLOCAL,它向后兼容,具有较少的JVM不兼容性并且适用于服务器(而MODE_GLOBAL不适合服务器使用)。

SecurityContextHolder类源码

public class SecurityContextHolder {
 // 三种模式设置,代表三种策略
	public static final String MODE_THREADLOCAL = "MODE_THREADLOCAL";
	public static final String MODE_INHERITABLETHREADLOCAL = "MODE_INHERITABLETHREADLOCAL";
	public static final String MODE_GLOBAL = "MODE_GLOBAL";
	// 系统属性
	public static final String SYSTEM_PROPERTY = "spring.security.strategy";
	// 获取系统属性spring.security.strategy的值
	private static String strategyName = System.getProperty(SYSTEM_PROPERTY);
	// 安全上下文持有策略
	private static SecurityContextHolderStrategy strategy;
	// 初始化SecurityContextHolderStrategy的次数
	private static int initializeCount = 0;

	static {
	 // 初始化
		initialize();
	}

	
	public static void clearContext() {
		strategy.clearContext();
	}

	
	public static SecurityContext getContext() {
		return strategy.getContext();
	}

	
	public static int getInitializeCount() {
		return initializeCount;
	}

 // 初始化方法,私有方法
	private static void initialize() {
	 // 如果strategyName属性没有值
		if (!StringUtils.hasText(strategyName)) {
			// 设置默认值MODE_THREADLOCAL
			strategyName = MODE_THREADLOCAL;
		}
  // 根据strategyName的值,设置strategy属性
  // 即三种模式设置,代表三种策略,默认为ThreadLocalSecurityContextHolderStrategy
		if (strategyName.equals(MODE_THREADLOCAL)) {
			strategy = ne ThreadLocalSecurityContextHolderStrategy();
		}
		else if (strategyName.equals(MODE_INHERITABLETHREADLOCAL)) {
			strategy = ne InheritableThreadLocalSecurityContextHolderStrategy();
		}
		else if (strategyName.equals(MODE_GLOBAL)) {
			strategy = ne GlobalSecurityContextHolderStrategy();
		}
		else {
			// 尝试加载自定义策略
			try {
				Class clazz = Class.forName(strategyName);
				Constructor customStrategy = clazz.getConstructor();
				strategy = (SecurityContextHolderStrategy) customStrategy.neInstance();
			}
			catch (Exception ex) {
				ReflectionUtils.handleReflectionException(ex);
			}
		}
  // 更新initializeCount
		initializeCount++;
	}

	
	public static void setContext(SecurityContext context) {
		strategy.setContext(context);
	}

	
	public static void setStrategyName(String strategyName) {
		SecurityContextHolder.strategyName = strategyName;
		// 调用初始化方法
		initialize();
	}

	
	public static SecurityContextHolderStrategy getContextHolderStrategy() {
		return strategy;
	}

	
	public static SecurityContext createEmptyContext() {
		return strategy.createEmptyContext();
	}

	@Override
	public String toString() {
		return "SecurityContextHolder[strategy='" + strategyName + "'; initializeCount="
				+ initializeCount + "]";
	}
}
SecurityContextHolderStrategy

针对线程存储安全上下文信息的策略,首选策略由SecurityContextHolder加载。

public interface SecurityContextHolderStrategy {

	
	void clearContext();

	
	SecurityContext getContext();

	
	void setContext(SecurityContext context);

	
	SecurityContext createEmptyContext();
}

SecurityContextHolderStrategy接口有三个实现类,如下图所示,分别对应SecurityContextHolder类中的三种模式设置。

ThreadLocalSecurityContextHolderStrategy

基于ThreadLocal的SecurityContextHolderStrategy实现。

final class ThreadLocalSecurityContextHolderStrategy implements
		SecurityContextHolderStrategy {

 // 使用ThreadLocal持有SecurityContext实例
	private static final ThreadLocal contextHolder = ne ThreadLocal<>();

 // 删除ThreadLocal持有的SecurityContext实例
	public void clearContext() {
		contextHolder.remove();
	} 
 // 获取ThreadLocal持有的SecurityContext实例
	public SecurityContext getContext() {
		SecurityContext ctx = contextHolder.get();
  // 如果为null
  // 会创建一个空上下文,并且设置到ThreadLocal中
		if (ctx == null) {
			ctx = createEmptyContext();
			contextHolder.set(ctx);
		}

		return ctx;
	}
 // 将SecurityContext实例设置到ThreadLocal中
	public void setContext(SecurityContext context) {
		Assert.notNull(context, "only non-null SecurityContext instances are permitted");
		contextHolder.set(context);
	}
 // 创建一个空上下文
	public SecurityContext createEmptyContext() {
	 // 调用SecurityContextImpl的无参构造方法,实例的authentication属性为null
		return ne SecurityContextImpl();
	}
}
InheritableThreadLocalSecurityContextHolderStrategy

基于InheritableThreadLocal的SecurityContextHolderStrategy实现。

InheritableThreadLocal类扩展了ThreadLocal类,以提供从父线程到子线程的值继承当创建子线程时,子线程接收父线程具有值的所有可继承线程局部变量的初始值。

final class InheritableThreadLocalSecurityContextHolderStrategy implements
		SecurityContextHolderStrategy {
	// 使用ThreadLocal持有SecurityContext实例,但该ThreadLocal是一个InheritableThreadLocal实例
	private static final ThreadLocal contextHolder = ne InheritableThreadLocal<>();

 // 删除ThreadLocal持有的SecurityContext实例
	public void clearContext() {
		contextHolder.remove();
	}
 // 获取ThreadLocal持有的SecurityContext实例
	public SecurityContext getContext() {
		SecurityContext ctx = contextHolder.get();
  // 如果为null
  // 会创建一个空上下文,并且设置到ThreadLocal中
		if (ctx == null) {
			ctx = createEmptyContext();
			contextHolder.set(ctx);
		}

		return ctx;
	}
 // 将SecurityContext实例设置到ThreadLocal中
	public void setContext(SecurityContext context) {
		Assert.notNull(context, "only non-null SecurityContext instances are permitted");
		contextHolder.set(context);
	}
 // 创建一个空上下文
	public SecurityContext createEmptyContext() {
	 // 调用SecurityContextImpl的无参构造方法,实例的authentication属性为null
		return ne SecurityContextImpl();
	}
}
GlobalSecurityContextHolderStrategy

基于static字段的SecurityContextHolderStrategy实现,这意味着JVM中的所有实例共享相同的SecurityContext。

final class GlobalSecurityContextHolderStrategy implements SecurityContextHolderStrategy {
	// 将SecurityContext实例定义成一个静态属性
	private static SecurityContext contextHolder;
   
 // 清除上下文,即设置contextHolder属性为null
	public void clearContext() {
		contextHolder = null;
	}
 // 获取上下文
	public SecurityContext getContext() {
	 // 如果contextHolder属性为null
	 // 将创建一个空上下文赋值给它
		if (contextHolder == null) {
			contextHolder = ne SecurityContextImpl();
		}

		return contextHolder;
	}
 // 设置上下文
	public void setContext(SecurityContext context) {
		Assert.notNull(context, "only non-null SecurityContext instances are permitted");
		contextHolder = context;
	}
 // 创建一个空上下文
	public SecurityContext createEmptyContext() {
		return ne SecurityContextImpl();
	}
}
Debug分析

项目结构图

pom.xml



 4.0.0

 .kaven
 security
 1.0-SNAPSHOT

 
  .springframeork.boot
  spring-boot-starter-parent
  2.3.6.RELEASE
 

 
  8
  8
 

 
  
   .springframeork.boot
   spring-boot-starter-eb
  
  
   .springframeork.boot
   spring-boot-starter-security
  
  
   .projectlombok
   lombok
  
 


application.yml

spring:
  security:
 user:
   name: kaven
   passord: itkaven
   roles: USER
logging:
  level:
 :
   springframeork:
  security: DEBUG

MessageController(定义接口)

package .kaven.security.controller;

import .springframeork.eb.bind.annotation.GetMapping;
import .springframeork.eb.bind.annotation.RestController;

@RestController
public class MessageController {
 @GetMapping("/message")
 public String getMessage() {
  return "hello spring security";
 }
}

启动类

package .kaven.security;

import .springframeork.boot.SpringApplication;
import .springframeork.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {
 public static void main(String[] args) {
  SpringApplication.run(Application.class);
 }
}

SecurityConfig(Spring Security的配置类,不是必须的,因为有默认的配置)

package .kaven.security.config;

import .springframeork.security.config.Customizer;
import .springframeork.security.config.annotation.eb.builders.HttpSecurity;
import .springframeork.security.config.annotation.eb.configuration.EnableWebSecurity;
import .springframeork.security.config.annotation.eb.configuration.WebSecurityConfigurerAdapter;

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

 @Override
 protected void configure(HttpSecurity http) thros Exception {
  // 任何请求都需要进行验证
  http.authorizeRequests()
    .anyRequest()
    .authenticated()
    .and()
     // 记住身份验证
    .rememberMe(Customizer.ithDefaults())
     // 基于表单登陆的身份验证方式
    .formLogin(Customizer.ithDefaults());
 }
}

Debug方式启动应用,访问http://localhost:8080/message,Spring Security会通过SecurityContextHolder创建空SecurityContextImpl实例(实例的authentication属性为空)。

SecurityContextHolder使用ThreadLocalSecurityContextHolderStrategy实例(默认策略)创建空SecurityContextImpl实例(实例的authentication属性为空)。


然后AnonymousAuthenticationFilter会处理请求(当前是匿名访问资源),该过滤器会设置该空SecurityContextImpl实例的authentication属性为AnonymousAuthenticationToken实例。

而通过身份验证(authenticated属性为true)的AnonymousAuthenticationToken实例(身份验证请求令牌),也是没有权限访问/message接口的,所以请求被拒绝访问了。

之后请求会被重定向到表单登陆页面,需要填写用户名和密码进行身份验证。

点击登陆后,又会创建一个空SecurityContextImpl实例(实例的authentication属性为空)。

之后会设置该空SecurityContextImpl实例的authentication属性为UsernamePassordAuthenticationToken实例,并且该UsernamePassordAuthenticationToken实例的authenticated属性为true(通过身份验证)。

控制台的日志也能说明身份验证成功了。

请求也成功访问到了/message接口。

安全上下文持有者SecurityContextHolder介绍与Debug分析就到这里,如果博主有说错的地方或者大家有不同的见解,欢迎大家评论补充。

Copyright © 2016-2025 www.jianfeikang.com 建飞家电维修 版权所有 Power by