運營人員反映,常常出現提示登陸超時的狀況;第一反映應該是session保存超時;因爲項目中使用redis保存session,而且設置了超時時間(想法是,session的過時交由redis超時控制);查看配置沒發現問題;因而就從http請求流程配合shiro源碼追蹤問題;java
一、shiro過濾器。每次請求都會通過該過濾器,且一次請求只執行一次;AbstractShiroFilter主要代碼web
protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain) throws ServletException, IOException { Throwable t = null; try { final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain);//把容器生成的HttpServletRequest封裝成ShiroHttpServletRequest final ServletResponse response = prepareServletResponse(request, servletResponse, chain); final Subject subject = createSubject(request, response);//每次都生成subject; //noinspection unchecked subject.execute(new Callable() { public Object call() throws Exception { updateSessionLastAccessTime(request, response);//更新session的最後更新時間 executeChain(request, response, chain); return null; } }); ...
protected WebSubject createSubject(ServletRequest request, ServletResponse response) { return new WebSubject.Builder(getSecurityManager(), request, response).buildWebSubject(); }
Builder方法是根據SecurityManager生成一個SubjectContext實例,該類繼承MapContext---該請求過長的securityManager、Request、Response、Session等都會被保存在改map中(多是爲了內部跳轉不用再建立subject);redis
二、建立subject; SecurityManager中的createSubjectapache
/** * This implementation functions as follows: * <p/> * <ol> * <li>Ensures the {@code SubjectContext} is as populated as it can be, using heuristics to acquire * data that may not have already been available to it (such as a referenced session or remembered principals).</li> * <li>Calls {@link #doCreateSubject(org.apache.shiro.subject.SubjectContext)} to actually perform the * {@code Subject} instance creation.</li> * <li>calls {@link #save(org.apache.shiro.subject.Subject) save(subject)} to ensure the constructed * {@code Subject}'s state is accessible for future requests/invocations if necessary.</li> * <li>returns the constructed {@code Subject} instance.</li> * </ol> * * @param subjectContext any data needed to direct how the Subject should be constructed. * @return the {@code Subject} instance reflecting the specified contextual data. * @see #ensureSecurityManager(org.apache.shiro.subject.SubjectContext) * @see #resolveSession(org.apache.shiro.subject.SubjectContext) * @see #resolvePrincipals(org.apache.shiro.subject.SubjectContext) * @see #doCreateSubject(org.apache.shiro.subject.SubjectContext) * @see #save(org.apache.shiro.subject.Subject) * @since 1.0 */ 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);//確保SecurityManager是有的,若是沒有把自身設置給subjectContext //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);//根據sessionID獲取session(也就是從redis中獲取)。並設置到subjectContext的Map中; //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);//開始建立subject //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);//保存subject,其實就是保存session return subject; }
三、建立web相關subject;具體在DefaultWebSubjectFactory中session
public Subject createSubject(SubjectContext context) { if (!(context instanceof WebSubjectContext)) {//判斷是不是web環境 return super.createSubject(context); } WebSubjectContext wsc = (WebSubjectContext) context; SecurityManager securityManager = wsc.resolveSecurityManager();//其實就是從subjectContext的map中獲取 Session session = wsc.resolveSession();//其實就是從subjectContext的map中獲取 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); }
四、wsc.resolveAuthenticated()ui
public boolean resolveAuthenticated() { Boolean authc = getTypedValue(AUTHENTICATED, Boolean.class);//這裏既是從map中取;如當前請求是登陸請求那麼會有值,不然是null; if (authc == null) { //see if there is an AuthenticationInfo object. If so, the very presence of one indicates a successful //authentication attempt: AuthenticationInfo info = getAuthenticationInfo(); authc = info != null; } if (!authc) { //fall back to a session check: Session session = resolveSession(); if (session != null) { Boolean sessionAuthc = (Boolean) session.getAttribute(AUTHENTICATED_SESSION_KEY);//從session中獲取是否已經認證 authc = sessionAuthc != null && sessionAuthc; } } return authc; }
subject.Login的時候回執行以下方法this
protected Subject createSubject(AuthenticationToken token, AuthenticationInfo info, Subject existing) { SubjectContext context = createSubjectContext(); context.setAuthenticated(true);//登陸成功後設置爲ture context.setAuthenticationToken(token); context.setAuthenticationInfo(info); if (existing != null) { context.setSubject(existing); } return createSubject(context); }
分析到這裏都沒發現爲什麼authenticated=false; 除非已經退出登陸;code
無奈轉戰ShiroSession自己;session的過時驗證方法在自身類;orm
SimpleSession看着看着發現有個,在AbstractValidatingSessionManager中有這麼個setSessionValidationSchedulerEnabled方法,默認爲true; 也就是說在不設置SessionValidationScheduler定時校驗任務的狀況下,shiro會啓動一個默認的任務校驗session;而項目中的校驗方式交由redis了,不是session自身的validate方法了。致使SimpleSession中的lastAccessTime永遠等於startTimestamp;。。。。那麼問題就應該找到了繼承