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的入口是過濾器,上圖是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集羣部署
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章