网络原理2.HTPP Session, Cookie和Token详解

一. 为什么要使用Cookie,Session或者Token

  1. http协议是无状态的,对于服务器而言,每次请求都是一个全新的请求
  2. 实际应用中,服务器要验证请求对应的用户信息,请求是否合法等
  3. 可以通过Cookie和Session验证用户信息
  4. 也可以通过token验证用户身份

二. Cookie和Session关联

http_cookie_session.png
  1. Session保存在服务器中,用于记录会话,而Cookie保存在浏览器中,用于跟踪服务器的会话
  2. 每个session都有一个Id,新建session的时候,服务器会自动生成一个sessionid
  3. 服务器设置Set-Cookie响应头,将sessionid返回给浏览器
  4. 浏览器根据Set-Cookie响应头,新建一个Cookie,name为jsessionid(Java Servlet默认值,也可以自定义),value为session的id
  5. 浏览器的每次请求,都会携带Cookie值
  6. 服务器根据jsessionid这个Cookie找到对应的session,获取用户信息,判断请求是否合法
  7. 如果浏览器禁用了Cookie,那么可以通过URL重定向的方式,将SessionID编码到URL中,获取服务器Session

1. Tomcat Session和Cookie源码剖析

1.1 Tomcat Request类

主要是通过cookie获取session,如果session不存在的话,则创建一个

//Tomcat Request类
public class Request implements HttpServletRequest {
    
    /**
     * 获取请求对应的Session
     * 如果session不存在,则创建一个
     */
    @Override
    public HttpSession getSession() {
        Session session = doGetSession(true);
        if (session == null) {
            return null;
        }
        return session.getSession();
    }
    
    /**
     * 获取Session
     * @param create 是否创建
     */
    protected Session doGetSession(boolean create) {
        //获取请求上下文
        Context context = getContext();
        if (context == null) {
            return null;
        }
        if ((session != null) && !session.isValid()) {
            //session过期了
            session = null;
        }
        if (session != null) {
            return session;
        }

        Manager manager = context.getManager();
        if (manager == null) {
            return null;
        }
        if (requestedSessionId != null) {
            //请求携带的requestedSessionId,也就是Cookie中的jsessionid
            try {
                //ManagerBase对象中获取Session
                session = manager.findSession(requestedSessionId);
            } catch (IOException e) {
                session = null;
            }
            if ((session != null) && !session.isValid()) {
                session = null;
            }
            if (session != null) {
                session.access();
                return session;
            }
        }

        if (!create) {
            return null;
        }
        if (response != null
                && context.getServletContext()
                        .getEffectiveSessionTrackingModes()
                        .contains(SessionTrackingMode.COOKIE)
                && response.getResponse().isCommitted()) {
            throw new IllegalStateException(
                    sm.getString("coyoteRequest.sessionCreateCommitted"));
        }

        String sessionId = getRequestedSessionId();
        if (requestedSessionSSL) {
            
        } else if (("/".equals(context.getSessionCookiePath())
                && isRequestedSessionIdFromCookie())) {
            //从Cookie中取出SessionId
            if (context.getValidateClientProvidedNewSessionId()) {
                boolean found = false;
                for (Container container : getHost().findChildren()) {
                    Manager m = ((Context) container).getManager();
                    if (m != null) {
                        try {
                            if (m.findSession(sessionId) != null) {
                                found = true;
                                break;
                            }
                        } catch (IOException e) {
                          
                        }
                    }
                }
                if (!found) {
                    sessionId = null;
                }
            }
        } else {
            sessionId = null;
        }
        session = manager.createSession(sessionId);

        //生成一个关联Session的Cookie
        if (session != null
                && context.getServletContext()
                        .getEffectiveSessionTrackingModes()
                        .contains(SessionTrackingMode.COOKIE)) {
            Cookie cookie =
                ApplicationSessionCookieConfig.createSessionCookie(
                        context, session.getIdInternal(), isSecure());
            //将cookie添加到response中
            response.addSessionCookieInternal(cookie);
        }

        if (session == null) {
            return null;
        }

        session.access();
        return session;
    }
}
1.2 Tomcat ManagerBase类

维护了所有的session

public abstract class ManagerBase extends LifecycleMBeanBase implements Manager {
    
    //保存sessiond的map
    protected Map<String, Session> sessions = new ConcurrentHashMap<>();

    /**
     * 通过sessionid获取session
     */
    public HashMap<String, String> getSession(String sessionId) {
        //从map中获取
        Session s = sessions.get(sessionId);
        if (s == null) {
            if (log.isInfoEnabled()) {
                log.info(sm.getString("managerBase.sessionNotFound", sessionId));
            }
            return null;
        }

        Enumeration<String> ee = s.getSession().getAttributeNames();
        if (ee == null || !ee.hasMoreElements()) {
            return null;
        }
        
        //将session中的值放到另一个map中
        //并返回新的map
        HashMap<String, String> map = new HashMap<>();
        while (ee.hasMoreElements()) {
            String attrName = ee.nextElement();
            map.put(attrName, getSessionAttribute(sessionId, attrName));
        }

        return map;
    }
}
1.3 tomcat Response类

主要通过过Set-Cookie请求头设置浏览器Cookie,还可以设置Cookie中维护的SessionId

/**
 * tomcat Response类
 */
public class Response implements HttpServletResponse {
    
    /**
     * 添加cookie到浏览器
     */
    @Override
    public void addCookie(final Cookie cookie) {
        if (included || isCommitted()) {
            return;
        }

        cookies.add(cookie);

        String header = generateCookieString(cookie);
        //设置Set-Cookie响应头
        addHeader("Set-Cookie", header, getContext().getCookieProcessor().getCharset());
    }

    /**
     * 将cookie添加到response中
     */
    public void addSessionCookieInternal(final Cookie cookie) {
        if (isCommitted()) {
            return;
        }

        String name = cookie.getName();
        //在Set-Cookie响应头中设置cookie
        final String headername = "Set-Cookie";
        final String startsWith = name + "=";
        String header = generateCookieString(cookie);
        boolean set = false;
        MimeHeaders headers = getCoyoteResponse().getMimeHeaders();
        int n = headers.size();
        for (int i = 0; i < n; i++) {
            if (headers.getName(i).toString().equals(headername)) {
                if (headers.getValue(i).toString().startsWith(startsWith)) {
                    headers.getValue(i).setString(header);
                    set = true;
                }
            }
        }
        if (!set) {
            addHeader(headername, header);
        }
    }
    
    //如果浏览器禁用了Cookie
    //那么可以使用该方法,将sessionId编码到URL中
    @Override
    public String encodeRedirectURL(String url) {
        if (isEncodeable(toAbsolute(url))) {
            return toEncoded(url, request.getSessionInternal().getIdInternal());
        } else {
            return url;
        }
    }
}

三. Token的使用

Token又被称为令牌,主要用于校验用户身份,也是我们当前推荐使用的一种认证方式

Token相比于Cookie的优势

  1. 无状态,服务器上不需要存储用户对应的Session数据,Token可以包含用户信息
  2. 支持跨域访问,Cookie默认是不支持跨域访问,允许跨域可能会导致CSRF攻击
  3. 解耦,将授权服务和应用服务解耦,只要token正确
  4. 安全性更高,可以采用各种加密方式来加密token,也可以有效避免CSRF攻击
  5. 有利于前后端分离,H5,APP各个应用都可以使用同一的授权验证模块
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容