Shiro-02-subject与session

Shiro的session与subject的创建

shiro框架默认有3种会话管理的实现

  1. DefaultSessionManager 用于JavaSE环境
  2. ServletContainerSessionManager 用于Web环境,直接使用Servlet容器会话
  3. DefaultWebSessionManager 用于Web环境,自己维护会话,不会使用Servlet容器的会话管理

先看一下shiro的session的api,熟悉一下用法

//获取当前subject
Subject subject = SecurityUtils.getSubject();
//通过subject获取session
Session session = subject.getSession();
//这个参数用于判定会话不存在时是否创建新会话。
Session session = subject.getSession(true);
//获取会话的唯一标识
session.getId();
//获取主机地址
session.getHost();
//获取超时时间
session.getTimeout();
//设置超时时间
session.setTimeout(毫秒);
//获取会话的启动时间
session.getStartTimestamp();
//获取会话的最后访问时间
session.getLastAccessTime();
//更新会话最后访问时间
session.touch();
//销毁session会话
session.stop();
//放入对象
session.setAttribute("key", "123");  
//移除对象
session.removeAttribute("key"); 

上面的3种会话管理,前两种不太常用,这里我学习一下第三种比较常用的DefaultWebSessionManager
Shiro-02-subject与session_第1张图片
Shiro的入口是过滤器,上图是shiro的过滤器类结构图
在OncePerRequestFilter中,执行了doFilter方法

public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
        if ( request.getAttribute(alreadyFilteredAttributeName) != null ) {
            log.trace("Filter '{}' already executed.  Proceeding without invoking this filter.", getName());
            filterChain.doFilter(request, response);
        } else //noinspection deprecation
            if (/* added in 1.2: */ !isEnabled(request, response) ||
                /* retain backwards compatibility: */ shouldNotFilter(request) ) {
            log.debug("Filter '{}' is not enabled for the current request.  Proceeding without invoking this filter.",
                    getName());
            filterChain.doFilter(request, response);
        } else {
            // Do invoke this filter...
            log.trace("Filter '{}' not yet executed.  Executing now.", getName());
            request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);

            try {
                doFilterInternal(request, response, filterChain);
            } finally {
                // Once the request has finished, we're done and we don't
                // need to mark as 'already filtered' any more.
                request.removeAttribute(alreadyFilteredAttributeName);
            }
        }
    }

继续跟踪源码

 protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain)
            throws ServletException, IOException {

        Throwable t = null;

        try {
        	//发现这里包装了request
            final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain);
            //发现这里包装了response
            final ServletResponse response = prepareServletResponse(request, servletResponse, chain);
			//这里创建了subjec
            final Subject subject = createSubject(request, response);

            //noinspection unchecked
            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;
        }

        if (t != null) {
            if (t instanceof ServletException) {
                throw (ServletException) t;
            }
            if (t instanceof IOException) {
                throw (IOException) t;
            }
            //otherwise it's not one of the two exceptions expected by the filter method signature - wrap it in one:
            String msg = "Filtered request failed.";
            throw new ServletException(msg, t);
        }
    }

再次跟踪createSubject,查看创建subject的方法

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:
        //session在这里完成了创建
        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在这里创建了,并且和session关联了起来
        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;
    }

继续追踪doCreateSubject

 public Subject createSubject(SubjectContext context) {
        if (!(context instanceof WebSubjectContext)) {
            return super.createSubject(context);
        }
        WebSubjectContext wsc = (WebSubjectContext) context;
        SecurityManager securityManager = wsc.resolveSecurityManager();
        //注意这里获取到了session
        Session session = wsc.resolveSession();
        boolean sessionEnabled = wsc.isSessionCreationEnabled();
        PrincipalCollection principals = wsc.resolvePrincipals();
        //是否已经登录成功
        boolean authenticated = wsc.resolveAuthenticated();
        String host = wsc.resolveHost();
        ServletRequest request = wsc.resolveServletRequest();
        ServletResponse response = wsc.resolveServletResponse();

        return new WebDelegatingSubject(principals, authenticated, host, session, sessionEnabled,
                request, response, securityManager);
    }

跟踪WebDelegatingSubject方法

//since 1.2
    public DelegatingSubject(PrincipalCollection principals, boolean authenticated, String host,
                             Session session, boolean sessionCreationEnabled, SecurityManager securityManager) {
        if (securityManager == null) {
            throw new IllegalArgumentException("SecurityManager argument cannot be null.");
        }
        this.securityManager = securityManager;
        this.principals = principals;
        this.authenticated = authenticated;
        this.host = host;
        if (session != null) {
        //把session赋值给了当前subject
            this.session = decorate(session);
        }
        this.sessionCreationEnabled = sessionCreationEnabled;
    }

总结一下:

  1. 每次请求都会创建一个subject,每次创建的时候,会先从会话中获取所有的认证信息,包括登录状态,这样就不会丢失会话状态了
  2. session和subject创建的过程中完成了关联
  3. 集群环境下使用shiro,如果负载均衡策略不是ip_hash的,会重新登录,因为会话保持还是基于session的,除非我们重写一下shiro的SessionDao,结合redis,就可以做到shiro集群部署

你可能感兴趣的:(shiro权限框架)