栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Spring Security:安全上下文持有者SecurityContextHolder介绍与Debug分析

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Spring Security:安全上下文持有者SecurityContextHolder介绍与Debug分析

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 = new ThreadLocalSecurityContextHolderStrategy();
		}
		else if (strategyName.equals(MODE_INHERITABLETHREADLOCAL)) {
			strategy = new InheritableThreadLocalSecurityContextHolderStrategy();
		}
		else if (strategyName.equals(MODE_GLOBAL)) {
			strategy = new GlobalSecurityContextHolderStrategy();
		}
		else {
			// 尝试加载自定义策略
			try {
				Class clazz = Class.forName(strategyName);
				Constructor customStrategy = clazz.getConstructor();
				strategy = (SecurityContextHolderStrategy) customStrategy.newInstance();
			}
			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 = new 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 new SecurityContextImpl();
	}
}
InheritableThreadLocalSecurityContextHolderStrategy

基于InheritableThreadLocal的SecurityContextHolderStrategy实现。

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

final class InheritableThreadLocalSecurityContextHolderStrategy implements
		SecurityContextHolderStrategy {
	// 使用ThreadLocal持有SecurityContext实例,但该ThreadLocal是一个InheritableThreadLocal实例
	private static final ThreadLocal contextHolder = new 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 new 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 = new SecurityContextImpl();
		}

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

项目结构图:

pom.xml:



    4.0.0

    com.kaven
    security
    1.0-SNAPSHOT

    
        org.springframework.boot
        spring-boot-starter-parent
        2.3.6.RELEASE
    

    
        8
        8
    

    
        
            org.springframework.boot
            spring-boot-starter-web
        
        
            org.springframework.boot
            spring-boot-starter-security
        
        
            org.projectlombok
            lombok
        
    


application.yml:

spring:
  security:
    user:
      name: kaven
      password: itkaven
      roles: USER
logging:
  level:
    org:
      springframework:
        security: DEBUG

MessageController(定义接口):

package com.kaven.security.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

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

启动类:

package com.kaven.security;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

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

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

package com.kaven.security.config;

import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

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

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属性为UsernamePasswordAuthenticationToken实例,并且该UsernamePasswordAuthenticationToken实例的authenticated属性为true(通过身份验证)。

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

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

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

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/715607.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号