shiro主要內容:
1:SecurityUtils
shiro提供的工具類,主要作用是獲取 SecurityManager和Subject
public abstract class SecurityUtils {
private static SecurityManager securityManager;
//獲取Subject
public static Subject getSubject() {
Subject subject = ThreadContext.getSubject();
if (subject == null) {
subject = (new Subject.Builder()).buildSubject();
ThreadContext.bind(subject);
}
return subject;
}
public static void setSecurityManager(SecurityManager securityManager) {
SecurityUtils.securityManager = securityManager;
}
//獲取securityManager
public static SecurityManager getSecurityManager() throws UnavailableSecurityManagerException {
SecurityManager securityManager = ThreadContext.getSecurityManager();
if (securityManager == null) {
securityManager = SecurityUtils.securityManager;
}
if (securityManager == null) {
String msg = "No SecurityManager accessible to the calling code, either bound to the " +
ThreadContext.class.getName() + " or as a vm static singleton. This is an invalid application " +
"configuration.";
throw new UnavailableSecurityManagerException(msg);
}
return securityManager;
}
}
ThreadContext內部使用ThreadLocal來保存Subject

2:SecurityManager 安全管理器
管理 shiro的各個組件實體,提供安全管理的服務,像一個容器
繼承了Authenticator(認證器),Authorizer(授權器),SessionManager(會話管理器)三個介面

其實作主要有這幾個 :
CacheSecurityManager:添加快取
RealmSecurityManager:添加資料源
AuthenticatingSecurityManager:內部包含一個Authenticator,將驗證的操作都委托給該實體
AuthorizingSecurityManager:內部包含一個Authenticator,將授權的操作都委托給該實體
SessionsSecurityManager:內部包含一個SessionManager,將session操作都交給該實體了
DefaultSecurityManger:securityManager的基本實作
3:Authenticator 認證器
只有一個authenticate介面,拿來認證AuthenticationToken是否有效,無效會拋出AuthenticationException例外
public interface Authenticator {
/**
* 驗證失敗會拋出的例外
* @see ExpiredCredentialsException:憑證過期
* @see IncorrectCredentialsException:憑證錯誤
* @see ExcessiveAttemptsException:多次嘗試失敗
* @see LockedAccountException:賬戶鎖定
* @see ConcurrentAccessException:并發訪問例外
* @see UnknownAccountException:賬號不存在
*/
public AuthenticationInfo authenticate(AuthenticationToken authenticationToken)
throws AuthenticationException;
}
其實作類:AbstractAuthenticator
public final AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
if (token == null) {
throw new IllegalArgumentException("Method argument (authentication token) cannot be null.");
}
log.trace("Authentication attempt received for token [{}]", token);
AuthenticationInfo info;
try {
info = doAuthenticate(token);
if (info == null) {
String msg = "No account information found for authentication token [" + token + "] by this " +
"Authenticator instance. Please check that it is configured correctly.";
throw new AuthenticationException(msg);
}
} catch (Throwable t) {
AuthenticationException ae = null;
if (t instanceof AuthenticationException) {
ae = (AuthenticationException) t;
}
if (ae == null) {
//Exception thrown was not an expected AuthenticationException. Therefore it is probably a little more
//severe or unexpected. So, wrap in an AuthenticationException, log to warn, and propagate:
String msg = "Authentication failed for token submission [" + token + "]. Possible unexpected " +
"error? (Typical or expected login exceptions should extend from AuthenticationException).";
ae = new AuthenticationException(msg, t);
if (log.isWarnEnabled())
log.warn(msg, t);
}
try {
notifyFailure(token, ae);
} catch (Throwable t2) {
if (log.isWarnEnabled()) {
String msg = "Unable to send notification for failed authentication attempt - listener error?. " +
"Please check your AuthenticationListener implementation(s). Logging sending exception " +
"and propagating original AuthenticationException instead...";
log.warn(msg, t2);
}
}
throw ae;
}
log.debug("Authentication successful for token [{}]. Returned account [{}]", token, info);
notifySuccess(token, info);
return info;
}
通過上面代碼可以看出驗證是通過doAuthenticate方法來驗證的,成功則回傳AuthenticationInfo物件,失敗則拋出例外,而doAuthenticate是一個抽象方法,shiro只提供了一個實作類:ModularRealmAuthenticator
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
assertRealmsConfigured();
Collection<Realm> realms = getRealms();
if (realms.size() == 1) {
return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);
} else {
return doMultiRealmAuthentication(realms, authenticationToken);
}
}
ModularRealmAuthenticator通過Realm數量來判斷使用那種方式獲取AuthenticationInfo
只有一個realm時:
protected AuthenticationInfo doSingleRealmAuthentication(Realm realm, AuthenticationToken token) {
if (!realm.supports(token)) {
String msg = "Realm [" + realm + "] does not support authentication token [" +
token + "]. Please ensure that the appropriate Realm implementation is " +
"configured correctly or that the realm accepts AuthenticationTokens of this type.";
throw new UnsupportedTokenException(msg);
}
AuthenticationInfo info = realm.getAuthenticationInfo(token);
if (info == null) {
String msg = "Realm [" + realm + "] was unable to find account data for the " +
"submitted AuthenticationToken [" + token + "].";
throw new UnknownAccountException(msg);
}
return info;
}
可以看到realm通過getAuthenticationInfo方法進行獲取AuthenticationInfo,getAuthenticationInfo方法是一個抽象方法,我們在使用的時候就是通過實作這個方法來完成自定義的用戶認證功能,到此如果只有一個relam,認證就結束了
如果有多個realm時:
protected AuthenticationInfo doMultiRealmAuthentication(Collection<Realm> realms, AuthenticationToken token) {
//獲取身份驗證嘗試期間使用的身份驗證策略,默認為AtLeastOneSuccessfulStrategy
AuthenticationStrategy strategy = getAuthenticationStrategy();
//獲取一個空的SimpleAuthenticationInfo物件
AuthenticationInfo aggregate = strategy.beforeAllAttempts(realms, token);
if (log.isTraceEnabled()) {
log.trace("Iterating through {} realms for PAM authentication", realms.size());
}
for (Realm realm : realms) {
// 認證前處理
// AllSuccessfulStrategy - 判斷realm.supports(token),如果不支持直接拋例外,回傳aggregate
// AtLeastOneSuccessfulStrategy - 回傳aggregate
// FirstSuccessfulStrategy - 回傳aggregate,也就是null
aggregate = strategy.beforeAttempt(realm, token, aggregate);
if (realm.supports(token)) {
log.trace("Attempting to authenticate token [{}] using realm [{}]", token, realm);
AuthenticationInfo info = null;
Throwable t = null;
try {
info = realm.getAuthenticationInfo(token);
} catch (Throwable throwable) {
t = throwable;
if (log.isDebugEnabled()) {
String msg = "Realm [" + realm + "] threw an exception during a multi-realm authentication attempt:";
log.debug(msg, t);
}
}
// AllSuccessfulStrategy - 如果有例外會拋出例外, 如果沒有就合并info和aggregate
// AtLeastOneSuccessfulStrategy - 如果有例外并不會拋出,只是會合并info和aggregate
// FirstSuccessfulStrategy - 如果aggregate存在,則回傳aggregate;否則回傳info
aggregate = strategy.afterAttempt(realm, token, info, aggregate, t);
} else {
log.debug("Realm [{}] does not support token {}. Skipping realm.", realm, token);
}
}
aggregate = strategy.afterAllAttempts(token, aggregate);
return aggregate;
}
多個realm比單個realm多了一個合并操作,先獲取一種驗證策略,在合并的時候驗證
AuthenticationStrategy strategy = getAuthenticationStrategy();
默認是AtLeastOneSuccessfulStrategy,至少有一個Realm認證成功,除了AtLeastOneSuccessfulStrategy還有AllSuccessfulStrategy和FirstSuccessfulStrategy,分別是所有realm成功則成功和第一個realm成功則成功

4Authorizer 授權器
Authorizer介面中的方法
/**
* 判斷是否有指定的權限
*/
boolean isPermitted(PrincipalCollection principals, String permission);
/**
* 判斷是否有指定的權限
*/
boolean isPermitted(PrincipalCollection subjectPrincipal, Permission permission);
/**
* 判斷是否有指定的權限集合
*/
boolean[] isPermitted(PrincipalCollection subjectPrincipal, String... permissions);
/**
* 判斷是否有指定的所有權限集合
*/
boolean isPermittedAll(PrincipalCollection subjectPrincipal, String... permissions);
/**
* 判斷是否有指定的所有權限集合
*/
boolean isPermittedAll(PrincipalCollection subjectPrincipal, Collection<Permission> permissions);
/**
* 檢測是否存在權限,否則拋例外
*/
void checkPermission(PrincipalCollection subjectPrincipal, String permission) throws AuthorizationException;
/**
* 檢測是否存在權限,否則拋例外
*/
void checkPermission(PrincipalCollection subjectPrincipal, Permission permission) throws AuthorizationException;
/**
* 檢測是否存在權限,否則拋例外
*/
void checkPermissions(PrincipalCollection subjectPrincipal, String... permissions) throws AuthorizationException;
/**
* 檢測是否存在權限,否則拋例外
*/
void checkPermissions(PrincipalCollection subjectPrincipal, Collection<Permission> permissions) throws AuthorizationException;
/**
* 判斷是否有指定的角色
*/
boolean hasRole(PrincipalCollection subjectPrincipal, String roleIdentifier);
/**
* 判斷是否有指定的角色集合
*/
boolean[] hasRoles(PrincipalCollection subjectPrincipal, List<String> roleIdentifiers);
/**
* 判斷是否有指定的所有角色集合
*/
boolean hasAllRoles(PrincipalCollection subjectPrincipal, Collection<String> roleIdentifiers);
/**
* 檢測角色
*/
void checkRole(PrincipalCollection subjectPrincipal, String roleIdentifier) throws AuthorizationException;
/**
* 檢測角色
*/
void checkRoles(PrincipalCollection subjectPrincipal, Collection<String> roleIdentifiers) throws AuthorizationException;
/**
* 檢測角色
*/
void checkRoles(PrincipalCollection subjectPrincipal, String... roleIdentifiers) throws AuthorizationException;
方法很多,但作用都差不多,檢查PrincipalCollection是否含有角色和權限,我們就看一個isPermitted(PrincipalCollection principals, String permission);
它的的實作類是AuthorizingRealm:
public boolean isPermitted(PrincipalCollection principals, String permission) {
Permission p = getPermissionResolver().resolvePermission(permission);
return isPermitted(principals, p);
}
獲取一個PermissionResolver物件,將permission字串轉換成Permission物件,PermissionResolver默認是WildcardPermissionResolver

轉換的Permission物件默認是WildcardPermission

回到上面,獲取到了Permission物件后就開始驗證了:isPermitted(principals, p)
public boolean isPermitted(PrincipalCollection principals, String permission) {
Permission p = getPermissionResolver().resolvePermission(permission);
return isPermitted(principals, p);
}
先獲取用戶資訊,然后再驗證:getAuthorizationInfo(principals);
public boolean isPermitted(PrincipalCollection principals, Permission permission) {
AuthorizationInfo info = getAuthorizationInfo(principals);
return isPermitted(permission, info);
}
先在快取里面獲取,如果為null就通過doGetAuthorizationInfo來獲取,而doGetAuthorizationInfo在我們使用shiro的時候一般會重寫這個方法,達到自定義的目的
protected AuthorizationInfo getAuthorizationInfo(PrincipalCollection principals) {
if (principals == null) {
return null;
}
AuthorizationInfo info = null;
if (log.isTraceEnabled()) {
log.trace("Retrieving AuthorizationInfo for principals [" + principals + "]");
}
Cache<Object, AuthorizationInfo> cache = getAvailableAuthorizationCache();
if (cache != null) {
if (log.isTraceEnabled()) {
log.trace("Attempting to retrieve the AuthorizationInfo from cache.");
}
Object key = getAuthorizationCacheKey(principals);
info = cache.get(key);
if (log.isTraceEnabled()) {
if (info == null) {
log.trace("No AuthorizationInfo found in cache for principals [" + principals + "]");
} else {
log.trace("AuthorizationInfo found in cache for principals [" + principals + "]");
}
}
}
if (info == null) {
// Call template method if the info was not found in a cache
info = doGetAuthorizationInfo(principals);
// If the info is not null and the cache has been created, then cache the authorization info.
if (info != null && cache != null) {
if (log.isTraceEnabled()) {
log.trace("Caching authorization info for principals: [" + principals + "].");
}
Object key = getAuthorizationCacheKey(principals);
cache.put(key, info);
}
}
return info;
}
上面獲取到了AuthorizationInfo,下面看一下驗證程序
//visibility changed from private to protected per SHIRO-332
protected boolean isPermitted(Permission permission, AuthorizationInfo info) {
Collection<Permission> perms = getPermissions(info);
if (perms != null && !perms.isEmpty()) {
for (Permission perm : perms) {
if (perm.implies(permission)) {
return true;
}
}
}
return false;
}
先獲取Permission集合物件,然后使用implies()方法來檢查權限,只要有一個為true就回傳true,下面看一下WildcardPermission類的驗證方法
public boolean implies(Permission p) {
// By default only supports comparisons with other WildcardPermissions
if (!(p instanceof WildcardPermission)) {
return false;
}
WildcardPermission wp = (WildcardPermission) p;
List<Set<String>> otherParts = wp.getParts();
int i = 0;
for (Set<String> otherPart : otherParts) {
//otherParts會和getParts()逐一匹配,如果getParts()在匹配程序中比otherParts數量少,就暗指省略可以匹配,回傳true
// 否則的話需要一一進行比較,在比較的程序中如果part不包含通配符(*),且part不能完全包含otherPart集合,就認為沒有權限,回傳false,
if (getParts().size() - 1 < i) {
return true;
} else {
Set<String> part = getParts().get(i);
if (!part.contains(WILDCARD_TOKEN) && !part.containsAll(otherPart)) {
return false;
}
i++;
}
}
for (; i < getParts().size(); i++) {
Set<String> part = getParts().get(i);
if (!part.contains(WILDCARD_TOKEN)) {
return false;
}
}
return true;
}
獲取List<Set> otherParts集合,如果getParts()在匹配程序中比otherParts數量少,就暗指省略可以匹配,回傳true,
否則的話需要一一進行比較,在比較的程序中如果part不包含通配符(*),且part不能完全包含otherPart集合,就認為沒有權限,回傳false,到此權限驗證就結束了
5:SessionManager會話管理器
shiro的session可以不依賴web環境,自己實作了一個企業級session,這表明shiro可以在任何環境使用session,是一個很強大 的功能,一般我們在使用shiro的時候會配置一個sessionManager比如這樣:

這里配置的是一個DefaultWebSessionManager,看名字就知道是一個支持web環境的sessionManager,下面看一下他默認的構造器
public DefaultWebSessionManager() {
Cookie cookie = new SimpleCookie(ShiroHttpSession.DEFAULT_SESSION_ID_NAME);
cookie.setHttpOnly(true); //more secure, protects against XSS attacks
this.sessionIdCookie = cookie;
this.sessionIdCookieEnabled = true;
this.sessionIdUrlRewritingEnabled = true;
}
這里需要注意一下ShiroHttpSession.DEFAULT_SESSION_ID_NAME這個引數,它的默認值是JSESSIONID,后面在生成sessionid的時候就是根據這個值來生成的
private String getSessionIdCookieValue(ServletRequest request, ServletResponse response) {
if (!isSessionIdCookieEnabled()) {
log.debug("Session ID cookie is disabled - session id will not be acquired from a request cookie.");
return null;
}
if (!(request instanceof HttpServletRequest)) {
log.debug("Current request is not an HttpServletRequest - cannot get session ID cookie. Returning null.");
return null;
}
HttpServletRequest httpRequest = (HttpServletRequest) request;
return getSessionIdCookie().readValue(httpRequest, WebUtils.toHttp(response));
}
根據cookie的值來獲取session的id,進入readValue方法
public String readValue(HttpServletRequest request, HttpServletResponse ignored) {
String name = getName();
String value = null;
javax.servlet.http.Cookie cookie = getCookie(request, name);
if (cookie != null) {
// Validate that the cookie is used at the correct place.
String path = StringUtils.clean(getPath());
if (path != null && !pathMatches(path, request.getRequestURI())) {
log.warn("Found '{}' cookie at path '{}', but should be only used for '{}'", new Object[] { name, request.getRequestURI(), path});
} else {
value = cookie.getValue();
log.debug("Found '{}' cookie value [{}]", name, value);
}
} else {
log.trace("No '{}' cookie value", name);
}
return value;
}
第一行就看到了,首先獲取一個name,而這個name的值就是初始化的時候傳遞進去的JSESSIONID,由此可見session和cookie就是通過這個值來關聯起來的,接下來看一下他的父類
DefaultSessionManager:

開頭注釋,所有會話CRUD操作都*委托給內部{@link SessionDAO},通過建構式可以看出默認是使用MemorySessionDAO來操作session的,如果想要使用我們自己的SessionDAO只需要替換這個就行了,而且還專門提供了一個帶有SessionDAO引數的構造器,除了SessionDAO還有兩個初始值,一個是deleteInvalidSessions,初始值是true,用來控制當session失效時是否洗掉session,
一個是SessionFactory,默認是SimpleSessionFactory,用來創建session,下面看一下session的創建程序

newSessionInstance方法創建session,下面的create是保存到sesssiondao,先看一下newSessionInstance,這個方法先通過getSessionFactory()獲取一個創建工廠,上面已經說過初始化了,默認是SimpleSessionFactory工廠,SimpleSessionFactory創建session:
*/
public class SimpleSessionFactory implements SessionFactory {
/**
* Creates a new {@link SimpleSession SimpleSession} instance retaining the context's
* {@link SessionContext#getHost() host} if one can be found.
*
* @param initData the initialization data to be used during {@link Session} creation.
* @return a new {@link SimpleSession SimpleSession} instance
*/
public Session createSession(SessionContext initData) {
if (initData != null) {
String host = initData.getHost();
if (host != null) {
return new SimpleSession(host);
}
}
return new SimpleSession();
}
}
有host就呼叫host的構造引數,沒有就呼叫無參構造器
// Serialization reminder:
// You _MUST_ change this number if you introduce a change to this class
// that is NOT serialization backwards compatible. Serialization-compatible
// changes do not require a change to this number. If you need to generate
// a new number in this case, use the JDK's 'serialver' program to generate it.
private static final long serialVersionUID = -7125642695178165650L;
//TODO - complete JavaDoc
private transient static final Logger log = LoggerFactory.getLogger(SimpleSession.class);
protected static final long MILLIS_PER_SECOND = 1000;
protected static final long MILLIS_PER_MINUTE = 60 * MILLIS_PER_SECOND;
protected static final long MILLIS_PER_HOUR = 60 * MILLIS_PER_MINUTE;
//serialization bitmask fields. DO NOT CHANGE THE ORDER THEY ARE DECLARED!
static int bitIndexCounter = 0;
private static final int ID_BIT_MASK = 1 << bitIndexCounter++;
private static final int START_TIMESTAMP_BIT_MASK = 1 << bitIndexCounter++;
private static final int STOP_TIMESTAMP_BIT_MASK = 1 << bitIndexCounter++;
private static final int LAST_ACCESS_TIME_BIT_MASK = 1 << bitIndexCounter++;
private static final int TIMEOUT_BIT_MASK = 1 << bitIndexCounter++;
private static final int EXPIRED_BIT_MASK = 1 << bitIndexCounter++;
private static final int HOST_BIT_MASK = 1 << bitIndexCounter++;
private static final int ATTRIBUTES_BIT_MASK = 1 << bitIndexCounter++;
// ==============================================================
// NOTICE:
//
// The following fields are marked as transient to avoid double-serialization.
// They are in fact serialized (even though 'transient' usually indicates otherwise),
// but they are serialized explicitly via the writeObject and readObject implementations
// in this class.
//
// If we didn't declare them as transient, the out.defaultWriteObject(); call in writeObject would
// serialize all non-transient fields as well, effectively doubly serializing the fields (also
// doubling the serialization size).
//
// This finding, with discussion, was covered here:
//
// http://mail-archives.apache.org/mod_mbox/shiro-user/201109.mbox/%3C4E81BCBD.8060909@metaphysis.net%3E
//
// ==============================================================
private transient Serializable id;
private transient Date startTimestamp;
private transient Date stopTimestamp;
private transient Date lastAccessTime;
private transient long timeout;
private transient boolean expired;
private transient String host;
private transient Map<Object, Object> attributes;
public SimpleSession() {
this.timeout = DefaultSessionManager.DEFAULT_GLOBAL_SESSION_TIMEOUT; //TODO - remove concrete reference to DefaultSessionManager
this.startTimestamp = new Date();
this.lastAccessTime = this.startTimestamp;
}
public SimpleSession(String host) {
this();
this.host = host;
}
這里主要看一下timeout,startTimestamp,lastAccessTime,分別是超時時間,開始時間,最后一次操作時間,超時時間默認是30分鐘

到這里,一個session就創建完成了,回傳到上面接著看創建完成之后又干了啥

進入create(s);
protected void create(Session session) {
if (log.isDebugEnabled()) {
log.debug("Creating new EIS record for new session instance [" + session + "]");
}
sessionDAO.create(session);
}
他把操作交給sessionDao了,而這個sessionDao在初始化的時候默認是MemorySessionDAO,如果我們自己配置了Dao,就進入到我們的Dao了,看一下默認的MemorySessionDAO

內部有一個 ConcurrentMap<Serializable, Session> sessions;通過storeSession方法可以看出,session最后都是被存在了這里,下面是他的一些CRUD方法,都是在操作這個map
protected Serializable doCreate(Session session) {
Serializable sessionId = generateSessionId(session);
assignSessionId(session, sessionId);
storeSession(sessionId, session);
return sessionId;
}
protected Session storeSession(Serializable id, Session session) {
if (id == null) {
throw new NullPointerException("id argument cannot be null.");
}
return sessions.putIfAbsent(id, session);
}
protected Session doReadSession(Serializable sessionId) {
return sessions.get(sessionId);
}
public void update(Session session) throws UnknownSessionException {
storeSession(session.getId(), session);
}
public void delete(Session session) {
if (session == null) {
throw new NullPointerException("session argument cannot be null.");
}
Serializable id = session.getId();
if (id != null) {
sessions.remove(id);
}
}
public Collection<Session> getActiveSessions() {
Collection<Session> values = sessions.values();
if (CollectionUtils.isEmpty(values)) {
return Collections.emptySet();
} else {
return Collections.unmodifiableCollection(values);
}
}
到此session的保存就完成了,雖然知道他怎么創建和保存的了,但還不知道他在什么時候創建session,下面開始研究,通過上面的了解,我們知道它最終是創建了一個SimpleSession,那么就在這打個斷點,看下呼叫鏈就知道了
public SimpleSession() {
this.timeout = DefaultSessionManager.DEFAULT_GLOBAL_SESSION_TIMEOUT; //TODO - remove concrete reference to DefaultSessionManager
this.startTimestamp = new Date();
this.lastAccessTime = this.startTimestamp;
}

看一下最熟悉的login方法
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 loggedIn = createSubject(token, info, subject);
onSuccessfulLogin(token, info, loggedIn);
return loggedIn;
}
通過呼叫鏈可以看出session是從這里開始創建的(Subject loggedIn = createSubject(token, info, subject)),也就是說sessio是在認證成功之后創建sunject的時候創建的,具體在創建subject的什么時候呢?

還是通過呼叫鏈,可以看出是在保存subject的時候創建的session,這里需要注意一下 doCreateSubject(context);,這個方法并沒有真正的創建的session,通過下面這段代碼可以看出,在getsession的時候傳遞的一個false
public Session resolveSession() {
Session session = getSession();
if (session == null) {
//try the Subject if it exists:
Subject existingSubject = getSubject();
if (existingSubject != null) {
session = existingSubject.getSession(false);
}
}
return session;
}
回到上面保存subject的方法
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;
}
這里通過isSessionStorageEnabled方法來判斷是否保存session,
protected boolean isSessionStorageEnabled(Subject subject) {
return getSessionStorageEvaluator().isSessionStorageEnabled(subject);
}
getSessionStorageEvaluator()獲取一個SessionStorageEvaluator,默認是DefaultSessionStorageEvaluator

下面是isSessionStorageEnabled方法
public boolean isSessionStorageEnabled(Subject subject) {
return (subject != null && subject.getSession(false) != null) || isSessionStorageEnabled();
}
可以看出只要subject存在session或者sessionStorageEnabled為true都回傳true,而sessionStorageEnabled默認是true

看完什么情況會保存session后,回到上面 saveToSession(subject);
/**
* Saves the subject's state (it's principals and authentication state) to its
* {@link org.apache.shiro.subject.Subject#getSession() session}. The session can be retrieved at a later time
* (typically from a {@link org.apache.shiro.session.mgt.SessionManager SessionManager} to be used to recreate
* the {@code Subject} instance.
*
* @param subject the subject for which state will be persisted to its session.
*/
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);
}
這一步就是在保存session了
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);
}
// otherwise no session and no principals - nothing to save
} else {
PrincipalCollection existingPrincipals =
(PrincipalCollection) session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
if (isEmpty(currentPrincipals)) {
if (!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
}
}
}
先獲取一下session,(subject.getSession(false)),如果session為null就進入創建session的代碼了:session = subject.getSession();
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/273663.html
標籤:其他
上一篇:【單片機】繼電器控制
下一篇:小白入門ROS-關鍵組件
