Cookie、Session、Token那点事儿

  1、什么是Cookie?
  
  Cookie 技术产生源于 HTTP 协议在互联网上的急速发展。随着互联网时代的策马奔腾,带宽等限制不存在了,人们需要更复杂的互联网交互活动,就必须同服务器保持活动状态(简称:保活)。
  
  于是,在浏览器发展初期,为了适应用户的需求技术上推出了各种保持 Web 浏览状态的手段,其中就包括了 Cookie 技术。Cookie 在计算机中是个存储在浏览器目录中的文本文件,当浏览器运行时,存储在 RAM 中发挥作用 (此种 Cookies 称作 Session Cookies),一旦用户从该网站或服务器退出,Cookie 可存储在用户本地的硬盘上 (此种 Cookies 称作 Persistent Cookies)。
  
  Cookie 起源:
  
  1993 年,网景公司雇员 Lou Montulli 为了让用户在访问某网站时,进一步提高访问速度,同时也为了进一步实现个人化网络,发明了今天广泛使用的 Cookie。(所以,适当的偷懒也会促进人类计算机发展史的一小步~)
  
  Cookie时效性:
  
  目前有些 Cookie 是临时的,有些则是持续的。临时的 Cookie 只在浏览器上保存一段规定的时间,一旦超过规定的时间,该 Cookie 就会被系统清除。
  
  Cookie使用限制:
  
  Cookie 必须在 HTML 文件的内容输出之前设置;不同的浏览器 (Netscape Navigator、Internet Explorer) 对 Cookie 的处理不一致,使用时一定要考虑;客户端用户如果设置禁止 Cookie,则 Cookie 不能建立。 并且在客户端,一个浏览器能创建的 Cookie 数量最多为 300 个,并且每个不能超过 4KB,每个 Web 站点能设置的 Cookie 总数不能超过 20 个。
  
  执行流程:
  
  复制代码
  
  A:首先,客户端会发送一个http请求到服务器端。
  
  B: 服务器端接受客户端请求后,发送一个http响应到客户端,这个响应头,其中就包含Set-Cookie头部。
  
  C:在客户端发起的第二次请求(注意:如果服务器需要我们带上Cookie,
  
  我们就需要在B步骤上面拿到这个Cookie然后作为请求头一起发起第二次请求),
  
  提供给了服务器端可以用来唯一标识客户端身份的信息。
  
  这时,服务器端也就可以判断客户端是否启用了cookies。
  
  尽管,用户可能在和应用程序交互的过程中突然禁用cookies的使用,
  
  但是,这个情况基本是不太可能发生的,所以可以不加以考虑,这在实践中也被证明是对的。
  
  复制代码
  
  为了方便理解,可以先看下这张流程执行图加深概念
  
  那么,在浏览器上面的请求头和Cookie在那?下图给大家截取了其中一种。
  
  那么,上面都是谈浏览器上的Cookie,那么在Android开发中,我们该如何去管理和使用Cookie?
  
  Okhttp作为经典到爆的网络框架,它的API(本文是基于Okhttp3.0版本以上,3.0以下的版本API有所不同)是通过OkhttpClient中的CookieJar或者拦截器去管理Cookie的。理论上,我们只需在构建单例OkhttpClient的时候,设置cookiejar或者拦截器,然后具体的操作(具体的操作也就是保存Cookie,取Cookie),Okhttp框架就会帮我们自动管理Cookie。
  
  如下图:
  
  这是其中一种通过集合的增查特性,就可以简单有效的帮我们管理Cookie。但我们还是要通过源代码去一探究竟。首先,CookieJar是一个接口。
  
  英文注释翻译过来就是(对应段落翻译):
  
  CookieJar这个接口为HTTP cookies提供了强大的支持和相关策略。
  
  这种策略的实现作用会负责选择接受和拒绝那些cookie。一个合理的策略是拒绝所有的cookie,尽管这样会干扰需要cookie的基于会话的自身身份验证方案。
  
  作为Cookie的持久性,该接口的实现也必须要提供Cookie的存储。一种简单的实现可以将cookie存储在内存中;复杂的系统可以使用文件系统用于保存已接受的cookie的数据库。这里的链接https://tools.ietf.org/html/rfc6265 指定cookie存储模型更新和过期的cookie的策略。
  
  所以,Okhttp的源码告知我们可以将cookie存储在内存中;复杂的系统可以使用文件系统用于保存已接受的cookie的数据库。因此,我们就可以通过Map去简单的管理和使用。
  
  继续分析CookieJar接口里面的方法,依旧上源码
  
  里面有方法一个是saveFromResponse(HttpUrl url, List cookies)、loadForRequest(HttpUrl url)
  
  saveFromResponse方法翻译:根据这个jar的方法,可以将cookie从一个HTTP响应保存到这里。
  
  请注意,如果响应,此方法可能被称为第二次HTTP响应,包括一个追踪。对于这个隐蔽的HTTP特性,这里的cookie只包含其追踪的cookie。简单点理解就是如果我们使用了这个方法,就会进行追踪(说白了就是客户端请求成功以后,在响应头里面去存cookie)
  
  loadForRequest方法翻译:将cookie从这个方法加载到一个HTTP请求到指定的url。这个方法从网络上返回的结果可能是一个空集合。简单的实现将返回尚未过期的已接受的cookie去进行匹配。(说白了就是加载url的时候在请求头带上cookie)。
  
  这样,我们通过以上代码就可以完成了Cookie的非持久化。什么,非持久化,这又是神马?
  
  继续给大家科普,在上面说道,Cookie是具有时效性的,所以,Cookie的管理又分为持久化Cookie和非持久化Cookie。非持久化Cookie存储在内存中,也就意味着,其生命周期基本和app保持一致,app关闭后,Cookie丢失。而持久化Cookie则是存储在本地磁盘中,app关闭后不丢失。那么,如果我们要使用Cookie的持久化策略,思想可以参考上面的非持久化策略,只需要将存储方式改一下即可:
  
  A:通过响应拦截器从response取出cookie并保存到本地,通过请求拦截器从本地取出cookie并添加到请求中
  
  B:自定义CookieJar,在saveFromResponse()中保存cookie到本地,在loadForRequest()从本地取出cookie。
  
  那么在这里主要介绍如何通过Okhttp逼格值较高的拦截器去进行持久化cookie操作。
  
  保存cookie拦截器-1
  
  保存cookie拦截器-2
  
  这个SaveCookiesInterceptor拦截器的实现,是首先从response获取set-cookie字段的值,然后通过SharedPreferences保存在本地。
  
  将Cookie添加到请求头
  
  AddCookiesInterceptor请求拦截器,这个拦截的作用就是判断如果该请求存在cookie,则为其添加到Header的Cookie中。
  
  写好这两个拦截器之后,我们只需要将实例对象放进OkhttpClient里面即可快速的完成Cookie持久化操作。(PS:这两个拦截器在同步Cookie的时候也是超级好用)
  
  Okhttp使用cookie拦截器
  
  2、Session
  
  Session是对于服务端来说的,客户端是没有Session一说的。Session是服务器在和客户端建立连接时添加客户端连接标志,最终会在服务器软件(Apache、Tomcat、JBoss)转化为一个临时Cookie发送给给客户端,当客户端第一请求时服务器会检查是否携带了这个Session(临时Cookie),如果没有则会添加Session,如果有就拿出这个Session来做相关操作。
  
  在这里引用别人家的一个小故事来加深印象:
  
  在说session是啥之前,我们先来说说为什么会出现session会话,它出现的机理是什么?
  
  我们知道,我们用浏览器打开一个网页,用到的是HTTP协议,了解计算机的应该都知道这个协议,它是无状态的,什么是无状态呢?就是说这一次请求和上一次请求是没有任何关系的,互不认识的,没有关联的。但是这种无状态的的好处是快速。所以就会带来一个问题就是,我希望几个请求的页面要有关联,比如:我在www.a.com/login.php里面登陆了,我在www.a.com/index.php 也希望是登陆状态,但是,这是2个不同的页面,也就是2个不同的HTTP请求,这2个HTTP请求是无状态的,也就是无关联的,所以无法单纯的在index.php中读取到它在login.php中已经登陆了!
  
  那咋搞呢?我不可能这2个页面我都去登陆一遍吧。或者用笨方法这2个页面都去查询数据库,如果有登陆状态,就判断是登陆的了。这种查询数据库的方案虽然可行,但是每次都要去查询数据库不是个事,会造成数据库的压力。所以正是这种诉求,这个时候,一个新的客户端存储数据方式出现了:cookie。cookie是把少量的信息存储在用户自己的电脑上,它在一个域名下是一个全局的,只要设置它的存储路径在域名www.a.com下 ,那么当用户用浏览器访问时,php就可以从这个域名的任意页面读取cookie中的信息。所以就很好的解决了我在www.a.com/login.php页面登陆了,我也可以在www.a.com/index.php获取到这个登陆信息了。
  
  同时又不用反复去查询数据库。虽然这种方案很不错,也很快速方便,但是由于cookie 是存在用户端,而且它本身存储的尺寸大小也有限,最关键是用户可以是可见的,并可以随意的修改,很不安全。那如何又要安全,又可以方便的全局读取信息呢?于是,这个时候,一种新的存储会话机制:session 诞生了。
  
  Session 就是在一次会话中解决2次HTTP的请求的关联,让它们产生联系,让2两个页面都能读取到找个这个全局的session信息。session信息存在于服务器端,所以也就很好的解决了安全问题。
  
  3、Token
  
  token是用户身份的验证方式,我们通常叫它:令牌。最简单的token组成:uid(用户唯一的身份标识)、time(当前时间的时间戳)、sign(签名,由token的前几位+盐以哈希算法压缩成一定长的十六进制字符串,可以防止恶意第三方拼接token请求服务器)。还可以把不变的参数也放进token,避免多次查库。
  
  应用场景:
  
  复制代码
  
  A:当用户首次登录成功(注册也是一种可以适用的场景)之后, 服务器端就会生成一个 token 值,这个值,会在服务器保存token值(保存在数据库中),再将这个token值返回给客户端.
  
  B:客户端拿到 token 值之后,进行本地保存。(SP存储是大家能够比较支持和易于理解操作的存储)
  
  C:当客户端再次发送网络请求(一般不是登录请求)的时候,就会将这个 token 值附带到参数中发送给服务器.
  
  D:服务器接收到客户端的请求之后,会取出token值与保存在本地(数据库)中的token值做对比
  
  对比一:如果两个 token 值相同, 说明用户登录成功过!当前用户处于登录状态!
  
  对比二:如果没有这个 token 值, 则说明没有登录成功.
  
  对比三:如果 token 值不同: 说明原来的登录信息已经失效,让用户重新登录.
  
  复制代码
  
  Cookie和Session的区别:
  
  复制代码
  
  1、cookie数据存放在客户的浏览器上,session数据放在服务器上。
  
  2、cookie不是很安全,别人可以分析存放在本地的cookie并进行cookie欺骗,考虑到安全应当使用session。
  
  3、session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能,考虑到减轻服务器性能方面,应当使用cookie。
  
  4、单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie。
  
  5、所以个人建议:
  
  将登陆信息等重要信息存放为session
  
  其他信息如果需要保留,可以放在cookie中
  
  复制代码
  
  Token 和 Session 的区别:
  
  session和 token并不矛盾,作为身份认证token安全性比session好,因为每个请求都有签名还能防止监听以及重放攻击,而session就必须靠链路层来保障通讯安全了。如上所说,如果你需要实现有状态的会话,仍然可以增加session来在服务器端保存一些状态。
  
  App通常用restful api跟server打交道。Rest是stateless的,也就是app不需要像browser那样用cookie来保存session,因此用session token来标示自己就够了,session/state由api server的逻辑处理。如果你的后端不是stateless的rest api,那么你可能需要在app里保存session.可以在app里嵌入webkit,用一个隐藏的browser来管理cookie session.
  
  Session是一种HTTP存储机制,目的是为无状态的HTTP提供的持久机制。所谓Session认证只是简单的把User信息存储到Session里,因为SID的不可预测性,暂且认为是安全的。这是一种认证手段。而Token,如果指的是OAuth Token或类似的机制的话,提供的是 认证 和 授权 ,认证是针对用户,授权是针对App。其目的是让 某App有权利访问 某用户 的信息。这里的Token是唯一的。不可以转移到其它App上,也不可以转到其它 用户 上。转过来说Session。Session只提供一种简单的认证,即有此SID,即认为有此User的全部权利。是需要严格保密的,这个数据应该只保存在站方,不应该共享给其它网站或者第三方App。所以简单来说,如果你的用户数据可能需要和第三方共享,或者允许第三方调用API接口,用Token。如果永远只是自己的网站,自己的App,用什么就无所谓了。
  
  token就是令牌,比如你授权(登录)一个程序时,他就是个依据,判断你是否已经授权该软件;cookie就是写在客户端的一个txt文件,里面包括你登录信息之类的,这样你下次在登录某个网站,就会自动调用cookie自动登录用户名;session和cookie差不多,只是session是写在服务器端的文件,也需要在客户端写入cookie文件,但是文件里是你的浏览器编号.Session的状态是存储在服务器端,客户端只有session id;而Token的状态是存储在客户端。
  
  //根据名字查询数据并删除
  
  UserDTO userDTO = mongoTemplate.findOne(Query.query(Criteria.where("name").is(name)), UserDTO.class);
  
  //remove方法不支持级联删除所以要单独删除子数据
  
  List<AddressDTO> addressList = userDTO.getAddressList();
  
  for (AddressDTO addressDTO : addressList) {
  
  mongoTemplate.remove(addressDTO);
  
  }
  
  //删除主数据
  
  mongoTemplate.remove(www.hendaylpt.cn  userDTO);
  
  }
  
  /**
  
  * 更新文档
  
  * @param userDTO
  
  */
  
  public void update(UserDTO userDTO) {
  
  mongoTemplate.updateFirst(Query.query(Criteria.where("name").is(userDTO.getName())), Update.update("age", userDTO.getAge()), UserDTO.class);
  
  }
  
  /**
  
  * 查询文档
  
  * @param name
  
  */
  
  public void find(String name) {
  
  Sort sort = new Sort(Sort.www.chenghylpt.com Direction.DESC, "name");
  
  List<UserDTO> userDTOS = mongoTemplate.find(Query.query(Criteria.where("name").is(name)), UserDTO.class);
  
  //基于sort排序使用findOne查询最新一条记录
  
  UserDTO userDTO = mongoTemplate.findOne(Query.query(Criteria.where("name").is(name)).with(sort), UserDTO.class);
  
  //模糊查询
  
  List<UserDTO> userDTOList = mongoTemplate.find(Query.query(Criteria.where("name").is(name).regex(name)).with(sort), UserDTO.class);
  
  //分页查询
  
  Pageable pageable = PageRequest.of(3, 20, sort);
  
  List<UserDTO> userDTOPageableList = mongoTemplate.find(Query.query(Criteria.where("name").is(name)).with(pageable), UserDTO.class);
  
  //总数
  
  long conut = mongoTemplate.count(www.jiujiuylzc.cn Query.query(Criteria.where("name").is(name)), UserDTO.class);
  
  Page<UserDTO> page = new PageImpl(userDTOPageableList, pageable, conut);
  
  }
  
  }
  
  NOTE:
  
    在开发中,如果从任何MongoDB操作返回的com.mongodb.WriteResult包含错误,则可以方便地记录或引发异常。 通常,在开发过程中很容易忘记执行此操作,然后最终得到一个看似运行成功的App,但实际上该数据库操作发生异常,没执行成功。 可以将MongoTemplate的WriteResultChecking属性设置为以下值之一:
  
  EXCEPTION:引发Exception
  
  NONE:不执行任何操作,默认值
  
    对于更高级的情况,可以将每个操作设置不同的WriteConcern值(用于删除,更新,插入和保存操作),则可以在MongoTemplate上配置WriteConcernResolver的策略接口。 由于MongoTemplate用于持久化POJO,因此WriteConcernResolver允许您创建一个策略,该策略可以将特定的POJO类映射到WriteConcern值。
  
  WriteConcernResolver接口:
  
  public interface WriteConcernResolver {
  
  WriteConcern resolve(MongoAction action);
  
  }
  
  自定义WriteConcernResolver接口,实现不同WriteConcern策略:
  
  private class MyAppWriteConcernResolver implements WriteConcernResolver {
  
  public WriteConcern resolve(MongoAction action) {
  
  if (action.getEntityClass(www.yifa5yl.com).getSimpleName().contains("UserDTO")) {
  
  return WriteConcern.NONE;
  
  } else if (action.getEntityClass().getSimpleName().contains("Metadata")) {
  
  return WriteConcern.JOURNAL_SAFE;
  
  }
  
  return action.getDefaultWriteConcern();
  
  }
  
  }
  
  5. 常用的类以及方法解析
  
  5.1 MongoClient、ServerAddress、MongoCredential以及MongoClientOptions
  
    基于ServerAddress单机或者Replica Set在使用MongoClient连接mongodb数据库注册mongo实例,在注册示例中可能要使得MongoCredential账号密码验证以及使用MongoClientOptions配置mongodb其他的参数。
  
  MongoClient常用的构造器方法:
  
  public MongoClient(String host){www.jiuzhoyulpt.cn}
  
  public MongoClient(MongoClientURI uri){}
  
  public MongoClient(String host, MongoClientOptions options) {}
  
  public MongoClient(ServerAddress addr, MongoCredential credential, MongoClientOptions options){}
  
  public MongoClient(List<ServerAddress> seeds, MongoCredential credential, MongoClientOptions options){}
  
  5.2 MongoTemplate
  
  使用MongoTemplate结合Sort、Criteria、Query、Update以及分页Pageable类灵活地进行对mongodb数据库进行增删改查。
  
  query方法:
  
  //根据查询条件查询
  
  public <T> List<T> find(Query query, Class<T> entityClass){}
  
  //根据查询条件查询返回一条记录
  
  public <T> <T>findOne(Query query, Class<T> entityClass){}
  
  //查询该collection所有记录
  
  public <T> List<T> findAll(Class<T> entityClass){}
  
  insert方法:
  
  //新增一条记录
  
  public <T> T insert(T objectToSave){}
  
  //在collectionName中新增一条记录
  
  public <T> T insert(T objectToSave, String collectionName) {}
  
  //
  
  public <T> T save(T objectToSave){}
  
  remove方法:
  
  //根据Object删除
  
  public DeleteResult remove(Object object) {}
  
  //根据查询条件进行删除
  
  public DeleteResult remove(Query query, Class<?> entityClass){}
  
  update方法:
  
  //
  
  public UpdateResult upsert(Query query, Update update, Class<?> entityClass) {}
  
  //更新查询出来的第一条记录
  
  public UpdateResult updateFirst(Query query, Update update, String collectionName) {}
  
  5.3 Sort
  
  Sort查询排序类。Sort类常用方法:
  
  //构造方法创建一个排序。direction为排序方向的枚举类型,properties为排序字段数组
  
  Sort(Sort.Direction direction, String... properties)
  
  //多个排序条件链接
  
  and(Sort sort)
  
  //返回升序排列对象
  
  ascending()
  
  //返回降序排列对象
  
  descending()
  
  5.4 Criteria
  
  Criteria查询条件类,类似于SQL的where,常用方法:
  
  //声明定义查询条件,且为静态方法
  
  where(String key)
  
  //与操作
  
  and(String key)
  
  //正则表达式,即可为模糊查询
  
  regex(String re)
  
  //包含
  
  in(Object... o)
  
  //大于
  
  gt(Object o)
  
  //大于等于
  
  gte(Object o)
  
  //等于
  
  is(Object o)
  
  //小于
  
  lt(Object o)
  
  //小于等于
  
  lte(Object o)
  
  //非
  
  not()
  
  //创建与操作
  
  andOperator(Criteria... criteria)
  
  5.5 Query
  
  Query查询对象,具有查询的全部信息,其中包括筛选条件、排序、返回数量等。常用的方法:
  
  //定义查询对象,静态方法
  
  query(CriteriaDefinition criteriaDefinition)
  
  //在本次查询添加一个CriteriaDefinition查询条件
  
  addCriteria(CriteriaDefinition criteriaDefinition)
  
  //添加一个Sort排序对象
  
  with(Sort sort)
  
  //添加一个Pageable分页对象、通常情况下,分页和排序一起使用。
  
  with(Pageable pageable)
  
  6.常用注解
  
  注解    解析
  
  @Id    用于标记id字段,没有标记此字段的实体也会自动生成id字段,但是我们无法通过实体来获取id。id建议使用ObjectId类型来创建
  
  @Document    用于标记此实体类是mongodb集合映射类
  
  @DBRef    用于指定与其他集合的级联关系,但是需要注意的是并不会自动创建级联集合
  
  @Indexed    用于标记为某一字段创建索引
  
  @CompoundIndex    用于创建复合索引
  
  @TextIndexed:    用于标记为某一字段创建全文索引
  
  @Language    指定documen语言
  
  @Transient:    被该注解标注的,将不会被录入到数据库中。只作为普通的javaBean属性
  
  @Field:    用于指定某一个字段映射到数据库中的名称

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值