task44 Cookie与登录注册

请预习 Cookie、Session、Cache-Control 等 HTTP 知识

课上的代码:https://github.com/FrankFang/sign-in-demo

要点:

Cookie 的特点

  1. 服务器通过 Set-Cookie 响应头设置 Cookie
  2. 浏览器得到 Cookie 之后,每次请求都要带上 Cookie
  3. 服务器读取 Cookie 就知道登录用户的信息(email)

问题

  1. 我在 Chrome 登录了得到 Cookie,用 Safari 访问,Safari 会带上 Cookie 吗
    no
  2. Cookie 存在哪
    Windows 存在 C 盘的一个文件里
  3. Cookie会被用户篡改吗?
    可以,下节课会讲 Session 来解决这个问题,防止用户篡改
  4. Cookie 有效期吗?
    默认有效期20分钟左右,不同浏览器策略不同
    后端可以强制设置有效期,具体语法看 MDN
  5. Cookie 遵守同源策略吗?
    也有,不过跟 AJAX 的同源策略稍微有些不同。
    当请求 qq.com 下的资源时,浏览器会默认带上 qq.com 对应的 Cookie,不会带上 baidu.com 对应的 Cookie
    当请求 v.qq.com 下的资源时,浏览器不仅会带上 v.qq.com 的Cookie,还会带上 qq.com 的 Cookie
    另外 Cookie 还可以根据路径做限制,请自行了解,这个功能用得比较少。

1. 复习HTTP请求响应相关初级知识

这里的cookie、cache-control指的是响应头中的


响应头.png

image.png
http请求和响应.png
js发请求.png
  • js发请求
//js发请求(使用XMLHttpRequest)
var request = new XMLHttpRequest()
request.open('get','/xxx')  //配置request(get请求)
request.send()
//request.open('post','/xxx')  //配置request(post请求)
//request.send('a=1&b=2')
request.onreadystatechange=function(){
  if(request.readyState===4){
    console.log('请求响应都完成了')
    if(request.status >= 200 && request.status<300 ){
      console.log('请求成功')
    }
  }
}
  • Node.js接收请求并响应


    image.png
// 后端代码
if(path==='/xxx'){
    response.statusCode = 200
    response.setHeader('Content-Type', 'text/json;charset=utf-8')
    response.setHeader('Access-Control-Allow-Origin', 'http://frank.com:8001')
    response.write(`
    {
      "note":{
        "to": "小谷",
        "from": "方方",
        "heading": "打招呼",
        "content": "hi"
      }
    }
    `)
    response.end()
}

2. 实现注册登录的功能

注册页面

客户端代码sign_up.html

hash数据.png

服务端代码server.js
客户端请求代码

promise????
此时发送post请求,服务端会返回什么结果?
——选B 一串html

服务端代码server.js更改

服务端代码更改如上图,此时发送post请求,服务端会返回什么结果?
——选A 404

那么Node.js怎么读取post请求的数据?然后做相应的处理呢?


google(node http get post data)

获取并打印请求体.png

获取用户请求的第四部分:
有点复杂,因为请求体是一段一段上传的,不是一下子上传的,所以要监听data事件,当监听到end的时候,将之前的数据接收到的数据都连接起来,获取完整的请求数据。

打印的请求体打印在服务端的控制台


控制台打印请求体
使用promise封装一个读取请求body的方法readBody
定义readBody
使用readBody
  • 服务端解析请求体
//服务端解析请求体
把字符串格式的:email=1&password=2&password_confirmation=3
解析成hash格式的:{email:'1',password:'2',password_confirmation:'3'}
服务端解析请求体.png
//ES5的写法
let email=hash[email]
let password=hash[password]
let password_confirmation=hash[password_confirmation]
//ES6的写法
let {email,password,password_confirmation}=hash  //ES6的写法

再加上客户端和服务端的数据验证、提示等
详见 课上的代码:https://github.com/FrankFang/sign-in-demo

//服务端代码
  }else if(path === '/sign_up' && method === 'POST'){
    readBody(request).then((body)=>{
      let strings = body.split('&') // ['email=1', 'password=2', 'password_confirmation=3']
      let hash = {}
      strings.forEach((string)=>{
        // string == 'email=1'
        let parts = string.split('=') // ['email', '1']
        let key = parts[0]
        let value = parts[1]
        hash[key] =value // hash['email'] = '1'
      })
      let {email, password, password_confirmation} = hash
      if(email.indexOf('@') === -1){//校验邮箱格式
        response.statusCode = 400
        response.setHeader('Content-Type', 'application/json;charset=utf-8')
        response.write(`{
          "errors": {
            "email": "invalid"
          }
        }`)
      }else if(password !== password_confirmation){//校验密码和确认密码是否一致
        response.statusCode = 400
        response.write('password not match')
      }else{//通过校验,返回200,注册成功
        response.statusCode = 200
        response.write('success sign up')
      }
      response.end()
    })
  }
//客户端代码
let $form = $('#signUpForm')
$form.on('submit', (e)=>{
  e.preventDefault()
  let hash = {}
  let need = ['email', 'password', 'password_confirmation']
  need.forEach((name)=>{
    let value = $form.find(`[name=${name}]`).val()
    hash[name] = value
  })
  $form.find('.error').each((index, span)=>{
    $(span).text('')
  })
  //校验各个字段非空,为空的话显示对应的提示信息并return
  if(hash['email'] === ''){
    $form.find('[name="email"]').siblings('.error')
      .text('填邮箱呀同学')
    return
  }
  if(hash['password'] === ''){
    $form.find('[name="password"]').siblings('.error')
      .text('填密码呀同学')
    return
  }
  if(hash['password_confirmation'] === ''){
    $form.find('[name="password_confirmation"]').siblings('.error')
      .text('确认密码呀同学')
    return
  }
  //校验密码和确认密码是否一致
  if(hash['password'] !== hash['password_confirmation']){
    $form.find('[name="password_confirmation"]').siblings('.error')
      .text('密码不匹配')
    return
  }
  $.post('/sign_up', hash)  
    .then((response)=>{
      console.log(response)
    }, (request)=>{
      let {errors} = request.responseJSON
      if(errors.email && errors.email === 'invalid'){
        $form.find('[name="email"]').siblings('.error')
          .text('邮箱格式错误')
      }
    })
})

这些校验前后端都要做!!!不能只是前端做,后端也要做的
因为可以通过curl不通过前端的js直接就请求到后端,所以后端一定要做相应的校验!!!


3. Cookie

这里有个问题,就是邮箱里面的@符号,在http请求中会变成%40
你以为有@,其实实际上没有@

服务端server.js打印email字段

服务端控制台显示email字段

不做处理的话,会一直报邮箱格式错误。怎么处理呢?
——decodeURIComponent : 将已编码 URI 中所有能识别的转义序列转换成原字符。

decodeURIComponent
当服务器发现邮箱、密码之类的都格式没问题了之后,需要做什么?
  • 创建文件db/users 用来存用户数据,当做数据库
  • 注册的时候验证没问题就把邮箱和密码存到数据库中
  • 新增users数据的时候需要考虑这个用户是否已经存在
  • 数据库中不应该存用户的明文密码,这样不安全,应该存加密后的密码,下次登录的时候将用户传过来的明文密码加密后和数据库中的密文密码比对,就可以判断密码是否正确了
  • 加一个登录页面
//注册接口服务端
//邮箱密码什么的都验证没问题后
}else{
  var users = fs.readFileSync('./db/users', 'utf8')
  try{
    users = JSON.parse(users) // []
  }catch(exception){
    users = []
  }
  let inUse = false
  for(let i=0; i<users.length; i++){
    let user = users[i]
    if(user.email === email){
      inUse = true
      break;
    }
  }
  if(inUse){
    response.statusCode = 400
    response.write('email in use')
  }else{
    users.push({email: email, password: password})
    var usersString = JSON.stringify(users)
    fs.writeFileSync('./db/users', usersString)
    response.statusCode = 200
  }
}
response.end()
添加登录页面
服务端代码

登录页面sign_in.html
  • 服务端代码处理登录
//server.js
//sign_in的get请求,响应登录页面(html)
}else if(path==='/sign_in' && method === 'GET'){
  let string = fs.readFileSync('./sign_in.html', 'utf8')
  response.statusCode = 200
  response.setHeader('Content-Type', 'text/html;charset=utf-8')
  response.write(string)
  response.end()
}else if(path==='/sign_in' && method === 'POST'){
  readBody(request).then((body)=>{
    let strings = body.split('&') // ['email=1', 'password=2', 'password_confirmation=3']
    let hash = {}
    strings.forEach((string)=>{
      // string == 'email=1'
      let parts = string.split('=') // ['email', '1']
      let key = parts[0]
      let value = parts[1]
      hash[key] = decodeURIComponent(value) // hash['email'] = '1'
    })
    let {email, password} = hash
    var users = fs.readFileSync('./db/users', 'utf8')
    try{
      users = JSON.parse(users) // []
    }catch(exception){
      users = []
    }
    let found
    for(let i=0;i<users.length; i++){
      if(users[i].email === email && users[i].password === password){
        found = true
        break
      }
    }
    if(found){//在数据库中找到了邮箱密码一致,登录成功
      response.statusCode = 200
    }else{//登录失败
      response.statusCode = 401
    }
    response.end()
  })
}
  • 客户端代码-发送登录post请求
//sign_in.html
//发送sign_in的post请求,提交登录数据
$.post('/sign_in', hash)  
.then((response)=>{
  //登录成功,进入首页
  window.location.href = '/'
}, (request)=>{
  alert('邮箱与密码不匹配')
})

现在有一个问题就是,如果不通过登录页面,直接输入首页的url也可以进入首页。这样不是我们想要的效果,我们希望登录用户才可以进入首页,同时首页显示登录用户信息,这就要引入cookie

if(found){
  response.setHeader('Set-Cookie', `sign_in_email=${email}`) //!!!!加这一句
  response.statusCode = 200
}
  • 响应头多返回了个Set-Cookie↓


    响应头.png
  • 后面同源的请求头也就默认多了个cookie


    请求头.png

Cookie的特点

  1. 服务器通过Set-Cookie响应头设置Cookie
  2. 浏览器得到Cookie之后,每次请求都要带上Cookie(同源下?)
  3. 服务器读取请求头的Cookie就知道登录用户的信息(email)

问题

  1. 我在 Chrome 登录了得到 Cookie,用 Safari 访问,Safari 会带上 Cookie 吗
    no
  2. Cookie 存在哪
    Windows 存在 C 盘的一个文件里
  3. Cookie会被用户篡改吗?
    可以,下节课会讲 Session 来解决这个问题,防止用户篡改
  4. Cookie 有有效期吗?
    默认有效期20分钟左右,不同浏览器策略不同
    后端可以强制设置有效期,具体语法看 MDN


    image.png
  5. Cookie 遵守同源策略吗?
    也有,不过跟 AJAX 的同源策略稍微有些不同。
    当请求 qq.com 下的资源时,浏览器会默认带上 qq.com 对应的 Cookie,不会带上 baidu.com 对应的 Cookie
    当请求 v.qq.com 下的资源时,浏览器不仅会带上 v.qq.com 的Cookie,还会带上 qq.com 的 Cookie
    另外 Cookie 还可以根据路径做限制,请自行了解,这个功能用得比较少。
如何在首页显示登录用户信息?
  • nodejs read cookie


    image.png

console.log(request.headers.cookie)

//server.js
//服务端读取cookie
//在首页显示登录用户的信息(密码)
if(path === '/'){
  let string = fs.readFileSync('./index.html', 'utf8')
  let cookies =  request.headers.cookie.split('; ') // ['email=1@frankfang.com', 'a=1', 'b=2']
  let hash = {}
  for(let i =0;i<cookies.length; i++){
    let parts = cookies[i].split('=')
    let key = parts[0]
    let value = parts[1]
    hash[key] = value 
  }
  let email = hash.sign_in_email
  let users = fs.readFileSync('./db/users', 'utf8')
  users = JSON.parse(users)
  let foundUser
  for(let i=0; i< users.length; i++){
    if(users[i].email === email){
      foundUser = users[i]
      break
    }
  }
  console.log(foundUser)
  if(foundUser){
    string = string.replace('__password__', foundUser.password)
  }else{
    string = string.replace('__password__', '不知道')
  }
  response.statusCode = 200
  response.setHeader('Content-Type', 'text/html;charset=utf-8')
  response.write(string)
  response.end()
}
首页html.png

代码细节不用那么清楚,主要要搞清楚流程

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • HTTP cookie(也称为web cookie,网络cookie,浏览器cookie或者简称cookie)是网...
    留七七阅读 18,084评论 2 71
  • 实现基本的注册功能 本地模拟,当输入localhost:8080/sign_up的时候,浏览器发起get请求,服务...
    lyp82nkl阅读 2,118评论 0 0
  • Cookie技术是客户端的解决方案,Cookie就是由服务器发给客户端的特殊信息,而这些信息以文本文件的方式存放在...
    饥人谷_陆邈阅读 1,512评论 1 5
  • 又一个春天,万物生长,春暖花开!经常听人说,现在的天,过了冬天就是夏天,其实春天一直有,只是我们没有关注而...
    漫步_d0d4阅读 167评论 0 0
  • 我是逆袭女神:黎秀平,深圳市毅力创能科技总经理 (主营进口仪器仪表,福禄克代理商 ),坚持每天写创业连载1年多,成...
    黎秀平阅读 141评论 0 1