zoukankan      html  css  js  c++  java
  • 【网络安全】登录问题(一)Session/Cookie源码分析

    从身份验证开始说起。

    我们在上BBS的时候,有些帖子是有限制的,只有允许身份的人才能观看,那么如果去校验你的身份呢?我们想到了一个办法,让用户有一个唯一的身份证明。但只有身份证明是不够的,你的身份证明完全可以伪造啊。就像使用ATM机,如果只通过银行卡做身份证明就可以取钱一样,你的钱很轻易的就被取走了。这时我们想到了一个办法“密码”。通过密码来确定你的使用是否伪造。

    从上边的例子来看,我们可以想到,一个完整的账号应该包含

    • 账号名<–> 身份证明,唯一
    • 密码<–> 这个身份的使用合法性校验,与账号名对应

    这样的一种结构很好的解决了访问身份性验证所遇到的两个问题:1.身份判断、2.身份合法性判断;

    账号有了之后,我们访问有限制的帖子可以通过输入账号密码来确定身份。此时我们遇到了一个问题,每一次访问有限制的帖子我都需要输入账号密码,结果浏览BBS大多的时间我都在输入账号密码了。浏览网页本来是获取信息,现在主要工作变为验证信息,这完全是本末倒置了吧!

    如何让服务器认识你呢?先将情况分为两种,一种是单轮对话,一种是多轮对话。

    在网络上和服务器交互中,我们通过万维网进行交互,就像《西厢记》中张生和崔莺莺互通书信之时通过媒人传递书信。

    假设,用户张生正在和崔莺莺服务器进行交互。过程基本是这样:

    • 张生发出书信。
    • 媒人送信。
    • 崔莺莺收信,信中署名张生,确认用户张生。笔迹和文字风格确认用户张生合法。回复张生。
    • 媒人送信
    • 张生收信

    这时一次完整的交互过程。

    单轮对话

    单轮对话是指你跟我交互,中间的间断时间在一定限度内且身份信息未发生变化。

    每次署名张生太麻烦,崔莺莺服务器在第一次确认张生信息之后,给张生一个特殊的暗号”&”,让张生每次信末尾画这样一个暗号,我就知道你是张生。而这个标记只维持这轮对话,这轮对话结束之后,你就必须重新署名张生,我在跟你确定一个暗号。

    这就是经典的Servlet内置模型中的Session,将用户的身份信息缓存到服务器,通过标记来识别用户。而在这个模型中,需要关注的点有五点个:

    1. 身份信息与暗号关联,以暗号代替账号关联身份

    2. 暗号唯一

    3. 暗号信息在服务器端存储

    4. 暗号 只维持有限时间内,超时需要重新验证分配

    5. 每次交互,维持时间重新计时

    多轮对话

    多轮对话中,指在一轮对话以外,身份信息未改变进行再次交互。

    张生进京赶考过后,想联系崔莺莺,直接发信怕被拒,想起之前所赠信物,将所赠信物托付信差。

    多轮对话中,服务器需要给终端一个信物以证明身份,这个信物就是cookie。cookie在特点上上跟session类似,主要的区别就是存储在终端。

    至此我们解决了两种场景下的用户识别问题。

    根据之前单轮对话的特点,我们可以概括seesion所需的主要内容:
    1. 数据存储
    1. 唯一性约束
    2. 超时删除

    Tomcat Catalina对于Session 的设计源码如下:

    //Session Manager
    public abstract class ManagerBase extends LifecycleMBeanBase
            implements Manager, PropertyChangeListener {
            ...
    
            // Session 存储
            protected Map<String, Session> sessions = new ConcurrentHashMap<>();
    
    
            protected final Deque<SessionTiming> sessionCreationTiming =
                new LinkedList<>();
    
            //双向队列实现Session 超时移除
            protected final Deque<SessionTiming> sessionExpirationTiming =
                new LinkedList<>();
    
            ...
    
            //实例化Session对象
            @Override
            public Session createSession(String sessionId) {
    
                if ((maxActiveSessions >= 0) &&
                        (getActiveSessions() >= maxActiveSessions)) {
                    rejectedSessions++;
                    throw new TooManyActiveSessionsException(
                            sm.getString("managerBase.createSession.ise"),
                            maxActiveSessions);
                }
    
                // Recycle or create a Session instance
                Session session = createEmptySession();
    
                // Initialize the properties of the new session and return it
                session.setNew(true);
                session.setValid(true);
                session.setCreationTime(System.currentTimeMillis());
                session.setMaxInactiveInterval(this.maxInactiveInterval);
    
                //唯一索引 “暗号”
                String id = sessionId;
                if (id == null) {
                    id = generateSessionId();
                }
                session.setId(id);
                sessionCounter++;
    
                SessionTiming timing = new SessionTiming(session.getCreationTime(), 0);
                synchronized (sessionCreationTiming) {
                    sessionCreationTiming.add(timing);
                    sessionCreationTiming.poll();
                }
                return (session);
    
            }
    
    
            //Session 生存检测
            public void processExpires() {
    
              long timeNow = System.currentTimeMillis();
              Session sessions[] = findSessions();
              int expireHere = 0 ;
    
              if(log.isDebugEnabled())
                  log.debug("Start expire sessions " + getName() + " at " + timeNow + " sessioncount " + sessions.length);
    
              //遍历检验
              for (int i = 0; i < sessions.length; i++) {
                  if (sessions[i]!=null && !sessions[i].isValid()) {
                      expireHere++;
                  }
              }
              long timeEnd = System.currentTimeMillis();
              if(log.isDebugEnabled())
                   log.debug("End expire sessions " + getName() + " processingTime " + (timeEnd - timeNow) + " expired sessions: " + expireHere);
              processingTime += ( timeEnd - timeNow );
    
          }
    
          //清理session实例
          public void remove(Session session, boolean update) {
    
              // 如果要回收的对象发生了更新,则生存时间更新
              if (update) {
                  long timeNow = System.currentTimeMillis();
                  int timeAlive =
                      (int) (timeNow - session.getCreationTimeInternal())/1000;
                  updateSessionMaxAliveTime(timeAlive);
                  expiredSessions.incrementAndGet();
                  SessionTiming timing = new SessionTiming(timeNow, timeAlive);
                  synchronized (sessionExpirationTiming) {
                      sessionExpirationTiming.add(timing);
                      sessionExpirationTiming.poll();
                  }
              }
    
              //删除实例
              if (session.getIdInternal() != null) {
                  sessions.remove(session.getIdInternal());
              }
          }
    
    
    
          //Sesssion结构
          public class More ...StandardSession implements HttpSession, Session, Serializable {
    
              //使用ConcurrentHashMap KV形式存储
              protected Map<String, Object> attributes = new ConcurrentHashMap<>();
    
              ...
    
              //实例化,保留对外部SessionManager引用
              public StandardSession(Manager manager) {
    
                  super();
                  this.manager = manager;
    
                  // Initialize access count
                  if (ACTIVITY_CHECK) {
                      accessCount = new AtomicInteger();
                  }
    
          }
    
          //Session 存值
          public void setNote(String name, Object value) {
    
            notes.put(name, value);
    
        }
    
    

    Cookie与Session的实现类似,源码如下

        //基本的存取形式也是基于KV
        private String name;    // NAME= ... "$Name" style is reserved
        private String value;   // value of NAME
    
        private String comment; // ;Comment=VALUE ... describes cookie's use
                    // ;Discard ... implied by maxAge < 0
        private String domain;  // ;Domain=VALUE ... domain that sees cookie
        private int maxAge = -1;    // ;Max-Age=VALUE ... cookies auto-expire
        private String path;    // ;Path=VALUE ... URLs that see the cookie
        private boolean secure; // ;Secure ... e.g. use SSL
        private int version = 0;    // ;Version=1 ... means RFC 2109++ style

    总结及Reference

    通过Session 和Cookie我们解决了,在单轮对话情况下和多轮对话情况下对于用户身份的识别。在单轮对话下,通过服务器端协商“暗号”实现身份识别,主要关注的点有:

    1. 身份信息与暗号关联,以暗号代替账号关联身份

    2. 暗号唯一

    3. 暗号信息在服务器端存储

    4. 暗号 只维持有限时间内,超时需要重新验证分配

    5. 每次交互,维持时间重新计时

    多轮对话和单论对话类似,信息保存在客户端,相当于“信物”。特性与Session类似,主要不同在于存储在服务端。

    Session源码 详见 Tomcat源码
    Cookie源码 详见 OpenJDK javax.servlet.http
    Cookie标准 详见 RFC 2109

  • 相关阅读:
    java 键盘监听事件
    DOM扩展
    DOM
    CSS hack
    客户端检测
    BOM
    函数表达式
    面向对象的程序设计
    引用类型(下)
    引用类型(上)
  • 原文地址:https://www.cnblogs.com/cunchen/p/9464116.html
Copyright © 2011-2022 走看看