注册登陆

从注册登陆实例学习Cookie\Session\LocalStorage\SessionStorage\Cache-Control\Expires\ETag\Last-Modified

简介

从做一个注册、登陆、主页、服务端的过程,学习相关的前端知识。

注册 sign_up.html

登陆 sign_in.html

主页 index.html

服务端 server.js

效果

Sign_up页面

注册页面

注册表单

  1. 邮箱
  2. 密码
  3. 确认密码
  4. 注册按钮

Sign_in页面

登陆页面

登陆表单

  1. 邮箱
  2. 密码

Index主页

显示密码 你的密码是:xxx

代码

代码地址

注册流程

前端发请求

  1. 遍历 form 表单里 email、password、password_confirmation 的 3 个值
  2. 把 3 个值赋值给一个 hash { email:'123@jj.com', password:'123', password_confirmation:'123' }
  3. 通过 ajax 请求把 hash 作为请求体字符串发给服务器 $.post('/sign_up', hash)

后端发响应

  1. 拿到请求,进行 URI 解码、分割 hash 字符串、合成新 hash。
  2. 校验 email 格式,校验密码一致性,读取数据库检查 email 是否已经存在。
  3. 都通过,就把 email 和 password 新增写入数据库。给前端响应注册成功的 JSON 字符串。
  4. 若不通过,响应相应错误的 JSON 字符串。

前端收响应

  1. 拿到成功的200响应,在页面显示类似“恭喜你注册成功”之类的提示。
  2. 若拿到失败的400响应,则根据响应的 JSON 在页面展示“邮箱格式错误、邮箱已被使用、密码格式错误”之类的提示。

其他细节

  1. 前端也可以在发请求前,对 hash 请求体做验证。例如
  2. 用正则验证email、password的规则
  3. password === password_confirmation 验证密码一致性
  4. 遍历 email 字符串去空格
  5. 但是,无法验证 email 是否已被使用,因为数据在服务器。
  6. 前端的验证不是必须的,但是后端是肯定有验证的。

首次登陆流程

前端发请求

和注册一样,把 email、password 合成一个 hash

通过 ajax 把 hash 字符串发给服务器。$.post('/sign_in', hash)

后端发响应

  1. 拿到请求,进行 URI 解码、分割 hash 字符串、合成新 hash 。
  2. 遍历数据库的 email 和 password 做对比。
  3. 若不通过,响应相应错误的 JSON 字符串。
  4. 若通过,生成一个随机数的 SessionID,用一个名为 Session 的 hash 存下 {SessionID:email}这样的对照表。
  5. 后端再通过 Cookie 给前端发送 SessionID。

前端收响应

前端若是收到服务器发来的401响应和相关的 JSON,则提示用户相应的错误,如密码错误之类。

前端若是收到带 SessionID 的 Cookie 相应头,以及响应码 200,说明登陆成功。

带Cookie登陆流程

前端带cookie访问主页

得到 cookie 后,浏览器会请求同域名下的页面(如首页 index.html) 会自动带着 Cookie 。

后端接收带cookie的请求

后端收到 cookie,得到 SessionID,拿 SessionID 在 Session 哈希表里找到对应的 email,就能知道用户是谁。

于是根据用户,读取 index.html 文件为字符串,并替换部分内容(本实例是替换密码显示)。

把替换过部分内容的 index.html 字符串发给前端。

前端收到替换过部分内容的 index.html 字符串

前端收到这个“针对用户定制”的 index.html 字符串,就能显示用户的个人信息了,如昵称、地址等。

其他细节

Cookie 访问同一个域名时(同域名,没同源那么严格),都是会自动带上的。

其实 Session 不是必要的,Cookie 也能直接存用户的 email,Session + SessionID 只是为了给 email 一个中转,中转出一个随机数 SessionID,让客户端无法通过篡改 Cookie 去登陆别人的账户。(email很容易猜,SessionID是随机的很难猜)

cookie是什么

cookie是服务器发给客户端的一个字符串。

cookie的流程

前端登陆成功后,后端发送cookie给前端

response.setHeader('Set-Cookie', `sign_in_eamil=${email}`)

以后前端发什么请求都会带上cookie

服务器收到cookie再分割后,拿它和数据库的账户对比

再针对该用户的信息,替换针对性的网页内容。

cookie的特性

  1. 服务器通过 set-cookie 头给客户端一串字符串

  2. 客户端每次访问相同域名的网页时,必须带上这段字符串

  3. 客户端要在一段时间内保存这个cookie

  4. cookie是可以被修改作假的

  5. cookie默认有效期20分钟左右,默认在关闭页面后失效,但服务器可以设置 cookie 有效期

session

session是什么

session是服务器存储的一个哈希表。

为什么有session

因为cookie直接发送账户email,会被用户修改cookie,登陆其他人的email账户。

所以我们需要用session+随机数sessionID来存储email,用户只有session,是没办法知道对应的email,更没办法修改session来登陆其他mail了。

session的作用

服务器把 sessionID(随机数)通过 cookie 发送给客户端

客户端访问服务器时,服务器读取 sessionID

服务器有一块内存(哈希表)保存了所有 session

通过 sessionID 可以得到对应用户的隐私信息,如账号、email

session的缺点

session会占用一定内存。

本来cookie直接把用户的账号发给客户端,服务器就只需要在数据库存储账号密码就行了。

使用session后,要用session来存储用户账号,服务器就多存了一份数据,即session。

session的特性

  1. 服务器将 sessionID(随机数)通过 cookie 发送给客户端
  2. 客户端访问服务器时,服务器读取 sessionID
  3. 服务器有一块内存(哈希表)保存了所有 session
  4. 通过 sessionID 我们可以得到对应用户的隐私信息(账户、email)
  5. 这块内存(哈希表)就是服务器上的所有 session

localStorage

localStorage是什么

localStorage是浏览器存储的一个哈希表。

localStorage是html5提供的API。

localStorage只能存储字符串。

localStorage的基本API

localStorage.setItem('a','1')
localStorage.getItem('a')
localStorage.clear()

localStorage.setItem('jsonString',JSON.stringify({name:'hehe'}))

localStorage的特性

  1. localStorage 跟 HTTP 无关
  2. HTTP 不会带上 localStorage 的值
  3. 只有相同域名的页面才能互相读取 localStorage
  4. 每个域名 localStorage 最大存储量为 5MB 左右(每个浏览器不一样)
  5. 常用场景:记录有没有提示过用户,例如域名搬迁的首次提示。(没有用的信息,不能记录密码)
  6. localStorage 永久有效,除非用户清理缓存

sessionStorage

sessionStorage的特性

  1. sessionStorage 跟 HTTP 无关
  2. HTTP 不会带上 sessionStorage 的值
  3. 只有相同域名的页面才能互相读取 sessionStorage
  4. 每个域名 sessionStorage 最大存储量为 5MB 左右(每个浏览器不一样)
  5. sessionStorage 在用户关闭页面(会话结束)后就失效

cookie、session、localStorage、sessionStorage的关系

  1. session 一般是通过 cookie 实现的,即服务器通过 cookie 把 sessionID 发给客户端。
  2. localStorage 是跟 HTTP 无关的,访问服务器不会带上 localStorage。sessionStorage同理。
  3. sessionStorage 和 localStorage 差不多,但是 sessionStorage 在关闭页面就失效。

session的2种实现方法

session用cookie的实现方法

服务器把sessionID发cookie请求头。

response.setHeader('Set-Cookie', `sessionId=${sessionId}`)

前端不用发sessionID,因为cookie自带,服务器能收到。

$.post('/sign_in', hash)
  .then((response) => {
    window.location.href = '/'
  }, (request) => {
    alert('邮箱与密码不匹配')
  })

session不用cookie的实现方法

服务器直接发sessionID作为响应体

response.write(`sessionId=${sessionId}`)

前端把sessionID存在localStorage,并通过查询参数把sessionID作为请求体发送给服务器。

$.post('/sign_in', hash)
  .then((response) => {
    let object = JSON.parse(response)
    localStorage.setItem('sessionId', object.sessionId)
    window.location.href = `/?sessionId=${object.sessionId}`
  }, (request) => {
    alert('邮箱与密码不匹配')
  })

服务器通过查询参数获取sessionID

let mySession = sessions[query.sessionId]

Cache-Control 缓存控制

同一URL请求,浏览器已经缓存过,就不再发该请求,直接本地加载缓存。

服务器代码

if (path === '/main.js') {
  let string = fs.readFileSync('/main.js', 'utf8')
  response.setHeader('Content-type', 'application/javascript;charset=utf8')
  // Cache-Control 缓存控制
  response.setHeader('Cache-Control', 'max-age=30')
  response.statusCode = 200
  response.write(string)
  response.end()
}

一般来说,HTML不设置缓存,CSS和JS的缓存时间设置10年。

服务器要更新CSS和JS的话,修改URL,服务端和前端的URL查询参数加上v=2这样:

http://www.baidu.com/main.js?v=2

Expires

跟 Cache-Control 差不多,时间设置是用格林尼治时间点。

服务器代码

// 缓存控制,格林尼治时间2025年前别再请求这个文件
response.setHeader('Expires', 'Wed, 21 Oct 2025 07:28:00 GMT')

Expires 设置的是本地时间点,如果用户本地时间被修改到2026年这样,Expires 就没用了。

ETag

服务器通过ETag响应头发文件的md5给客户端,客户端下次发请求会带上文件的md5,如果md5一致,则不再下载文件,直接从客户端本地读取文件。

if (path === '/main.js') {
  let string = fs.readFileSync('/main.js', 'utf8')
  response.setHeader('Content-type', 'application/javascript;charset=utf8')
  let fileMd5 = md5(string)
  // ETag
  response.setHeader('ETag', 'fileMd5')
  if (request.headers['if-none-match'] === fileMd5) {
    response.statusCode = 304
  } else {
    response.write(string)
  }
  response.end()
}

ETag和Cache-Control的区别在于

ETag要发请求然后判读要不要下载,只响应304,不发响应体。

Cache-Control是直接不请求。

Last-Modified / If-Modified-Since

在浏览器第一次请求某一个 URL 时,服务器端的返回状态会是200,内容是你请求的资源,同时有一个 Last-Modified 的属性标记此文件在服务期端最后被修改的时间,格式类似这样:

Last-Modified: Mon, 30 Nov 2015 23:21:37 GMT

浏览器第二次请求此 URL 时,根据 HTTP 协议的规定,浏览器会向服务器传送 If-Modified-Since 报头,询问该时间之后文件是否有被修改过:

If-Modified-Since: Mon, 30 Nov 2015 23:21:37 GMT

如果服务器端的资源没有变化,则自动返回 HTTP 304 (Not Changed)状态码,内容为空,这样就节省了传输数据量。当服务器端代码发生改变或者重启服务器时,则重新发出资源,返回和第一次请求时类似。从而保证不向客户端重复发出资源,也保证当服务器有变化时,客户端能够得到最新的资源。