• 关于Shiro的退出请求是如何关联到登录请求的思考


    一、结论

    先给出结论,是因为本身是很简单的道理。假设我们没有使用任何认证授权的框架,就简单的使用Cookie和HttpSession,那么用户登录后的每一个请求是如何关联上这个用户的呢?答案很简单,由于每个请求Tomcat使用一个单独线程来处理,但是Http请求时是有cookie的,那么一般来说是在cookie中加入sessionId,后台服务根据sessionId去查找HttpSession,这样就可以关联起来了。这本是很基础的内容,为什么在使用Shiro的时候还有这个疑问呢?

    二、缘由

    起初我的认知:

    1. shiro为每一个用户创建了一个Subject(这个实际并不是每一个用户,只是之前是这样认为的),这个Subject是使用ThreadLocal绑定的。
    2. shiro的退出是直接使用的subject.logout()方法,也可以通过subject获取session、token等认证授权信息。

    以上两点是我之前对shiro有的认知,所以我以为一个用户登录之后会有一个Subject。因此我就发现这里就有一个疑问的地方,如果一个用户一个Subject,那Subject又是和线程绑定的,用户每一个请求都是一个单独的线程,那么用户的请求是如何与登录请求关联上获取到同一个Subject的呢?

    三、关键点分析

    这里就直接开始的跟踪源码分析,首先我想的是查看获取Subject相关的源码,跟踪到ThreadContext类中关键代码,两部分

    public static Subject getSubject() {
            return (Subject) get(SUBJECT_KEY);
    }
    
    public static Object get(Object key) {
            if (log.isTraceEnabled()) {
                String msg = "get() - in thread [" + Thread.currentThread().getName() + "]";
                log.trace(msg);
            }
    
            Object value = getValue(key);
            if ((value != null) && log.isTraceEnabled()) {
                String msg = "Retrieved value of type [" + value.getClass().getName() + "] for key [" +
                        key + "] " + "bound to thread [" + Thread.currentThread().getName() + "]";
                log.trace(msg);
            }
            return value;
        }
    
    //这里是最终获取到Subject的地方,resource为InheritableThreadLocalMap对象
    private static Object getValue(Object key) {
            return resources.get().get(key);
    }
    

    另外一个地方

    private static final Logger log = LoggerFactory.getLogger(ThreadContext.class);
    
        public static final String SECURITY_MANAGER_KEY = ThreadContext.class.getName() + "_SECURITY_MANAGER_KEY";
        public static final String SUBJECT_KEY = ThreadContext.class.getName() + "_SUBJECT_KEY";
    
        private static final ThreadLocal<Map<Object, Object>> resources = new InheritableThreadLocalMap<Map<Object, Object>>();
    

    可以看到,Subject确实是从InheritableThreadLocalMap对象中取出来的。但是为什么是InheritableThreadLocalMap,这样的话,子线程是哪里产生的?
    然后从shiro里层的过滤器开始跟踪代码,发现在AbstractShiroFilterdoFilterInternal方法中有关键代码:

    try {
                final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain);
                final ServletResponse response = prepareServletResponse(request, servletResponse, chain);
                //关键部分1
                final Subject subject = createSubject(request, response);
    
                //noinspection unchecked
                //关键部分2
                subject.execute(new Callable() {
                    public Object call() throws Exception {
                        updateSessionLastAccessTime(request, response);
                        executeChain(request, response, chain);
                        return null;
                    }
                });
            } catch (ExecutionException ex) {
                t = ex.getCause();
            } catch (Throwable throwable) {
                t = throwable;
            }
    

    在这里可以知道了,这里是另起线程来处理之后的事情。并且subject是每一次请求都会创建一个,那么请求之间的subject是如何关联起来的呢?跟踪createSubject方法,找到关键代码DefaultSecurityManagercreateSubject方法

    public Subject createSubject(SubjectContext subjectContext) {
            //create a copy so we don't modify the argument's backing map:
            SubjectContext context = copy(subjectContext);
    
            //ensure that the context has a SecurityManager instance, and if not, add one:
            context = ensureSecurityManager(context);
    
            //Resolve an associated Session (usually based on a referenced session ID), and place it in the context before
            //sending to the SubjectFactory.  The SubjectFactory should not need to know how to acquire sessions as the
            //process is often environment specific - better to shield the SF from these details:
            context = resolveSession(context);
    
            //Similarly, the SubjectFactory should not require any concept of RememberMe - translate that here first
            //if possible before handing off to the SubjectFactory:
            context = resolvePrincipals(context);
    
            Subject subject = doCreateSubject(context);
    
            //save this subject for future reference if necessary:
            //(this is needed here in case rememberMe principals were resolved and they need to be stored in the
            //session, so we don't constantly rehydrate the rememberMe PrincipalCollection on every operation).
            //Added in 1.2:
            save(subject);
    
            return subject;
        }
    

    在这个代码之前还有一些处理,主要的包括,将Request和Response对象放入之前的InheritableThreadLocalMap对象中。这里的代码主要是将SecurityManage实例、Session对象以及登录之后的认证信息Principals存入SubjectContext。然后在doCreateSubject方法中将这些内容都赋值给Subject。这样,虽然每一次请求都是一个新的Subject,但是subject里面的内容都是一致的。最后在subject.logout()方法中,删除掉session即可实现退出功能。

    参考文章:https://blog.zlf.me/Shiro-web线程绑定解惑.html

  • 相关阅读:
    java.lang.UnsatisfiedLinkError: No implementation found for
    target release 1.5 conflicts with default source release 1.7
    (转)makefile里PHONY的相关介绍
    Hint: A potential Change-Id was found, but it was not in the footer (last paragraph) of the commit message
    linux jdk版本随时切换
    提高Service的优先级
    第一章、数字图像的描述
    gluas图形处理——导读
    图像处理学习过程——网站,视频,书籍(长期更新)
    基数排序
  • 原文地址:https://www.cnblogs.com/bencakes/p/9000280.html
Copyright © 2020-2023  润新知