zoukankan      html  css  js  c++  java
  • Cookie/Session/Token

    原文使用有道云笔记:为更好浏览体验移步有道云:
     
    为什么有cookie/Session?
    因为HTTP协议本身是无状态,HTTP无状态协议,是指协议对于事务处理没有记忆能力。
    无状态是指Web浏览器与Web服务器之间不需要建立持久的连接,这意味着当一个客户端向服务器端发出请求,然后Web服务器返回响应(Response),连接就被关闭了,在服务器端不保留连接的有关信息。
    于是,两种用于保持HTTP连接状态的技术就应运而生了,一个是Cookie,而另一个则是Session
    也就是会话管理
    Cookie技术:会话数据保存在浏览器客户端。
    Session技术:会话数据保存在服务器端。
     
    Cookie
    Cookie实际上是一小段的文本信息(key-value格式)。它的原理如下:
    1:由服务器产生一个cookie;
    2:服务器发送给浏览器(响应头中包含set-cookie);
    3:浏览器保存;
    4:浏览器之后发送请求,请求头中会有cookie信息;
    5:服务器接收到cookie。
     
    通过一个示例说明:
    新建项目,这里使用之前的springboot项目(outwebest)了,懒得建。
    我这里使用servlet来写写看:新建两个servlet:
    @WebServlet(urlPatterns = "/setCookie")
    public class SetCookieServlet extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            Cookie cookie = new Cookie("userName","sunsas");
            resp.addCookie(cookie);
        }
    }
     
    @WebServlet("/getCookie")
    public class GetCookieServlet extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            Cookie[] cookies = req.getCookies();
            if(cookies != null){
                for(Cookie cookie : cookies){
                    System.out.println(cookie.getName() + ":" + cookie.getValue());
                }
            }
            else {
                System.out.println("no cookies");
            }
        }
    }
    需要注意要在启动类加上注解:@ServletComponentScan
    否则404.
    启动后,打开浏览器(这里使用chrome)控制台(F12)-》network:
    查看刚才的请求:
     
    可知,我们在响应中设置的cookie,实际是在响应头添加了:
    Set-Cookie: userName=sunsas
    浏览器会把它放到请求头中,下一次访问http://localhost:8080/getCookie
    已经携带cookie了。后端也接收到了:
    当然上面两个servlet也可以使用一个controller代替了:
    @Controller
    public class CookieSessionTokenController {
        @RequestMapping("/cSetCookie")
        public void cSetCookie(HttpServletResponse resp){
            Cookie cookie = new Cookie("userName","sunsas");
            resp.addCookie(cookie);
        }
        @RequestMapping("/cGetCookie")
        public void cGetCookie(HttpServletRequest req){
            Cookie[] cookies = req.getCookies();
            if(cookies != null){
                for(Cookie cookie : cookies){
                    System.out.println(cookie.getName() + ":" + cookie.getValue());
                }
            }
            else {
                System.out.println("no cookies");
            }
        }
    }
    只能说以前写代码真麻烦,servlet3以上才支持的@Webservlet,更久以前还得自己写xml中配置servlet,一个servlet至少6行,吐了。现在一个controller直接给你搞了,来再多的接口,也不虚。
    这里还要说下setMaxAge这个方法:
    /**
    * Sets the maximum age of the cookie in seconds.
    * <p>
    * A positive value indicates that the cookie will expire after that many
    * seconds have passed. Note that the value is the <i>maximum</i> age when
    * the cookie will expire, not the cookie's current age.
    * <p>
    * A negative value means that the cookie is not stored persistently and
    * will be deleted when the Web browser exits. A zero value causes the
    * cookie to be deleted.
    *
    源码已经说的很清楚了,这个方法就是设置cookie的过期时间,单位是秒,
    正值就是多少秒过期,0的话就是马上过期。没啥意义。
    默认为-1,负值表示浏览器关闭就会丢失cookie。
    我这里设置存活时间为10min:
    cookie.setMaxAge(60*10);
     
    响应头添加上了过期时间。
     
    Demo:显示上次访问时间
    思路就是每次访问去看看cookie中有没有访问时间,如果没有,是第一次访问,如果有,则打印时间,并更新时间:
    @RequestMapping("/lastVisitTime")
    @ResponseBody
    public String lastVisitTime(HttpServletRequest req,HttpServletResponse resp){
        String cookieKey = "lastVisitTime";
        String lastTime = null;
        // 首先去拿cookie
        Cookie[] cookies = req.getCookies();
        if(cookies != null){
            for(Cookie cookie : cookies){
                if(cookieKey.equals(cookie.getName())){
                    lastTime = cookie.getValue();
                }
            }
        }
        // 更新cookie ,注意addCookie时是不能使用空格的 而在ASCII码中32对应的就是空格。
        String time = new SimpleDateFormat("yyyy-MM-dd/hh:mm:ss").format(new Date());
        Cookie cookie1 = new Cookie(cookieKey, time);
        resp.addCookie(cookie1);
        // 返回值
        if(lastTime == null){
            return "first visit";
        }
        return lastTime;
    }
    注意addCookie时是不能使用空格的 而在ASCII码中32对应的就是空格。
    否则会报错:
    An invalid character [32] was present in the Cookie value
     
    Cookie的局限:
    1)Cookie只能存字符串类型。不能保存对象
    2)只能存非中文。
    3)1个Cookie的容量不超过4KB。
    如果大小超过4kb,则使用session。
     
    Session
    会话数据保存在服务器端。(内存中)
     
    当访问服务器否个网页的时候,会在服务器端的内存里开辟一块内存,这块内存就叫做session,而这个内存是跟浏览器关联在一起的。这个浏览器指的是浏览器窗口,或者是浏览器的子窗口,意思就是,只允许当前这个session对应的浏览器访问,就算是在同一个机器上新启的浏览器也是无法访问的。而另外一个浏览器也需要记录session的话,就会再启一个属于自己的session。
     
    由于HTTP协议无状态,那么浏览器怎么知道自己的session,答案是sessionId
    1:第一次访问创建session对象,分配一个sessionId;
    2:把sessionId放在cookie中给浏览器;
    3:第二次访问cookie就携带了sessionId;
    4:服务器根据sessionId查找对应的session(缓存中);
    5:如果找到,返回session对象;
    6:没有找到则创建session。
     
    新建servlet类:
    @WebServlet("/createSessionServlet")
    public class CreateSessionServlet extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            HttpSession session = req.getSession(true);
            session.setAttribute("userName", "sunsas");
        }
    }
     
    @WebServlet("/getSessionServlet")
    public class GetSessionServlet extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            HttpSession session = req.getSession(false);
            if(session != null){
                String value = (String) session.getAttribute("userName");
                System.out.println(value);
            }
            else {
                System.out.println("no session");
            }
        }
    }
    注意req.getSession()方法。此方法默认传参true,如果为false,获得session为空也直接返回,为true则会创建一个session:
    /**
    * Return the session associated with this Request, creating one
    * if necessary.
    */
    @Override
    public HttpSession getSession() {
        return getSession(true);
    }
     
    @Override
    public HttpSession getSession(boolean create) {

        if (crossContext) {

            // There cannot be a session if no context has been assigned yet
            // crossContext为true表示配置的不同context共享一个session,这里省略
        } else {
            return super.getSession(create);
        }
     
    // Request类下
    @Override
    public HttpSession getSession(boolean create) {
        Session session = doGetSession(create);
        if (session == null) {
            return null;
        }
        return session.getSession();
    }

    // 创建session具体代码

    protected Session doGetSession(boolean create) {

        // There cannot be a session if no context has been assigned yet
        Context context = getContext();
        if (context == null) {
            return null;
        }

        // Return the current session if it exists and is valid
        if ((session != null) && !session.isValid()) {
            session = null;
        }
        if (session != null) {
            return session;
        }

        // Return the requested session if it exists and is valid
        Manager manager = context.getManager();
        if (manager == null) {
            return null;      // Sessions are not supported
        }
        if (requestedSessionId != null) {
            try {
                session = manager.findSession(requestedSessionId);
            } catch (IOException e) {
                session = null;
            }
            if ((session != null) && !session.isValid()) {
                session = null;
            }
            if (session != null) {
                session.access();
                return session;
            }
        }

        // Create a new session if requested and the response is not committed
        if (!create) {
            return null;
        }
        boolean trackModesIncludesCookie =
                context.getServletContext().getEffectiveSessionTrackingModes().contains(SessionTrackingMode.COOKIE);
        if (trackModesIncludesCookie && response.getResponse().isCommitted()) {
            throw new IllegalStateException(sm.getString("coyoteRequest.sessionCreateCommitted"));
        }

        // Re-use session IDs provided by the client in very limited
        // circumstances.
        String sessionId = getRequestedSessionId();
        if (requestedSessionSSL) {
            // If the session ID has been obtained from the SSL handshake then
            // use it.
        } else if (("/".equals(context.getSessionCookiePath())
                && isRequestedSessionIdFromCookie())) {
            /* This is the common(ish) use case: using the same session ID with
             * multiple web applications on the same host. Typically this is
             * used by Portlet implementations. It only works if sessions are
             * tracked via cookies. The cookie must have a path of "/" else it
             * won't be provided for requests to all web applications.
             *
             * Any session ID provided by the client should be for a session
             * that already exists somewhere on the host. Check if the context
             * is configured for this to be confirmed.
             */
            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) {
                            // Ignore. Problems with this manager will be
                            // handled elsewhere.
                        }
                    }
                }
                if (!found) {
                    sessionId = null;
                }
            }
        } else {
            sessionId = null;
        }
        session = manager.createSession(sessionId);

        // Creating a new session cookie based on that session
        if (session != null && trackModesIncludesCookie) {
            Cookie cookie = ApplicationSessionCookieConfig.createSessionCookie(
                    context, session.getIdInternal(), isSecure());

            response.addSessionCookieInternal(cookie);
        }

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

        session.access();
        return session;
    }
    控制台打印“
    后访问
    还是F12看一下浏览器请求:
    可见里面存的叫JSessionId。
    这里我把浏览器关闭了,再次创建session然后获得session,所以他们id不一样。
    由于每次重新开启浏览器都会生成新的session,但这并不代表原来的session失效了。
    例如我现在复制下原来的sessionId
    A7439D2FFDF37F89C87AE5B6D4D64EAE
    我们给项目打上断点(Request下的doGetSession方法中),debug运行:
    重启浏览器,再次访问http://localhost:8080/createSessionServlet
    右键此方法进行运行:
    把老的sessionId传进去:
    可见session仍然在。
    session本身有一个存活时间,在tomcat中默认的是30分钟,
    和浏览器是没有关系的
     
    Token
    在计算机身份认证中是令牌(临时)的意思。一般作为邀请、登录系统使用。
    Token 是在服务端产生的。如果前端使用用户名/密码向服务端请求认证,服务端认证成功,那么在服务端会返回 Token 给前端。前端可以在每次请求的时候带上 Token 证明自己的合法地位。
    这有一篇文章关于cookie,session,token的演化讲的很好:
    简单来说,我们以前保存用户登录可能就是放在session,现在也可以,服务器返回的的sessionId相当于就是token,但是这样服务器要存很多的sessionId,来做验证。
    由于sessionId增建了服务器压力,于是有人发明了token,token存在客户端,在请求时随请求头过来。
    但是服务器端没有token,关键就是验证。就是加上比如userId+密钥,加密。这个只有服务器知道,有请求过来,就去解析,拿到userId。
    1:客户端请求来拿到token;
    2:服务器根据此用户id,加上签名,密钥加密,并返回给客户端;
    3:客户端在请求头中保存token;
    4:客户端下一次请求,服务器拿到token,并去解密;
    5:根据结果做不同操作。
     
    现在企业用JWT比较多,有机会再说这个东西。

  • 相关阅读:
    【Java基础】static关键字相关
    【Java基础】foreach循环
    【Java基础】可变参数
    Android的启动模式(下)
    Android的启动模式(上)
    Universal-Image-Loader完全解析(下)
    Universal-Image-Loader完全解析(上)
    布局优化之ViewStub、Include、merge使用分析
    人在千锋--网络学习之开发项目爱限免
    4_2网络学习第二天--XML解析
  • 原文地址:https://www.cnblogs.com/SunSAS/p/12468894.html
Copyright © 2011-2022 走看看