在 request 之间共享 SecurityContext

来源:互联网 发布:时间浪人网络大电影 编辑:程序博客网 时间:2024/06/17 03:42

引言

既然 SecurityContext 是存放在 ThreadLocal 中的,而且在每次权限鉴定的时候都是从 ThreadLocal 中获取 SecurityContext 中对应的 Authentication 所拥有的权限,并且不同的 request 是不同的线程,为什么每次都可以从 ThreadLocal 中获取到当前用户对应的 SecurityContext 呢?

入题

在 Web 应用中这是通过 SecurityContextPersistentFilter 实现的,默认情况下其会在每次请求开始的时候从 session 中获取 SecurityContext,然后把它设置给 SecurityContextHolder,在请求结束后又会将 SecurityContextHolder 所持有的 SecurityContext 保存在 session 中,并且清除 SecurityContextHolder 所持有的 SecurityContext。这样当我们第一次访问系统的时候,SecurityContextHolder 所持有的 SecurityContext 肯定是空的,待我们登录成功后,SecurityContextHolder 所持有的 SecurityContext 就不是空的了,且包含有认证成功的 Authentication 对象,待请求结束后我们就会将 SecurityContext 存在 session 中,等到下次请求的时候就可以从 session 中获取到该 SecurityContext 并把它赋予给 SecurityContextHolder 了,由于 SecurityContextHolder 已经持有认证过的 Authentication 对象了,所以下次访问的时候也就不再需要进行登录认证了。

相关代码

          //Stores the supplied security context in the session
 protected voidsaveContext(SecurityContext context) {
            final Authentication authentication = context.getAuthentication();
            HttpSession httpSession = request.getSession(false);


            // See SEC-776
            if (authentication == null || authenticationTrustResolver.isAnonymous(authentication)) {
                if (logger.isDebugEnabled()) {
                    logger.debug("SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession.");
                }


                if (httpSession != null && !contextObject.equals(contextBeforeExecution)) {
                    // SEC-1587 A non-anonymous context may still be in the session
                    // SEC-1735 remove if the contextBeforeExecution was not anonymous
                    httpSession.removeAttribute(springSecurityContextKey);
                }
                return;
            }


            if (httpSession == null) {
                httpSession = createNewSessionIfAllowed(context);
            }


            // If HttpSession exists, store current SecurityContext but only if it has
            // actually changed in this thread (see SEC-37, SEC-1307, SEC-1528)
            if (httpSession != null) {
                // We may have a new session, so check also whether the context attribute is set SEC-1561
                if (contextChanged(context) || httpSession.getAttribute(springSecurityContextKey) == null) {
                    httpSession.setAttribute(springSecurityContextKey, context);


                    if (logger.isDebugEnabled()) {
                        logger.debug("SecurityContext stored to HttpSession: '" + context + "'");
                    }
                }
            }
        }

//
private SecurityContextreadSecurityContextFromSession(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;
    }

重点看上面颜色标注部分代码!