第一次使用SecurityUtils.getSubject()来获取Subject时
public static Subject getSubject() {
Subject subject = ThreadContext.getSubject();
if (subject == null) {
subject = (new Subject.Builder()).buildSubject();
ThreadContext.bind(subject);
}
return subject;
}
使用ThreadLocal模式来获取,若没有则创建一个并绑定到当前线程。此时创建使用的是Subject内部类Builder来创建的,Builder会创建一个SubjectContext接口的实例DefaultSubjectContext,最终会委托securityManager来根据SubjectContext信息来创建一个Subject,下面详细说下该过程,在DefaultSecurityManager的createSubject方法中:
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:
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 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;
}
先看代码,发现有个一个方法是我们需要的及context = resolveSession(context)
,跟踪代码如下,具体分为1、创建Session;2、获取Session,本篇文章主要围绕这连个主题讲解
DefaultSecurityManager源码如下:
@SuppressWarnings({"unchecked"})
protected SubjectContext resolveSession(SubjectContext context) {
// 1 创建Session
if (context.resolveSession() != null) {
log.debug("Context already contains a session. Returning.");
return context;
}
try {
//Context couldn't resolve it directly, let's see if we can since we have direct access to
//the session manager:
// 2 获取Sessioin
Session session = resolveContextSession(context);
if (session != null) {
context.setSession(session);
}
} catch (InvalidSessionException e) {
log.debug("Resolved SubjectContext context session is invalid. Ignoring and creating an anonymous " +
"(session-less) Subject instance.", e);
}
return context;
}
创建Session
1.先尝试从Context中获取Session,如果没有则尝试从Context中获取Subject,然后从Subject中获取Session,如果获取到则直接返回context.。
DefaultSubjectContext源码:如下
public Session resolveSession() {
Session session = getSession();//尝试从Context中获取Session
if (session == null) {
//try the Subject if it exists:
Subject existingSubject = getSubject();// 从Context中获取Subject
if (existingSubject != null) {
// 1.1
session = existingSubject.getSession(false);// 从Subject中获取Session
}
}
return session;
}
1.1 从步骤1代码中我们发现获取Subject后调用getSession(false)方法(具体实现在DelegatingSubject)
getSession的参数为false,标示是否创建Session;如果Session为空,并且传入的参数为true则创建一个Session,而传入的参数为false,所以此处不会创建Session。从以下源码我们分析创建Session分三步,
①先创建SessionContext,
②然后根据SessionContext创建Session,
③接着会装饰Session。
接下来具体分析①②
public Session getSession(boolean create) {
if (log.isTraceEnabled()) {
log.trace("attempting to get session; create = " + create +
"; session is null = " + (this.session == null) +
"; session has id = " + (this.session != null && session.getId() != null));
}
if (this.session == null && create) {
//added in 1.2:
if (!isSessionCreationEnabled()) {
String msg = "Session creation has been disabled for the current subject. This exception indicates " +
"that there is either a programming error (using a session when it should never be " +
"used) or that Shiro's configuration needs to be adjusted to allow Sessions to be created " +
"for the current Subject. See the " + DisabledSessionException.class.getName() + " JavaDoc " +
"for more.";
throw new DisabledSessionException(msg);
}
log.trace("Starting session for host {}", getHost());
// 1.1.1
SessionContext sessionContext = createSessionContext();// 创建SessionContext
// 1.1.2
Session session = this.securityManager.start(sessionContext);// 然后根据SessionContext创建Session
this.session = decorate(session);// 接着会装饰Session
}
return this.session;
}
1.1.1 我们先看SessionContext的主要内容,下面是SessionContext类图信息及具体操作,无非就是设置获取Host及SessionId
SessionContext 类
public interface SessionContext extends Map<String, Object> {
void setHost(String host);
String getHost();
Serializable getSessionId();
void setSessionId(Serializable sessionId);
}
看完SessionContext类我们从新回到上边① 及createSessionContext()方法,具体源码如下,我们发现吧ServletRequest、ServletResponse和host封装到SessionContext中。
@Override
protected SessionContext createSessionContext() {
WebSessionContext wsc = new DefaultWebSessionContext();
String host = getHost();
if (StringUtils.hasText(host)) {
wsc.setHost(host);
}
wsc.setServletRequest(this.servletRequest);
wsc.setServletResponse(this.servletResponse);
return wsc;
}
1.1.2 接着会调用this.securityManager.start(sessionContext),交由securityManager管理创建Session任务,具体代码在AbstractNativeSessionManager类中实现
public Session start(SessionContext context) {
// 1.1.2.1
Session session = createSession(context);// 创建Session,具体看1.1.2.1
// 1.1.2.2
applyGlobalSessionTimeout(session);// 设置全局超时时间,具体看1.1.2.2
onStart(session, context);// 判断是否是web程序,是则存储Cookie
notifyStart(session);// 处理实现SessionListener
//Don't expose the EIS-tier Session object to the client-tier:
return createExposedSession(session, context);
}
1.1.2.1 创建Session,主要做了判断Session是否有效,创建Session,
protected Session createSession(SessionContext context) throws AuthorizationException {
enableSessionValidationIfNecessary();// 如果启动定时任务,检查session的有效性
return doCreateSession(context);// 创建Session
}
protected Session doCreateSession(SessionContext context) {
Session s = newSessionInstance(context);// 创建一个Session实例,将SessionContext给Session,此时SessionId还没有创建
if (log.isTraceEnabled()) {
log.trace("Creating session for host {}", s.getHost());
}
create(s);// 创建Session,在这个方法中,会交个SessionDAO来处理,①①创建SessionId、②②存储Session信息
return s;
}
protected Session newSessionInstance(SessionContext context) {
return getSessionFactory().createSession(context);
}
protected void create(Session session) {
if (log.isDebugEnabled()) {
log.debug("Creating new EIS record for new session instance [" + session + "]");
}
// 具体操作交由sessionDAO实现
sessionDAO.create(session);
}
①①从上述代码中我们发现创建Session交由SessionFactory管理,查看SimpleSessionFactory源码,利用SimpleSession创建Session对象
public Session createSession(SessionContext initData) {
if (initData != null) {
String host = initData.getHost();
if (host != null) {
return new SimpleSession(host);
}
}
return new SimpleSession();
}
②②session的具体操作在SessionDAO中,发现会先创建SessionId,如果支持缓存,则将Session缓存起来
public Serializable create(Session session) {
Serializable sessionId = super.create(session);// 创建SessionId
cache(session, sessionId);// 如果支持缓存则存储Session
return sessionId;
}
1.1.2.2 applyGlobalSessionTimeout(session)设置全局超时时间,具体实现在AbstractNativeSessionManager类中
protected void applyGlobalSessionTimeout(Session session) {
session.setTimeout(getGlobalSessionTimeout());
onChange(session);
}
protected void onChange(Session session) {
sessionDAO.update(session);
}
分析了这么多我们回到步骤1发现session = existingSubject.getSession(false);getSession传入的参数为false也就是说此时不会创建Session,具体创建哪Session是在什么时候创建的呢??????????????
回到最开始public Subject createSubject(SubjectContext subjectContext)
方法,假设context = resolveSession(context)
已经执行完了,其实只实行了创建Session的过程,而且还没有给我们创建。
接着调用Subject subject = doCreateSubject(context);
方法,代码如下,通过SubjectFactory工厂接口来创建Subject的,而DefaultSecurityManager默认使用的SubjectFactory是DefaultWebSubjectFactory;
protected Subject doCreateSubject(SubjectContext context) {
return getSubjectFactory().createSubject(context);
}
继续看createSubject(context)方法,仍然是将SubjectContext一些属性给WebSubContext对象,创建完成后保存
public Subject createSubject(SubjectContext context) {
if (!(context instanceof WebSubjectContext)) {
return super.createSubject(context);
}
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);
}
重新回到最开始public Subject createSubject(SubjectContext subjectContext)
方法,Subject subject = doCreateSubject(context);
也执行完了,接着会调用save(subject);方法,我们发现在save方法中利用subjectDAO去调用save方法
protected void save(Subject subject) {
this.subjectDAO.save(subject);
}
默认使用DefaultSubjectDAO去操作save,首先判断isSessionStorageEnabled是否要存储Subject的session,如果要存储接着会调用saveToSession方法,
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;
}
protected void saveToSession(Subject subject) {
//performs merge logic, only updating the Subject's session if it does not match the current state:
mergePrincipals(subject);
mergeAuthenticationState(subject);
}
下面是具体存储过程,在mergePrincipals中我们关心的重点来了,其中有一个 Session session = subject.getSession(false);先去获取Session如果Session为null,则会调用session = subject.getSession();则默认调用subject.getSession(true);则会创建Session接着会走步骤1的流程
protected void mergePrincipals(Subject subject) {
//merge PrincipalCollection state:
PrincipalCollection currentPrincipals = null;
//SHIRO-380: added if/else block - need to retain original (source) principals
//This technique (reflection) is only temporary - a proper long term solution needs to be found,
//but this technique allowed an immediate fix that is API point-version forwards and backwards compatible
//
//A more comprehensive review / cleaning of runAs should be performed for Shiro 1.3 / 2.0 +
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);// 如果有Session则获取,否则返回null
if (session == null) {
if (!CollectionUtils.isEmpty(currentPrincipals)) {
session = subject.getSession();// 如果有Session则获取,没有Session创建
session.setAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY, currentPrincipals);// 获取Session,会走步骤2,具体请看下边分析
}
//otherwise no session and no principals - nothing to save
} else {
PrincipalCollection existingPrincipals =
(PrincipalCollection) session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
if (CollectionUtils.isEmpty(currentPrincipals)) {
if (!CollectionUtils.isEmpty(existingPrincipals)) {
session.removeAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
}
//otherwise both are null or empty - no need to update the session
} else {
if (!currentPrincipals.equals(existingPrincipals)) {
session.setAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY, currentPrincipals);
}
//otherwise they're the same - no need to update the session
}
}
}
但是我们发现,在这段代码中,在第一次创建Subject时,此时的凭证为Null,也就是不会执行session = subject.getSession();那什么时候会创建Session呢?
Session session = subject.getSession(false);
if (session == null) {
if (!CollectionUtils.isEmpty(currentPrincipals)) {
session = subject.getSession();
session.setAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY, currentPrincipals);
}
//otherwise no session and no principals - nothing to save
} else {
跟踪代码发现当我们登录调用Subject.login()的时候,此时通过会有凭证,之后会创建Session,跟踪login代码,在DefaultSecurityManager来中会调用createSubject(token, info, subject),接着调用
public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {
AuthenticationInfo info;
try {
info = authenticate(token);// 认证成功
} catch (AuthenticationException ae) {
try {
onFailedLogin(token, ae, subject);
} catch (Exception e) {
if (log.isInfoEnabled()) {
log.info("onFailedLogin method threw an " +
"exception. Logging and propagating original AuthenticationException.", e);
}
}
throw ae; //propagate
}
// 创建Subject
Subject loggedIn = createSubject(token, info, subject);
onSuccessfulLogin(token, info, loggedIn);
return loggedIn;
}
protected Subject createSubject(AuthenticationToken token, AuthenticationInfo info, Subject existing) {
SubjectContext context = createSubjectContext();
context.setAuthenticated(true);
context.setAuthenticationToken(token);
context.setAuthenticationInfo(info);
if (existing != null) {
context.setSubject(existing);
}
return createSubject(context);// 我们熟悉的方法
}
获取Session
2.如果从Context中没有取到Session则,尝试根据SessionId获取Session
DefaultSubjectContext源码:如下
protected Session resolveContextSession(SubjectContext context) throws InvalidSessionException {
// 2.1
SessionKey key = getSessionKey(context);
if (key != null) {
// 2.2
return getSession(key);
}
return null;
}
2.1 getSessionKey(context)具体过程在DefaultWebSecurityManager类中,源码如下,我们发现如果是Web程序,始终走第一个会返回WebSessionKey对象。所以在步骤2中key != null为true,会执行getSession(key)方法。
@Override
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);
}
}
2.2 getSession(key)具体执行过程在SessionsSecurityManager类中,源码如下,交由sessionManager处理
public Session getSession(SessionKey key) throws SessionException {
// 2.2.1
return this.sessionManager.getSession(key);
}
2.2.1 从2.2步骤我们看出此时获取Session交给了sessionManager(具体执行过程在AbstractNativeSessionManager类中),没有做实质性的操作,接着会调用doGetSession(key)方法
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.");
}
// 2.2.2
return doGetSession(key);
}
2.2.2 doGetSession(key)具体实现在AbstractValidatingSessionManager类中,首先会调用任务调度器验证Session的有效性,然后接着调用retrieveSession(key)方法
@Override
protected final Session doGetSession(final SessionKey key) throws InvalidSessionException {
enableSessionValidationIfNecessary();
log.trace("Attempting to retrieve session with key {}", key);
// 2.2.3
Session s = retrieveSession(key);
if (s != null) {
validate(s, key);
}
return s;
}
2.2.3 retrieveSession(key)具体实现在DefaultSessionManager类中,调用getSessionId(sessionKey)方法,sessionKey封装了Request和Response对象,此时我们会获取请求携带的Cookie信息,如果有SessionId则获取,如果sessionId不为空调用retrieveSessionFromDataSource(sessionId)方法,利用SessionDAO获取Session信息,如果有sessionId却没有取到session则抛出异常。
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;
}
protected Session retrieveSessionFromDataSource(Serializable sessionId) throws UnknownSessionException {
return sessionDAO.readSession(sessionId);
}
总结:
一共有两次会进入createSubject(SubjectContext subjectContext)方法,
第一次是拦截器
OncePerRequestFilter.doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
----->>>>
AbstractShiroFilter.oFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain)
----->>>>
AbstractShiroFilter.createSubject(ServletRequest request, ServletResponse response)
----->>>>
WebSubject.Builder(getSecurityManager(), request, response).buildWebSubject
----->>>>
DefaultSecurityManager.createSubject(this.subjectContext);
第二次进入
DelegatingSubject.login(token)
----->>>>
DefaultWebSecurityManager.login(this, token)// 具体Session创建
----->>>>
DefaultSecurityManager.createSubject(token, info, subject)
----->>>>
DefaultSecurityManager.createSubject(this.subjectContext);