Web发展史

在线购物,博客,视频等网站都需要管理会话,需要记录保存用户的状态和信息,然而HTTP请求是无状态的,如果每次请求都是一个新的HTTP协议,那么用户第一次发起请求,登录成功后,每次打开一个页面都需要重新登录。服务端无法知道客户之前的状态,对于交互式的web应用,使用一种技术保存用户信息是有必要的。为了解决这个问题,演进出了Cookie,Session。

其中的一个解决方案是使用会话标识(session id)。然而Session id只在保留在了一个节点上,如果由于负载均衡转发到另一个节点上,就会产生Session丢失的问题

Session Sticky原理:负载均衡会将具有相同的ip的请求转发到同一个节点上去,这样就可以在小规模的场景下解决Session丢失的问题,比如修改Nginx负载均衡路由策略

upstream example{
server ip:port;
server ip:port;
ip_hash; 
}

cookie是浏览器实现的一种数据存储功能。cookie以k,v形式保存到某个目录下的文本文件内,下一次请求同一网站时会把该cookie发送给服务器。由于cookie是存在客户端上的,所以浏览器加入了一些限制确保cookie不会被恶意使用,同时每个域的cookie数量是有限的,一般不超过4KB。使用Cookie实际上只能存储一小段的文本信息,另外Cookie中的数据只能以字符串的形式保存一小段的文本信息。

Session

服务器使用session把用户的信息临时保存在了服务器上,用户离开网站后session会被销毁。这种用户信息存储方式相对cookie来说更安全,可是session有一个缺陷:如果web服务器做了负载均衡,那么下一个操作请求到了另一台服务器的时候session会丢失。对于大型项目而言,不推荐使用Session Sticky,基于分布式Memcache的Session共享方案比较合适。为什么使用Memcache链接

另外,Session可以用于共享同一用户不同请求间的数据,服务端以ConcurrentMap<String, Object>的形式存储在StandardHttpSession中。相比Cookie而言,存储在服务端的技术更加安全,但是对服务器产生了额外的负担。

在项目中,通常是Cookie与Session结合使用,通过Cookie保存对安全性要求不高的内容,比如用户最近浏览的文章,商品,token。而Session用于记录用户登入信息。

为什么使用token进行身份验证

无状态、可扩展、支持移动设备、跨程序调用、安全

token的生成

当用户第一次登入时,服务端验证用户密码并生成一串字符串,这个字符串将被作为token存储到HttpServletResponse,返回保存客户端的Cookie中,这样做的好处是保持登入状态无需在Cookie中存储用户名密码。

当用户再次访问时,服务端通过HttpServletRequest.getCookies()查询用户请求中携带的cookie数据,通过和缓存中的token比较并验证有效期,如果验证成功执行对应的业务。

服务端token存储形式

浏览器可以利用Cookie以键值对的方式存储token,比如token-name:token的形式。比如,可以将Session id作为token写入到Cookie,并添加到HttpServletResponse

 public void writeLoginToken(HttpServletResponse response, String token){
        Cookie ck = new Cookie(COOKIE_NAME,token);
        ck.setDomain(COOKIE_DOMAIN);
        ck.setPath("/");//代表设置在根目录
        ck.setHttpOnly(true);
        ck.setMaxAge(60 * 60 * 24 * 365);//如果是-1,代表永久
        response.addCookie(ck);
    }

对于服务器端,可以利用缓存存储token与字符串形式的用户对象,这时token也就是Session id作为键,而将存有权限用户名等信息的User对象转化为字符串并存储在缓存中。

FASTjson能够很好地支持对象与String之间的转换,Memcached支持缓存自动失效。同时当用户请求通过负载均衡转发到任一节点,该节点可以通过查询缓存的数据验证token的有效性,并获取到对应的用户的id、权限等信息。

    public <T> boolean set(KeyPrefix prefix, String key,  T value ,int exTime) {
        Jedis jedis = jedisPool.getResource();
        String str = JSON.toJSONString(value);//FastJson
        if (str == null || str.length() <= 0) {
            return false;
        }
        //为了演示,省略了一些逻辑判断
        String realKey = prefix.getPrefix() + key;
        if (exTime == 0) {
            jedis.set(realKey, str);
        } else {
            //设置过期时间
            jedis.setex(realKey, exTime, str);
        }
        return true;
    }
服务器端不存储token的解决方案:

不保存token方案的关键点在于需要验证token来自于服务器生成,还是来自恶意伪造。相比之下,缓存token的方案不需要考虑这点,在缓存中直接查询即可。

此时,token必须能够被验证,下面贴一个知乎上的token生成方案【在该方案的基础上补上时间戳】:密钥只有服务器知道,那么通过HMAC-SHA256和密钥对数据和时间戳签名,如此服务器在验证时可以确保该token来自服务器,并且通过时间戳验证该token是否失效。

但是该方案的缺点是,一旦密钥丢失,通过HMAC-SHA256和密钥可以伪造任何用户的token,所以这种方案并不安全。

 posted on 2020-10-26 15:24  春秋流千事  阅读(127)  评论(0)    收藏  举报