springsecurity 源码解读之 SecurityContext

在springsecurity 中,我们一般可以通过代码:

SecurityContext securityContext = SecurityContextHolder.getContext();

Authentication auth = securityContext.getAuthentication();

获取当前登录人员信息,其实我们可以从SecurityContext 获取 springsecurity 实现的秘密。

就让我从SecurityContextHolder 一步步抽丝剥茧,解读一下SecurityContext  的来源。

这里我们可以通过查看SecurityContextHolder 源码,看看这个SecurityContext  到底 是哪里来的。

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

    /**
     * Obtain the current <code>SecurityContext</code>.
     *
     * @return the security context (never <code>null</code>)
     */
    public static SecurityContext getContext() {
        return strategy.getContext();
    }
public static void setContext(SecurityContext context) {
        strategy.setContext(context);
    }

代码很简单,我们可以看到他是通过 strategy 获取 的,那这个strategy 有是什么东西呢?

通过跟踪 源码发现 ,这个 strategy 实际是ThreadLocalSecurityContextHolderStrategy 的一个实例对象。

那这个ThreadLocalSecurityContextHolderStrategy  又是什么呢,我们继续看源码。

final class ThreadLocalSecurityContextHolderStrategy implements SecurityContextHolderStrategy {
    //~ Static fields/initializers =====================================================================================

    private static final ThreadLocal<SecurityContext> contextHolder = new ThreadLocal<SecurityContext>();

    //~ Methods ========================================================================================================

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

    public SecurityContext getContext() {
        SecurityContext ctx = contextHolder.get();

        if (ctx == null) {
            ctx = 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();
    }
}

不过就是一个放到一个线程变量中。看到这里我们需要知道 什么使用调用了这个setContext 方法。

通过跟踪源码发现:原来调用这个setContext方法的类:

SecurityContextPersistenceFilter

这是一个过滤器。我们知道 springsecurity 是有一组 过滤器 组成的,并且这个过滤器排在这些过滤器的第一个位置。

这个我们终于找到设置这个context的源头了。

通过阅读源码 ,我们看到这样两行代码.

HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response);
SecurityContext contextBeforeChainExecution = repo.loadContext(holder);

我们看到了这个SecurityContext 的源头了。

我们找到了这个 SecurityContext 是通过这个SecurityContextRepository 类获取的。

我们找到了这个接口的实现 HttpSessionSecurityContextRepository。 

public SecurityContextPersistenceFilter() {
        this(new HttpSessionSecurityContextRepository());
    }

    public SecurityContextPersistenceFilter(SecurityContextRepository repo) {
        this.repo = repo;
    }

最终我们找到了HttpSessionSecurityContextRepository代码

 private SecurityContext readSecurityContextFromSession(HttpSession httpSession) {
        final boolean debug = logger.isDebugEnabled();

        if (httpSession == null) {
            if (debug) {
                logger.debug("No HttpSession currently exists");
            }

            return null;
        }

        // Session exists, so try to obtain a context from it.

        Object contextFromSession = httpSession.getAttribute(springSecurityContextKey);

        if (contextFromSession == null) {
            if (debug) {
                logger.debug("HttpSession returned null object for SPRING_SECURITY_CONTEXT");
            }

            return null;
        }

        // We now have the security context object from the session.
        if (!(contextFromSession instanceof SecurityContext)) {
            if (logger.isWarnEnabled()) {
                logger.warn(springSecurityContextKey + " did not contain a SecurityContext but contained: '"
                        + contextFromSession + "'; are you improperly modifying the HttpSession directly "
                        + "(you should always use SecurityContextHolder) or using the HttpSession attribute "
                        + "reserved for this class?");
            }

            return null;
        }

        if (debug) {
            logger.debug("Obtained a valid SecurityContext from " + springSecurityContextKey + ": '" + contextFromSession + "'");
        }

        // Everything OK. The only non-null return from this method.

        return (SecurityContext) contextFromSession;
    }

这里我们可以看到 实际 这个SecurityContext 实际是从session 中获取的。

Object contextFromSession = httpSession.getAttribute("SPRING_SECURITY_CONTEXT");

从这里我们就知道了这个SecurityContext 来源了。

步骤是:

1.通过 SecurityContextPersistenceFilter 这个过滤器,从session 中获取SecurityContext .

2.并且把这个SecurityContext  放到线程变量中,然后我们在这个请求中就可以直接通过SecurityContextHolder.getContext();

   获取SecurityContext 对象了。

解决了获取的问题,我们只需要把登录后的SecurityContext  放到httpsession 中就好了。

下面代码就是登录的代码实现:

UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(userName, pwd);
        authRequest.setDetails(new WebAuthenticationDetails(request));
        SecurityContext securityContext = SecurityContextHolder.getContext();
        Authentication auth = authenticationManager.authenticate(authRequest);
        securityContext.setAuthentication(auth);
        
        HttpSession session = request.getSession();
        session.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, securityContext); 

通过上面的代码,我们就将securityContext 放到 session中了。

原文地址:https://www.cnblogs.com/yg_zhang/p/10651990.html