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是浏览器实现的一种数据存储功能。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
浙公网安备 33010602011771号