Spring-session source code interpretation session

Abstract:  Session general strategy Session is usually saved in the browser through a cookie, and the jessionid is saved in the cookie, which represents the user's session id. An access path has only one session cookie (in fact, there is only one cookie on the client side, and jsessionid is part of the cookie value. Here, the cookie is abstracted into a server-like implementation), that is, an access path has only one se on a browser.

Session general strategy

Sessions are usually saved by cookies in browsers, and the jessionid is saved in the cookie, which represents the user's session id. An access path has only one session cookie (in fact, there is only one cookie on the client side, and jsessionid is used as part of the cookie value. Here, the cookie is abstracted into a server-like implementation), that is, an access path has only one session on a browser. , which is the implementation of session for most containers. However, spring can support single-browser multi-user sessions. Let's take a look at how spring supports multi-user sessions.

Support for multi-user sessions

Spring session implements multi-user sessions by adding the concept of session alias, and each user is mapped to a session alias. When there are multiple sessions, spring will generate a cookie value structure like "alias1 sessionid1 alias2 sessid2....".

If a new session is generated when the spring session is submitted, the onNewSession action will be triggered to generate a new session cookie

public void onNewSession(Session session, HttpServletRequest request, HttpServletResponse response) {
        Set<String> sessionIdsWritten = getSessionIdsWritten(request);
        if(sessionIdsWritten.contains(session.getId())) {
            return;
        }
        sessionIdsWritten.add(session.getId());

        Map<String,String> sessionIds = getSessionIds(request);
        String sessionAlias = getCurrentSessionAlias(request);
        sessionIds.put(sessionAlias, session.getId());
        Cookie sessionCookie = createSessionCookie(request, sessionIds);
        response.addCookie(sessionCookie);
    }

a) Make sure that sessions that already exist in cookies will not be processed again. 
b) Generate a map containing the session id of all aliases, and construct a new session cookie value from this map.

createSessionCookie会根据一个alias-sessionid的map去构造session cookie。

private Cookie createSessionCookie(HttpServletRequest request,
            Map<String, String> sessionIds) {
        //cookieName是"SESSION",spring的session cookie都是
        //以"SESSION"命名的
        Cookie sessionCookie = new Cookie(cookieName,"");
        //省略部分非关键逻辑

        if(sessionIds.isEmpty()) {
            sessionCookie.setMaxAge(0);
            return sessionCookie;
        }

        if(sessionIds.size() == 1) {
            String cookieValue = sessionIds.values().iterator().next();
            sessionCookie.setValue(cookieValue);
            return sessionCookie;
        }
        StringBuffer buffer = new StringBuffer();
        for(Map.Entry<String,String> entry : sessionIds.entrySet()) {
            String alias = entry.getKey();
            String id = entry.getValue();

            buffer.append(alias);
            buffer.append(" ");
            buffer.append(id);
            buffer.append(" ");
        }
        buffer.deleteCharAt(buffer.length()-1);

        sessionCookie.setValue(buffer.toString());
        return sessionCookie;
    }

a) 当session被invalidate,可能会存在seesionids为空的情况,这种情况下将session cookie的最大失效时间设成立即。 
b) 如果只有一个session id,则和普通session cookie一样处理,cookie值就是session id。 
c) 如果存在多个session id,则生成前文提到的session cookie值结构。

session cookie的获取

getSessionIds方法会取出request里的session cookie值,并且对每种可能的值结构进行相应的格式化生成一个key-value的map。

public Map<String,String> getSessionIds(HttpServletRequest request) {
        Cookie session = getCookie(request, cookieName);
        String sessionCookieValue = session == null ? "" : session.getValue();
        Map<String,String> result = new LinkedHashMap<String,String>();
        StringTokenizer tokens = new StringTokenizer(sessionCookieValue, " ");
        //单用户cookie的情况
        if(tokens.countTokens() == 1) {
            result.put(DEFAULT_ALIAS, tokens.nextToken());
            return result;
        }
        while(tokens.hasMoreTokens()) {
            String alias = tokens.nextToken();
            if(!tokens.hasMoreTokens()) {
                break;
            }
            String id = tokens.nextToken();
            result.put(alias, id);
        }
        return result;
    }
  1. 对单用户session cookie的处理,只取出值,默认为是默认别名(默认为0)用户的session。
  2. 对多用户,则依据值结构的格式生成alias-sessionid的map。
  3. 以上两种格式化都是对创建session的逆操作。

getCurrentSessionAlias用来获取当前操作用户。可以通过在request里附加alias信息,从而让spring可以判断是哪个用户在操作。别名是通过”alias name=alias”这样的格式传入的,alias name默认是_s,可以通过setSessionAliasParamName(String)方法修改。我们可以在url上或者表单里添加”_s=your user alias”这样的形式来指明操作用户的别名。如果不指明用户别名,则会认为是默认用户,可以通过setSessionAliasParamName(null)取消别名功能。

public String getCurrentSessionAlias(HttpServletRequest request) {
        if(sessionParam == null) {
            return DEFAULT_ALIAS;
        }
        String u = request.getParameter(sessionParam);
        if(u == null) {
            return DEFAULT_ALIAS;
        }
        if(!ALIAS_PATTERN.matcher(u).matches()) {
            return DEFAULT_ALIAS;
        }
        return u;
    }

触发session提交

spring会通过两个方面确保session提交:

a) response提交,主要包括response的sendRedirect和sendError以及其关联的字节字符流的flush和close方法。

abstract class OnCommittedResponseWrapper extends HttpServletResponseWrapper {
    public OnCommittedResponseWrapper(HttpServletResponse response) {
        super(response);
    }
    /**
     * Implement the logic for handling the {@link javax.servlet.http.HttpServletResponse} being committed
     */
    protected abstract void onResponseCommitted();

    @Override
    public final void sendError(int sc) throws IOException {
        doOnResponseCommitted();
        super.sendError(sc);
    }
    //sendRedirect处理类似sendError
    @Override
    public ServletOutputStream getOutputStream() throws IOException {
        return new SaveContextServletOutputStream(super.getOutputStream());
    }

    @Override
    public PrintWriter getWriter() throws IOException {
        return new SaveContextPrintWriter(super.getWriter());
    }

    private void doOnResponseCommitted() {
        if(!disableOnCommitted) {
            onResponseCommitted();
            disableOnResponseCommitted();
        } else if(logger.isDebugEnabled()){
            logger.debug("Skip invoking on");
        }
    }

    private class SaveContextPrintWriter extends PrintWriter {
        private final PrintWriter delegate;

        public SaveContextPrintWriter(PrintWriter delegate) {
            super(delegate);
            this.delegate = delegate;
        }

        public void flush() {
            doOnResponseCommitted();
            delegate.flush();
        }
//close方法与flush方法类似
    }
//SaveContextServletOutputStream处理同字符流
}

onResponseCommitted的实现由子类SessionRepositoryResponseWrapper提供

private final class SessionRepositoryResponseWrapper extends OnCommittedResponseWrapper {

        private final SessionRepositoryRequestWrapper request;
        /**
         * @param response the response to be wrapped
         */
        public SessionRepositoryResponseWrapper(SessionRepositoryRequestWrapper request, HttpServletResponse response) {
            super(response);
            if(request == null) {
                throw new IllegalArgumentException("request cannot be null");
            }
            this.request = request;
        }

        @Override
        protected void onResponseCommitted() {
            request.commitSession();
        }
    }

response提交后触发了session提交。 
b) SessionRespositoryFilter 
仅仅通过response提交时触发session提交并不能完全保证session的提交,有些情况下不会触发response提交,比如对相应资源的访问没有servlet处理,这种情况就需要通过全局filter做保证。

protectedvoiddoFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        try {
        //省略//filterChain会在所有filter都执行完毕后调用对应的servlet
            filterChain.doFilter(strategyRequest, strategyResponse);
        } finally {
        //所有的处理都完成后提交session
            wrappedRequest.commitSession()
        }   


https://yq.aliyun.com/articles/57421

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=326600057&siteId=291194637