Shiro源码剖析——Subject的创建与获取(一次完整的请求执行流程)
文章目录
- 一、AbstractShiroFilter
- 二、createSubject(request, response)
- 1、new Builder(this.getSecurityManager(), request, response)
- 2、buildWebSubject()
- 1)this.copy(SubjectContext subjectContext)
- 2)this.ensureSecurityManager(context)
- 3)this.resolveSession(context)
- 4)this.resolvePrincipals(context)
- 5)this.doCreateSubject(context)
- 6)this.save(subject)
- 三、subject.execute()
- 1、execute()
- 2、updateSessionLastAccessTime(request, response)
- 3、executeChain(request, response, chain)
- 四、认证时的createSubject()
- 1、getSubject()
- 2、subject.login()
- 五、用户认证后的后续请求
本文可能较长,但是通读一定能让你对整个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)
- resolveSession(subjectContext),首先尝试从context(MapContext)中获取session,没有就获取subject后尝试从subject中获取
- 如果仍不存在则调用resolveContextSession(subjectContext),试着从MapContext中获取sessionId
- 根据sessionId实例化一个SessionKey对象,并通过SessionKey实例获取session
- getSession(key)的任务直接交给sessionManager来执行
- 如果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执行完成创建,主要步骤如下
- 拿到subjectContext
- 解析security,放入contex(map)中
- 解析session,放入context(map)中
- 解析principals,放入context(map)中
- 通过subjectFactory创建subject
- 通过sessionDAO保存到session中
需要注意的是上面讲述的是这个方法的总体功能,但这是在过滤时调用的这个方法,其实大多数都没有实现,因为此时其实并没有获得到多少信息(除非是第二次请求,可以获得session),故创建的subject也没有多少信息。
如果没有获得session,则此时还没有用户身份信息,这个Subject还没有通过验证,只保留了三个属性:request,response,securityManager。
在过滤时没有session也不会创建session来保存subject信息,具体可以看下文认证时的createSubject来做对比。
三、subject.execute()
// 将该subject绑定到当前线程,并更新会话的上次访问时间以及分发合适的过滤器
subject.execute(new Callable() {public Object call() throws Exception {AbstractShiroFilter.this.updateSessionLastAccessTime(request, response);AbstractShiroFilter.this.executeChain(request, response, chain);return null;}
});
1、execute()
追踪execute(),来到DelegatingSubject类
public <V> V execute(Callable<V> callable) throws ExecutionException {Callable associated = this.associateWith(callable);try {return associated.call();} catch (Throwable var4) {throw new ExecutionException(var4);}
}
this.associateWith(callable)通过参数callable创建了一个SubjectCallable对象,所以我们查看SubjectCallable类中的call方法,如下:
public V call() throws Exception {Object var1;try {this.threadState.bind();var1 = this.doCall(this.callable);} finally {this.threadState.restore();}return var1;
}
this.threadState.bind();方法将subject和securityManager绑定到当前线程的resources(一个map),如下
public void bind() {SecurityManager securityManager = this.securityManager;if (securityManager == null) {securityManager = ThreadContext.getSecurityManager();}this.originalResources = ThreadContext.getResources();ThreadContext.remove();ThreadContext.bind(this.subject);if (securityManager != null) {ThreadContext.bind(securityManager);}}
doCall(this.callable)调用回调方法
2、updateSessionLastAccessTime(request, response)
更新会话上次访问时间
protected void updateSessionLastAccessTime(ServletRequest request, ServletResponse response) {if (!this.isHttpSessions()) {Subject subject = SecurityUtils.getSubject();if (subject != null) {Session session = subject.getSession(false);if (session != null) {try {session.touch();} catch (Throwable var6) {log.error("session.touch() method invocation has failed. Unable to update the corresponding session's last access time based on the incoming request.", var6);}}}}}
3、executeChain(request, response, chain)
将请求分发给合适的过滤器
此部分不做详解
四、认证时的createSubject()
基本认证流程如下:
Subject subject = SecurityUtils.getSubject();
subject.login(new UsernamePasswordToken("cyh", "123"));
1、getSubject()
追踪源码发现getSubject最终执行的是SecurityUtils类中的这么一个方法
public static Subject getSubject() {Subject subject = ThreadContext.getSubject();if (subject == null) {subject = (new Builder()).buildSubject();ThreadContext.bind(subject);}return subject;
}
从上文的分析中我们知道了每一次访问都会重新建立subject然后绑定到当前线程,所以当前线程中获得subject不会是null。只有在没有配置shirofilter的应用中才会出现当前线程中subject==null的情况,所以这里就可以拿到前面过滤过程中创建的subject
2、subject.login()
随后通过当前的subject和token调用login函数
public void login(AuthenticationToken token) throws AuthenticationException {clearRunAsIdentitiesInternal();Subject subject = securityManager.login(this, token);PrincipalCollection principals;String host = null;if (subject instanceof DelegatingSubject) {DelegatingSubject delegating = (DelegatingSubject) subject;//we have to do this in case there are assumed identities - we don't want to lose the 'real' principals:principals = delegating.principals;host = delegating.host;} else {principals = subject.getPrincipals();}if (principals == null || principals.isEmpty()) {String msg = "Principals returned from securityManager.login( token ) returned a null or " +"empty value. This value must be non null and populated with one or more elements.";throw new IllegalStateException(msg);}this.principals = principals;this.authenticated = true;if (token instanceof HostAuthenticationToken) {host = ((HostAuthenticationToken) token).getHost();}if (host != null) {this.host = host;}Session session = subject.getSession(false);if (session != null) {this.session = decorate(session);} else {this.session = null;}
}
其在最终会通过调用realm中的认证方法获得当前用户的info
Subject loggedIn = createSubject(token, info, subject);
随后在此处又创建了一次Subject
那么两次创建subject有什么区别呢
protected Subject createSubject(AuthenticationToken token, AuthenticationInfo info, Subject existing) {SubjectContext context = createSubjectContext();// 保存登陆状态context.setAuthenticated(true);// 保存tokencontext.setAuthenticationToken(token);// 保存认证的返回信息context.setAuthenticationInfo(info);context.setSecurityManager(this);if (existing != null) {// 保存subject的信息到上下文context.setSubject(existing);}// 开始创建subjectreturn createSubject(context);
}
可以看到此处的创建其实就是在上下文中额外增加了一些信息,之后也是通过调用上面过滤时用的那个函数createSubject(SubjectContext subjectContext)创建的subject
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;
}
这里着重看save函数,前面我们进入到
public Subject save(Subject subject) {if (isSessionStorageEnabled(subject)) {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;
}
如果开启session,则将subject保存至session中,这里我们接着saveToSession(subject)往下追踪,来到
protected void saveToSession(Subject subject) {mergePrincipals(subject);mergeAuthenticationState(subject);
}
先看mergePrincipals(subject)函数
protected void mergePrincipals(Subject subject) {PrincipalCollection currentPrincipals = null;if (subject.isRunAs() && subject instanceof DelegatingSubject) {try {Field field = DelegatingSubject.class.getDeclaredField("principals");field.setAccessible(true);currentPrincipals = (PrincipalCollection)field.get(subject);} catch (Exception e) {throw new IllegalStateException("Unable to access DelegatingSubject principals property.", e);}}if (currentPrincipals == null || currentPrincipals.isEmpty()) {currentPrincipals = subject.getPrincipals();}Session session = subject.getSession(false);if (session == null) {if (!isEmpty(currentPrincipals)) {session = subject.getSession();session.setAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY, currentPrincipals);}} else {PrincipalCollection existingPrincipals =(PrincipalCollection) session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);if (isEmpty(currentPrincipals)) {if (!isEmpty(existingPrincipals)) {session.removeAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);}} else {if (!currentPrincipals.equals(existingPrincipals)) {session.setAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY, currentPrincipals);}}}
}
其中先通过反射获取subject中的principals属性
try {Field field = DelegatingSubject.class.getDeclaredField("principals");field.setAccessible(true);currentPrincipals = (PrincipalCollection)field.get(subject);
}
显然当我们在过滤时创建的subject是没有这个属性的(除非得到了session,通过session获取保存的principals),只有在登陆认证的时候之后才有这个属性
if (!isEmpty(currentPrincipals)) {session = subject.getSession();session.setAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY, currentPrincipals);
}
以下都是针对第一次请求,没有能够获取到cookie中的session的情况
这里表示如果currentPrincipals不为空则获取session,获取不到则创建session并保存principals,过滤时的save函数不会进去这个代码段,也就没有创建session。
到这我们就清晰了,在最开始过滤时,显然是没有principals的,虽然前面执行了resolvePrincipals(context)函数,但是此时context上下文中是没有principals的信息的,所以过滤操作时处我们并不会去创建一个session
而在认证时再次执行save操作的时候,此时已经调用了认证获得了对应的info,info中解析出了principals信息在调用resolvePrincipals(context)时就保存了该信息,所以save时就会因此创建一个session,随后存放principals信息在session中
下方的mergeAuthenticationState(subject)函数其实也同理,在认证之后会将该状态保存到session中,然后 SessionId 写到 Cookies 中(DefaultWebSessionManager的onstart方法)
subject 创建出来之后,暂且叫内部subject,就是把认证通过的内部subject的信息和session复制给我们外界使用的subject.login(token)的subject中,这个subject暂且叫外部subject,看下session的赋值,又进行了一次装饰(decorate),这次装饰则把session(类型为StoppingAwareProxiedSession,即是内部subject和session的合体)和外部subject绑定到一起。
五、用户认证后的后续请求
除了手动调用login会进入认证,其他请求过来时也会先检测当前用户是否进行了认证(即subject.authenticated是否为true),没有则先认证,有则不需要。
现在我们已经知道使用了全局的Session为我们保存了认证状态, 并且每次请求就会从这个全局Session中取出认证状态并保存到新创建的Subject中即可,不需要再执行login,也不需调用doGetAuthenticationInfo()方法
现在, 我们只需要保证前后两次请求是从同一个Session中取的认证状态即可(即两次请求在同一个会话中),这只要通过sessionId便可以实现,即
当用户访问其他接口时,经过过滤器时会创建Subject,在其中执行resovleSession时便会调用方法获取请求携带的cookie,随后通过Cookies 中的SessionId 获取到Session,并将 Session 中的信息合并到当前Subject中,此时当前线程的 Subject.authenticated = true, Subject.principals 保存了用户信息。
随后经过过滤器验证,如指定的是 authc 过滤器,则认为是 FormAuthenticationFilter 来执行此处请求的验证。而 FormAuthenticationFilter 的判断条件是 Subject.authenticated 是否为true,因为已经合并了session中保存的subject的认证信息,故校验通过。(如果没有session,则在此处需要重新执行login操作)
Shiro源码剖析——Subject的创建与获取(一次完整的请求执行流程)相关推荐
- 老李推荐:第14章8节《MonkeyRunner源码剖析》 HierarchyViewer实现原理-获取控件列表并建立控件树 1...
老李推荐:第14章8节<MonkeyRunner源码剖析> HierarchyViewer实现原理-获取控件列表并建立控件树 poptest是国内唯一一家培养测试开发工程师的培训机构,以学 ...
- 4.2.10 Kafka源码剖析, 阅读环境搭建, broker启动流程, topic创建流程, Producer生产者流程, Consumer消费者流程,
目录 4.1 Kafka源码剖析之源码阅读环境搭建 4.1.1 安装配置Gradle 4.1.2 Scala的安装和配置 4.1.3 Idea配置 4.1.4 源码操作 4.2 Kafka源码剖析之B ...
- 源码剖析Django REST framework的认证方式及自定义认证
源码剖析Django REST framework的认证方式 由Django的CBV模式流程,可以知道在url匹配完成后,会执行自定义的类中的as_view方法. 如果自定义的类中没有定义as_vie ...
- Oracle调用接口(OCI)源码剖析(2):执行SQL语句并获取结果
概述 接着上一篇文章<Oracle调用接口(OCI)源码剖析(1):创建数据库连接>,我们继续对OCI中执行SQL语句并获取结果的源码进行剖析.该操作主要是由两个函数完成的:CDbExec ...
- redis惰性删除 lazy free 源码剖析,干货满满
目录 前言 数据删除场景 lazy free 概念 配置 源码剖析(版本 6.2.6) 场景一:客户端执行的显示删除/清除命令 场景二:某些指令带有的隐式删除命令 场景三:删除过期数据 场景四:内存淘 ...
- shiro源码篇 - 疑问解答与系列总结,你值得拥有
前言 开心一刻 小明的朋友骨折了,小明去他家里看他.他老婆很细心的为他换药,敷药,然后出去买菜.小明满脸羡慕地说:你特么真幸福啊,你老婆对你那么好!朋友哭得稀里哗啦的说:兄弟你别说了,我幸福个锤子,就 ...
- K8s基础知识学习笔记及部分源码剖析
K8s基础知识学习笔记及部分源码剖析 在学习b站黑马k8s视频资料的基础上,查阅了配套基础知识笔记和源码剖析,仅作个人学习和回顾使用. 参考资料: 概念 | Kubernetes 四层.七层负载均衡的 ...
- Python猫荐书系统之四:《Python源码剖析》
大家好,新一期的荐书栏目如期跟大家见面了. 先来看看今天的主角是谁:<Python源码剖析--深度探索动态语言核心技术>,2008年出版,作者 @陈儒 ,评分8.7分. 是的,你没看错,出 ...
- JS魔法堂:mmDeferred源码剖析
一.前言 avalon.js的影响力愈发强劲,而作为子模块之一的mmDeferred必然成为异步调用模式学习之旅的又一站呢!本文将记录我对mmDeferred的认识,若有纰漏请各位指正,谢谢.项目请见 ...
- Shiro源码学习之二
接上一篇 Shiro源码学习之一 3.subject.login 进入login public void login(AuthenticationToken token) throws Authent ...
最新文章
- PCL-1.8.1从源码搭建开发环境三(QHULL库的编译)
- Oracle的自定义函数浅析
- 如何配置Spring的XML文件及使用
- 【spring boot】【POI】错误:The supplied data appears to be in the Office 2007+ XML
- 关于webSocket建立前后端连接,并进行心跳机制的实现
- 分布式系统开发注意点_分布式系统注意事项
- 高频面试题3 类初始化过程与实例初始化
- Scikit-learn机器学习算法库代码实践
- linux jdk1.7 tomcat
- 基于网络的 Red Hat 无人值守安装
- 阶段1 语言基础+高级_1-3-Java语言高级_05-异常与多线程_第2节 线程实现方式_11_Thread类的常用方法_sleep...
- 深圳保障性住房【公租房、安居房、人才房】简单说明
- hbase基本操作命令及练习
- UE4 单面模型法线翻转及碰撞问题
- Nolanjdc对接傻妞教程
- css实现固定宽高比例的div
- 【牛客网专项练习题】
- 模糊测试框架 Sulley 使用手记 (一)
- android图片裁剪xof,HttpServletResponse response相关头信息,内容信息设置
- 使用多可用区Kubernetes进行灾难恢复