本文可能较长,但是通读一定能让你对整个shiro请求的执行流程有清晰的了解
总体流程:
1、在过滤的过程中创建subject
doFilter -> SecurityManager -> SubjectContext -> 创建subject -> 解析各种信息并赋值
2、若该subject未认证则进行认证并在认证时再次创建subject
调用realm中的doAuthenticationInfo()获得返回的信息重新创建subject并保存到session
一、AbstractShiroFilter
当我们使用shiro框架时,用户每次发送一个请求给服务端,都会被shiro的AbstractShiroFilter过滤器所拦截(AbstractShiroFilter是shiro的全局过滤器,所有的请求都会经过该过滤器)
protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain) throws ServletException, IOException { Throwable t = null; try { final ServletRequest request = this.prepareServletRequest(servletRequest, servletResponse, chain); final ServletResponse response = this.prepareServletResponse(request, servletResponse, chain); Subject subject = this.createSubject(request, response); subject.execute(new Callable() { public Object call() throws Exception { AbstractShiroFilter.this.updateSessionLastAccessTime(request, response); AbstractShiroFilter.this.executeChain(request, response, chain); return null; } }); } catch (ExecutionException var8) { t = var8.getCause(); } catch (Throwable var9) { t = var9; } if (t != null) { if (t instanceof ServletException) { throw (ServletException)t; } else if (t instanceof IOException) { throw (IOException)t; } else { String msg = "Filtered request failed."; throw new ServletException(msg, t); } } }
查看这个过滤器的**doFilterInternal()**方法,我们发现它主要做了两件事
// 创建一个subject createSubject(request, response)
// 将该subject绑定到当前线程,并更新会话的上次访问时间以及分发合适的过滤器 subject.execute(new Callable() { public Object call() throws Exception { AbstractShiroFilter.this.updateSessionLastAccessTime(request, response); AbstractShiroFilter.this.executeChain(request, response, chain); return null; } });
二、createSubject(request, response)
我们先看createSubject(request, response)这个方法,追踪后来到以下方法
protected WebSubject createSubject(ServletRequest request, ServletResponse response) { return (new Builder(this.getSecurityManager(), request, response)).buildWebSubject(); }
这个方法可以分为两部分
- new Builder(this.getSecurityManager(), request, response)
- buildWebSubject()
1、new Builder(this.getSecurityManager(), request, response)
首先通过当前的安全管理器等创建了一个Builder,以下是其构造方法
public Builder(SecurityManager securityManager, ServletRequest request, ServletResponse response) { super(securityManager); if (request == null) { throw new IllegalArgumentException("ServletRequest argument cannot be null."); } else if (response == null) { throw new IllegalArgumentException("ServletResponse argument cannot be null."); } else { this.setRequest(request); this.setResponse(response); } }
首先调用了父类的构造函数,如下
public Builder(SecurityManager securityManager) { if (securityManager == null) { throw new NullPointerException("SecurityManager method argument cannot be null."); } else { this.securityManager = securityManager; this.subjectContext = this.newSubjectContextInstance(); if (this.subjectContext == null) { throw new IllegalStateException("Subject instance returned from 'newSubjectContextInstance' cannot be null."); } else { this.subjectContext.setSecurityManager(securityManager); } } }
在其中设置了安全管理器,并创建了一个subjectContext,随后通过this.setRequest(request); this.setResponse(response);两个方法为这个subjectContext设置request和response,如下(response设置同理)
protected WebSubject.Builder setRequest(ServletRequest request) { if (request != null) { ((WebSubjectContext)this.getSubjectContext()).setServletRequest(request); } return this; }
至此Builder构造完成
2、buildWebSubject()
追踪源码我们来到**WebSubject类的buildWebSubject()**方法
public WebSubject buildWebSubject() { Subject subject = super.buildSubject(); if (!(subject instanceof WebSubject)) { String msg = "Subject implementation returned from the SecurityManager was not a " + WebSubject.class.getName() + " implementation. Please ensure a Web-enabled SecurityManager has been configured and made available to this builder."; throw new IllegalStateException(msg); } else { return (WebSubject)subject; } }
其中调用了父类**Subject类的buildSubject()**方法
public Subject buildSubject() { return this.securityManager.createSubject(this.subjectContext); }
需要注意的是这里的this.securityManager一般是DefaultWebSecurityManager类型的,继承自DefaultSecurityManager类
其实最终调用的是DefaultSecurityManager类的createSubject()方法
public Subject createSubject(SubjectContext subjectContext) { SubjectContext context = this.copy(subjectContext); context = this.ensureSecurityManager(context); context = this.resolveSession(context); context = this.resolvePrincipals(context); Subject subject = this.doCreateSubject(context); this.save(subject); return subject; }
1)this.copy(SubjectContext subjectContext)
这里copy方法用的是**DefaultWebSecurityManager重写的copy()**方法
protected SubjectContext copy(SubjectContext subjectContext) { return (SubjectContext)(subjectContext instanceof WebSubjectContext ? new DefaultWebSubjectContext((WebSubjectContext)subjectContext) : super.copy(subjectContext)); }
将之前调用无参构造初始化的SubjectContext(上文构造Builder时使用this.newSubjectContextInstance()方法创建的,这个方法调用了DefaultSubjectContext的无参构造函数,实例化了一个SubjectContext)作为参数,调用了DefaultSubjectContext的有参构造,最终也调用了MapContext中的有参构造;返回了一个SubjectContext
SubjectContext接口由DefaultSubjectContext实现(还有一个子类是DefaultWebSubjectContext),同时DefaultSubjectContext还继承自MapContext,其中有一个backingMap(本质也是一个map),里面是一路收集的一些信息,比如securityManger,subject,sessionId,principals,session等,key是DefaultSubjectContext中定义的一些常量(如下)。
private static final String SECURITY_MANAGER = DefaultSubjectContext.class.getName() + ".SECURITY_MANAGER"; private static final String SESSION_ID = DefaultSubjectContext.class.getName() + ".SESSION_ID"; private static final String AUTHENTICATION_TOKEN = DefaultSubjectContext.class.getName() + ".AUTHENTICATION_TOKEN"; private static final String AUTHENTICATION_INFO = DefaultSubjectContext.class.getName() + ".AUTHENTICATION_INFO"; private static final String SUBJECT = DefaultSubjectContext.class.getName() + ".SUBJECT"; private static final String PRINCIPALS = DefaultSubjectContext.class.getName() + ".PRINCIPALS"; private static final String SESSION = DefaultSubjectContext.class.getName() + ".SESSION"; private static final String AUTHENTICATED = DefaultSubjectContext.class.getName() + ".AUTHENTICATED"; private static final String HOST = DefaultSubjectContext.class.getName() + ".HOST"; public static final String SESSION_CREATION_ENABLED = DefaultSubjectContext.class.getName() + ".SESSION_CREATION_ENABLED"; public static final String PRINCIPALS_SESSION_KEY = DefaultSubjectContext.class.getName() + "_PRINCIPALS_SESSION_KEY"; public static final String AUTHENTICATED_SESSION_KEY = DefaultSubjectContext.class.getName() + "_AUTHENTICATED_SESSION_KEY"; private static final transient Logger log = LoggerFactory.getLogger(DefaultSubjectContext.class);
这个上下文的作用就是当初始化Subject时,从中获取需要的值来作为初始化Subject的参数
resolve的意思是解析,接下来的三步就是解析上下文中的manager,session和principals,为SubjectContext也就是MapContext中的backingMap的key中添加相应的value;
点进去查看会发现调用了DefaultSubjectContext的父类MapContext中的nullSafePut与put方法;
2)this.ensureSecurityManager(context)
用来确保上下文中已经存在securityManager,如果没有则将当前的securityManager设置进去
3)this.resolveSession(context)
1.resolveSession(subjectContext),首先尝试从context(MapContext)中获取session,没有就获取subject后尝试从subject中获取
2.如果仍不存在则调用resolveContextSession(subjectContext),试着从MapContext中获取sessionId
3.根据sessionId实例化一个SessionKey对象,并通过SessionKey实例获取session
4.getSession(key)的任务直接交给sessionManager来执行
5.如果key中获得的sessionId为null,则前往cookie中获取
具体如下:
protected SubjectContext resolveSession(SubjectContext context) { if (context.resolveSession() != null) { log.debug("Context already contains a session. Returning."); return context; } else { try { Session session = this.resolveContextSession(context); if (session != null) { context.setSession(session); } } catch (InvalidSessionException var3) { log.debug("Resolved SubjectContext context session is invalid. Ignoring and creating an anonymous (session-less) Subject instance.", var3); } return context; } }
如果上下文中有session则直接返回,没有则进行解析,**调用this.resolveContextSession(context)**方法
protected Session resolveContextSession(SubjectContext context) throws InvalidSessionException { SessionKey key = this.getSessionKey(context); return key != null ? this.getSession(key) : null; }
注意这里this.getSessionKey(context)是调用DefaultWebSecurityManager类中的方法
protected SessionKey getSessionKey(SubjectContext context) { if (WebUtils.isWeb(context)) { Serializable sessionId = context.getSessionId(); ServletRequest request = WebUtils.getRequest(context); ServletResponse response = WebUtils.getResponse(context); return new WebSessionKey(sessionId, request, response); } else { return super.getSessionKey(context); } }
如果是web请求(携带着response和request)则context.getSessionId()这个接口会调用DefaultSessionManager的实现(注意此处不是DefaultWebSessionManager),如下:
public Serializable getSessionId() { return getTypedValue(SESSION_ID, Serializable.class); }
可以看这里其实时通过上下文去获取sessionId而不是获取请求中的sessionId
显然在此处上下文中还尚未有该信息,自然是获取不到的
随后获取request和response,根据sessionId(null)和request和response封装为WebSessionKey
回到return key != null ? this.getSession(key) : null;
这里key!=null,所以调用this.getSession(key)
public Session getSession(SessionKey key) throws SessionException { return this.sessionManager.getSession(key); }
接着调用在AbstractNativeSessionManager中的实现
public Session getSession(SessionKey key) throws SessionException { Session session = lookupSession(key); return session != null ? createExposedSession(session, key) : null; }
private Session lookupSession(SessionKey key) throws SessionException { if (key == null) { throw new NullPointerException("SessionKey argument cannot be null."); } return doGetSession(key); }
这里key不等于null直接执行doGetSession(key),来到AbstractvalidatingSessionMangager类的实现
protected final Session doGetSession(final SessionKey key) throws InvalidSessionException { enableSessionValidationIfNecessary(); log.trace("Attempting to retrieve session with key {}", key); Session s = retrieveSession(key); if (s != null) { validate(s, key); } return s; }
接着执行Session s = retrieveSession(key)
,来到DefaultSessionManager中的实现
protected Session retrieveSession(SessionKey sessionKey) throws UnknownSessionException { Serializable sessionId = getSessionId(sessionKey); if (sessionId == null) { log.debug("Unable to resolve session ID from SessionKey [{}]. Returning null to indicate a " + "session could not be found.", sessionKey); return null; } Session s = retrieveSessionFromDataSource(sessionId); if (s == null) { //session ID was provided, meaning one is expected to be found, but we couldn't find one: String msg = "Could not find session with ID [" + sessionId + "]"; throw new UnknownSessionException(msg); } return s; }
其中第一句Serializable sessionId = getSessionId(sessionKey);由于我们在配置文件中设置的默认sessionManager是DefaultWebSessionManager,所以这里执行的是DefaultWebSessionManager类中的getSessionId(sessionKey)而不是DefaultSessionManager类中的,终于我们来到了下面这一步
public Serializable getSessionId(SessionKey key) { Serializable id = super.getSessionId(key); if (id == null && WebUtils.isWeb(key)) { ServletRequest request = WebUtils.getRequest(key); ServletResponse response = WebUtils.getResponse(key); id = this.getSessionId(request, response); } return id; }
这里第一句就是返回去调用DefaultSessionManager类中的方法, 然而这是通过上下文获取的,显然还是获取不到,接着id为空,我们来到this.getSessionId(request, response);
protected Serializable getSessionId(ServletRequest request, ServletResponse response) { return this.getReferencedSessionId(request, response); }
private Serializable getReferencedSessionId(ServletRequest request, ServletResponse response) { String id = this.getSessionIdCookieValue(request, response); if (id != null) { request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, "cookie"); } else { id = this.getUriPathSegmentParamValue(request, "JSESSIONID"); if (id == null && request instanceof HttpServletRequest) { String name = this.getSessionIdName(); HttpServletRequest httpServletRequest = WebUtils.toHttp(request); String queryString = httpServletRequest.getQueryString(); if (queryString != null && queryString.contains(name)) { id = request.getParameter(name); } if (id == null && queryString != null && queryString.contains(name.toLowerCase())) { id = request.getParameter(name.toLowerCase()); } } if (id != null) { request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, "url"); } } if (id != null) { request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id); request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE); } request.setAttribute(ShiroHttpServletRequest.SESSION_ID_URL_REWRITING_ENABLED, this.isSessionIdUrlRewritingEnabled()); return id; }
逐层调用最终来到
private String getSessionIdCookieValue(ServletRequest request, ServletResponse response) { if (!this.isSessionIdCookieEnabled()) { log.debug("Session ID cookie is disabled - session id will not be acquired from a request cookie."); return null; } else if (!(request instanceof HttpServletRequest)) { log.debug("Current request is not an HttpServletRequest - cannot get session ID cookie. Returning null."); return null; } else { HttpServletRequest httpRequest = (HttpServletRequest)request; return this.getSessionIdCookie().readValue(httpRequest, WebUtils.toHttp(response)); } }
return this.getSessionIdCookie().readValue(httpRequest, WebUtils.toHttp(response));
看到这一句,终于,我们通过获取cookie来读得其中保存的sessionId
这里this.getSessionIdCookie()获取的是类中定义的cookie——private Cookie sessionIdCookie,他在构造方法中创建
public DefaultWebSessionManager() { Cookie cookie = new SimpleCookie("JSESSIONID"); cookie.setHttpOnly(true); this.sessionIdCookie = cookie; this.sessionIdCookieEnabled = true; this.sessionIdUrlRewritingEnabled = false; }
之后通过readValue方法,它会在请求中寻找名为JSESSIONID的cookie并返回其值
至此我们终于获得了SessionId
那我们继续回到刚才这个函数
protected Session retrieveSession(SessionKey sessionKey) throws UnknownSessionException { Serializable sessionId = this.getSessionId(sessionKey); if (sessionId == null) { log.debug("Unable to resolve session ID from SessionKey [{}]. Returning null to indicate a session could not be found.", sessionKey); return null; } else { Session s = this.retrieveSessionFromDataSource(sessionId); if (s == null) { String msg = "Could not find session with ID [" + sessionId + "]"; throw new UnknownSessionException(msg); } else { return s; } } }
如果刚才cookie中获取不到sessionI则返回null,如果获取到则执行:Session s = retrieveSessionFromDataSource(sessionId); 通过SessionDao根据sessionId获取到了Session
如果session不为null返回最开始的那句调用:context.setSession(session);
public void setSession(Session session) { this.nullSafePut(SESSION, session); }
protected void nullSafePut(String key, Object value) { if (value != null) { this.put(key, value); } }
public Object put(String s, Object o) { return this.backingMap.put(s, o); }
如上文所说的将信息存储在backingMap中
至此便解析完上下文中的session,对于第一次请求没有session来说在这里(过滤时)并不会创建新的session
4)this.resolvePrincipals(context)
同理将获得到的principals存储在backingMap中,过滤时如果前面没有获得到session那么这里也将得不到principals(如果是认证时调用到此处则可以获得,差别见第四点)
5)this.doCreateSubject(context)
追踪该方法可以到DefaultWebSubjectFactory类中的如下方法
public Subject createSubject(SubjectContext context) { boolean isNotBasedOnWebSubject = context.getSubject() != null && !(context.getSubject() instanceof WebSubject); if (context instanceof WebSubjectContext && !isNotBasedOnWebSubject) { WebSubjectContext wsc = (WebSubjectContext)context; SecurityManager securityManager = wsc.resolveSecurityManager(); 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); } else { return super.createSubject(context); } }
我们通过subjectContext中保存的信息,执行return new WebDelegatingSubject(principals, authenticated, host, session, sessionEnabled, request, response, securityManager),这样就能够得到当前操作的主体,知道是谁在操作,是否已经认证了。
至此完成subject的创建
6)this.save(subject)
最终调用的是subjectDao中的save方法
public Subject save(Subject subject) { if (this.isSessionStorageEnabled(subject)) { this.saveToSession(subject); } else { log.trace("Session storage of subject state for Subject [{}] has been disabled: identity and authentication state are expected to be initialized on every request or invocation.", subject); } return subject; }
这里暂时不往下追踪,等到下面第四点时会再次提到这个函数
至此createSubject执行完成创建,主要步骤如下
1.拿到subjectContext
2.解析security,放入contex(map)中
3.解析session,放入context(map)中
4.解析principals,放入context(map)中
5.通过subjectFactory创建subject
6.通过sessionDAO保存到session中
需要注意的是上面讲述的是这个方法的总体功能,但这是在过滤时调用的这个方法,其实大多数都没有实现,因为此时其实并没有获得到多少信息(除非是第二次请求,可以获得session),故创建的subject也没有多少信息。
如果没有获得session,则此时还没有用户身份信息,这个Subject还没有通过验证,只保留了三个属性:request,response,securityManager。
在过滤时没有session也不会创建session来保存subject信息,具体可以看下文认证时的createSubject来做对比。