【网络安全】登录问题(一)Session/Cookie源码分析

版权声明:转载请注明出处,欢迎各位斧正 https://blog.csdn.net/wang135139/article/details/72772900

从身份验证开始说起。

我们在上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

猜你喜欢

转载自blog.csdn.net/wang135139/article/details/72772900