1. 程式人生 > >springsecurity 源碼解讀之 SecurityContext

springsecurity 源碼解讀之 SecurityContext

使用 方法 security currently mit ctx ESS 直接 getc

在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中了。

springsecurity 源碼解讀之 SecurityContext