主頁 >  其他 > shiro總結

shiro總結

2021-04-08 11:02:04 其他

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-關鍵組件

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more