SecurityContextHolder之策略模式源碼分析

本文已參與好文召集令活動,點擊查看:後端、大前端雙賽道投稿,2萬元獎池等你挑戰前端

springsecurity 之 登陸用戶數據的獲取

springsecurity中,用戶登陸信息本質是保存到HttpSession中,springsecurity進行封裝 獲取登陸數據有兩種思路:java

  1. 從SecurityContextHolder中獲取
  2. 從當前請求對象中獲取

從SecurityContextHolder中獲取

@RestController
public class HelloController {
    @GetMapping("/hello")
    public void hello() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        System.out.println("authentication.getClass() = " + authentication.getClass());
    }
}
複製代碼

SecurityContextHolder存放的是SecurityContext ,SecurityContextHolder中定義三種不一樣的數據存儲策略,採用了策略模式web

  1. MODE_THREADLOCAL :將SecurityContext放在ThreadLocal中,開啓子線程,子線程獲取不到用戶數據。
  2. MODE_INHERITABLETHREADLOCAL:多線程環境,子線程也能獲取到用戶數據。
  3. MODE_GLOBAL:數據保存到一個靜態變量中,web開發中不多使用。

SecurityContextHolderStrategy接口用來規範存儲策略中的方法spring

public interface SecurityContextHolderStrategy {
    void clearContext();

    SecurityContext getContext();

    void setContext(SecurityContext var1);

    SecurityContext createEmptyContext();
}
複製代碼

有三個實現類 對應三個不一樣的存儲策略後端

ThreadLocalSecurityContextHolderStrategy

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.springframework.security.core.context;

import org.springframework.util.Assert;

final class ThreadLocalSecurityContextHolderStrategy implements SecurityContextHolderStrategy {
    private static final ThreadLocal<SecurityContext> contextHolder = new ThreadLocal();

    ThreadLocalSecurityContextHolderStrategy() {
    }

    public void clearContext() {
        contextHolder.remove();
    }

    public SecurityContext getContext() {
        SecurityContext ctx = (SecurityContext)contextHolder.get();
        if (ctx == null) {
            ctx = this.createEmptyContext();
            contextHolder.set(ctx);
        }

        return ctx;
    }

    public void setContext(SecurityContext context) {
        Assert.notNull(context, "Only non-null SecurityContext instances are permitted");
        contextHolder.set(context);
    }

    public SecurityContext createEmptyContext() {
        return new SecurityContextImpl();
    }
}
複製代碼

存儲載體是ThreadLocal 針對SecurityContext的操做都是在ThreadLocal中進行操做。SecurityContext只是個接口,只有一個實現類是SecurityContextImplmarkdown

InheritableThreadLocalSecurityContextHolderStrategy

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.springframework.security.core.context;

import org.springframework.util.Assert;

final class InheritableThreadLocalSecurityContextHolderStrategy implements SecurityContextHolderStrategy {
    private static final ThreadLocal<SecurityContext> contextHolder = new InheritableThreadLocal();

    InheritableThreadLocalSecurityContextHolderStrategy() {
    }

    public void clearContext() {
        contextHolder.remove();
    }

    public SecurityContext getContext() {
        SecurityContext ctx = (SecurityContext)contextHolder.get();
        if (ctx == null) {
            ctx = this.createEmptyContext();
            contextHolder.set(ctx);
        }

        return ctx;
    }

    public void setContext(SecurityContext context) {
        Assert.notNull(context, "Only non-null SecurityContext instances are permitted");
        contextHolder.set(context);
    }

    public SecurityContext createEmptyContext() {
        return new SecurityContextImpl();
    }
}
複製代碼

存儲載體爲InheritableThreadLocal ,InheritableThreadLocal繼承ThreadLocal,多了一個特性,就是在子線程建立的時間,會自動將父線程的數據複製到子線程中。實現了子線程中可以獲取登陸數據的功能。多線程

GlobalSecurityContextHolderStrategy

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.springframework.security.core.context;

import org.springframework.util.Assert;

final class GlobalSecurityContextHolderStrategy implements SecurityContextHolderStrategy {
    private static SecurityContext contextHolder;

    GlobalSecurityContextHolderStrategy() {
    }

    public void clearContext() {
        contextHolder = null;
    }

    public SecurityContext getContext() {
        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();
    }
}
複製代碼

存儲載體是一個靜態變量,也能夠在多線程環境下使用,但用的較少。app

SecurityContextHolder源碼

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.springframework.security.core.context;

import java.lang.reflect.Constructor;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;

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";
    private static String strategyName = System.getProperty("spring.security.strategy");
    private static SecurityContextHolderStrategy strategy;
    private static int initializeCount = 0;

    public SecurityContextHolder() {
    }

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

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

    public static int getInitializeCount() {
        return initializeCount;
    }

    private static void initialize() {
        if (!StringUtils.hasText(strategyName)) {
            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 {
                Class<?> clazz = Class.forName(strategyName);
                Constructor<?> customStrategy = clazz.getConstructor();
                strategy = (SecurityContextHolderStrategy)customStrategy.newInstance();
            } catch (Exception var2) {
                ReflectionUtils.handleReflectionException(var2);
            }
        }

        ++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();
    }

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

    static {
        initialize();
    }
}
複製代碼

SecurityContextHolder 定義三個靜態常量描述三種不一樣存儲策略,在靜態代碼塊中初始化,根據不一樣的strategyName初始化不一樣的存儲策略,能夠調用配置系統變量或者調用setStrategyName改變策略。post

在默認狀況下,從子線程中獲取登陸數據是獲取不到的,由於默認是MODE_THREADLOCAL策略。this

存儲策略在System.getProperty("spring.security.strategy");加載,能夠配置系統變量來修改策略。

在VM option參數添加

-Dspring.security.strategy=MODE_INHERITABLETHREADLOCAL

SpringContextHolder默認將用戶信息存儲在ThreadLocal中,在SpringBoot中不一樣的請求使用不一樣的線程處理,是怎麼獲取到用戶的信息的呢? 下篇文章揭曉,有懂的能夠在評論區留言。

相關文章
相關標籤/搜索