Shiro Learning Sharing (4) - Session management and single-user login restrictions (web application version)

Session management and concurrent user login restrictions (web application version)


First, let's talk about the blogger's understanding of session and cookie usage in shiro.
After each user successfully logs in, shiro will automatically create a session and store it on the server. The session will contain the basic information of the Subject, and will return the sessionId of the session to the client after the request. In the case of closing, you can use the sessionId for authentication, and retrieve the session on the server side by bringing the sessionId cookie in the subsequent request. If the session exists, the verification is passed, and the session information is used.


By the way, here is the implementation mechanism of the rememberMe function.
If the rememberMe function of shiro is enabled, a rememberMe cookie will be added after the request is completed. The cookie will store the user's basic information, which is generally encrypted. When the browser is closed, the sessionId cookie will be Clear, but the cookie of rememberMe will be kept in the user's browser according to the time defined by itself. When the user calls the browser again to visit the website next time, the cookie will be automatically brought, and if the website is not the login website , Shiro will decode the cookie to obtain the user information inside, so as to achieve password-free login, update the subject information, generate a new session and return the sessionId.


In short, shiro maintains the connection to the client by generating a session on the server, and the cookie is the token that determines whether the
connection is valid or not.


session management

sessionDao implementation

Ehcache is used here as a cache, and redis or database can also be used for persistence
operations . AbstractSessionDAO is inherited here.

/**
 * Shiro的是Session操作的实现类
 * 但update操作由于是更新用户的最新一次操作,所以调用频率高
 * 如果shiro的过滤器过滤所有的链接,那么就算是静态资源也算会调用update
 * 因此如果要减少update的调用,目前的解决方案是将对外服务的接口的url加上.do之类的标志结尾
 * shiro的过滤器只过滤这些url
 * @author MDY
 */
public class MyShiroDao extends AbstractSessionDAO {
    //用于缓存session
    private Cache<Serializable, Session> cache;
    //用于缓存sessionId对应的用户,实现用户登陆人数限制
    private Cache<String, Serializable> userCache;

    @Override
    protected Serializable doCreate(Session session) {
        Serializable sessionId = generateSessionId(session);
        assignSessionId(session, sessionId);
        cache.put(sessionId, session);
        return sessionId;
    }

    @Override
    protected Session doReadSession(Serializable sessionId) {
        if (sessionId == null) {
            return null;
        }
        return cache.get(sessionId);
    }

    @Override
    public void update(Session session) throws UnknownSessionException {
        if (session instanceof ValidatingSession && !((ValidatingSession) session).isValid()) {
            //如果会话过期/停止 没必要再更新了
            return;
        }
        cache.put(session.getId(), session);
    }

    @Override
    public void delete(Session session) {
        cache.remove(session.getId());
        userCache.put(String.valueOf(session.getAttribute(PRINCIPALS_SESSION_KEY)), null);
    }

    @Override
    public Collection<Session> getActiveSessions() {
        return cache.values();
    }

    public void setCache(MyShiroCache myShiroCache) {
        userCache = myShiroCache.getUserSessionCache();
        cache = myShiroCache.getSessionCache();
    }

}

SessionManager implementation

Here is a direct copy of the code on the Internet. By setting the sessionId and session in the request, the number of reads of sessionDao is reduced.

public class MyShiroSessionManager extends DefaultWebSessionManager {
    /**
     * 获取session
     * 优化单次请求需要多次访问缓存的问题
     * @throws UnknownSessionException
     */
    @Override
    protected Session retrieveSession(SessionKey sessionKey){
        Serializable sessionId = getSessionId(sessionKey);
        ServletRequest request = null;
        if (sessionKey instanceof WebSessionKey) {
            request = ((WebSessionKey) sessionKey).getServletRequest();
        }

        if (request != null && null != sessionId) {
            Object sessionObj = request.getAttribute(sessionId.toString());
            if (sessionObj != null) {
                return (Session) sessionObj;
            }
        }
        Session session = super.retrieveSession(sessionKey);
        if (request != null && null != sessionId) {
            request.setAttribute(sessionId.toString(), session);
        }
        return session;
    }
}

Single User Login Restriction

Let me talk about my own implementation ideas: most of the online tutorials directly implement the kicking out of users in the filter, using the isAccessAllowed method of rewriting shiro's filter, while the blogger judges the session when logging in. By overriding the doCredentialsMatch method of HashedCredentialsMatcher , this method will be called when Subject.login(token), only single-user login restriction is implemented here

 @Override
    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
        String username = (String) token.getPrincipal();
        boolean match = super.doCredentialsMatch(token, info);
        if (match) {
            myShiroCache.getPasswordRetryCache().remove(username);
            //实现对之前登陆的用户踢出
            Session session = SecurityUtils.getSubject().getSession();
            Serializable sessionId = session.getId();
            //从缓存中取出之前该用户对应的sessionId,有的话就删除
            Serializable perSessionId = myShiroCache.getUserSessionCache().get(username);
            if (perSessionId != null) {
                myShiroCache.getSessionCache().remove(perSessionId);
            }
            myShiroCache.getUserSessionCache().put(username, sessionId);
            System.err.println(sessionId.toString());
        }
        return match;
    }

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325351137&siteId=291194637