美文网首页我爱编程
Cookie与登录注册

Cookie与登录注册

作者: 是刘快啊 | 来源:发表于2018-05-26 03:52 被阅读0次

    首先,写一个简单的注册页面sign_up.html(前端)

        <div class="form-wrapper">
            <h1>注册</h1>
            <form>
                <div class="row">
                    <label>邮箱</label>
                    <input type="text" name="emali">
                    <span class="error"></span>
                </div>
                <div class="row">
                    <label>密码</label>
                    <input type="text" name="password">
                    <span class="error"></span>
                </div>
                <div class="row">
                    <label>确认密码</label>
                    <input type="text" name="password_confirmation">
                    <span class="error"></span>
                </div>
                <input type="submit" value="注册">
            </form>
        </div>
    

    把用户输入的内容放到一个哈希中(前端)

            let $form = $('#signUpForm')
            let hash = {}
            $form.on('submit', (e) => {
                e.preventDefault()
                let need = ['email', 'password', 'password_confirmation']
                need.forEach( (name) => {
                    let value = $form.find(`[name = ${name}]`).val()
                    hash[name] = value
                })
            })  //得到的hash为 {email: "xx", password: "xx", password_confirmation: "xx"}
    

    给server.js里添加一个路由,如果请求路径是/sign_up,就显示当前目录下的sign_up.html文件(后端)

     if(path === '/sign_up'){
        let string = fs.readFileSync('./sign_up.html', 'utf8')
        response.statusCode = 200
        response.setHeader('Content-Type', 'text/html;charset=utf-8')
        response.write(string)
        response.end()
     }
    

    node server 8080 打开server,访问http://localhost:8080/sign_up就可以看到我们写的注册页面。
    尝试发送一个Ajax post请求:jQuery.post() (前端)

    $.post('/sign_up', hash)
          .then( (response) => {
              console.log(response) //得到一串符合html格式的字符串
          }, () => {
              console.log(‘error’)
          })
    

    打印出来一个html格式的字符串,因为server.js中写明了只要请求路径是/sign_up就表示请求成功,返回字符串。

    由于这是一个post请求,如果在路由里将请求方式的限制为get,则会打印出‘error’:(后端)

     if(path === '/sign_up' && method === 'GET'){
        let string = fs.readFileSync('./sign_up.html', 'utf8')
        response.statusCode = 200
        response.setHeader('Content-Type', 'text/html;charset=utf-8')
        response.write(string)
        response.end()
     }
    

    所以要再添加一个请求方式为post的路由:(后端)

     if(path === '/sign_up' && method === 'POST'){
        readBody(request).then( (body) => {
            response.statusCode = 200
            response.end()
        })    
     } //用户post到/sign_up, 服务器读取到请求的第四部分(Form Data),得到字符串'email=1&password=2&password_confirmation=3'
    
    //由于请求的第四部分(Form Data)是分段上传的,Node.js无法直接读到其内容,封装以下函数:
    //作用是获取请求第四部分数据,并返回一个Promise对象
    function readBody(request){
      return new Promise((resolve, reject)=>{
        let body = []
        request.on('data', (chunk) => {
          body.push(chunk);
        }).on('end', () => {
          body = Buffer.concat(body).toString();
          resolve(body)
        })
      })
    }
    

    现在用户向sign_up发送post请求,服务器得到的请求的第四部分是一个字符串'email=1&password=2&password_confirmation=3',使用split()方法将其变成哈希:(后端)

    let strings = body.split('&') // ['email=1', 'password=2', 'password_confirmation=3']
    let hash = {}
    strings.forEach((string) => {
        let parts = string.split('=') // ['email', '1']
        let key = parts[0]
        let value = parts[1]
        hash[key] = (value) // hash['email'] = '1'
    })
    

    以上所做的事情总结起来就是:
    客户端收集form表单内容到一个hash中,作为一个字符串传给服务端,
    服务端以一个字符串的形式拿到表单内容,
    再将其还原为一个hash。

    这就是前端向后台传数据的方式:前端把所有东西变成一个字符串传给后台,后台从字符串里按照所要的结构解析出来。

    现在服务器有了一个包含表单信息的hash,继续进行验证:(后台)

    let {email, password, password_confirmation} = hash
    if(email.indexOf('@') === -1){ //验证用户输入的邮箱中是否有@
        response.statusCode = 400
        response.setHeader('Content-Type', 'application/json;charset=utf-8') //jQuery只要发现响应中说了这是json就会自动JSON.parse()
        response.write(`{
            "errors": {
                "email": "invalid"
            }
        }`)
    }else if(password !== password_confirmation){ //验证两次输入的密码是否匹配
        response.statusCode = 400
        response.write('password not match')
    }else{
        response.statusCode = 200
    }
    

    (前端)

    $.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('邮箱格式错误')
                        }
                    })
    

    前端也可以在发起请求之前进行一些验证:(前端)jQuery.each()

                $form.find('.error').each( (index, span) => {
                    $(span).text('')
                })
                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
                }
    

    现在,我们实现的功能是邮箱、密码、验证密码必填,密码与验证密码相同(前端验证),邮箱中必须有'@'(后端验证)。
    由于前后端代码都是JS,所以后端的验证前端都能做到。但是前端可以不验,后端必须要验。例如用crul发请求的话,可以直接与服务器交流。所以不能依赖浏览器上的JS,必须确保后端是安全的。

    验证成功之后,服务器需要将得到的信息保存到数据库中。这里我们在当前目录下创建db/users文件作为数据库,初始化为一个空数组[]。(后端)

    var users = fs.readFileSync('./db/users', 'utf8')
    try{
      users = JSON.parse(users) // 如果users不符合JSON语法就放弃它,把它变成空数组
    }catch(exception){
      users = []
    }
    users.push({email: email, password: password})
    var usersString = JSON.stringify(users) //对象不能直接存,将其变成字符串
    fs.writeFileSync('./db/users', usersString)
    response.statusCode = 200
    

    现在有个问题,同一个邮箱可以多次注册。服务器可以在将信息存入数据库之前验证邮箱是否已注册:(后端)

    var users = fs.readFileSync('./db/users', 'utf8')
    try {
        users = JSON.parse(users) // 如果users不符合JSON语法就放弃它,把它变成空数组
    } 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
    }
    

    至此注册过程完成。接着来做登录功能。
    首先写一个登录页面sign_in.html:(前端)

    <body>
        <div class="form-wrapper">
            <h1>登录</h1>
            <form id="signInForm">
                <div class="row">
                    <label>邮箱</label>
                    <input type="text" name="email">
                    <span class="error"></span>
                </div>
                <div class="row">
                    <label>密码</label>
                    <input type="password" name="password">
                    <span class="error"></span>
                </div>
                <input type="submit" value="登录">
            </form>
        </div>
        <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
        <script>
            let $form = $('#signInForm')
            let hash = {}
            $form.on('submit', (e) => {
                e.preventDefault()
                let need = ['email', 'password']
                need.forEach( (name) => {
                    let value = $form.find(`[name = ${name}]`).val()
                    hash[name] = value
                })
                $form.find('.error').each( (index, span) => {
                    $(span).text('')
                })
                if(hash['email'] === ''){
                    $form.find('[name = "email"]').siblings('.error')
                        .text('填邮箱呀同学')
                    return
                }
                if(hash['password'] === ''){
                    $form.find('[name = "password"]').siblings('.error')
                        .text('填密码呀同学')
                    return
                }
    
                $.post('/sign_in', hash)
                    .then( (response) => {
                        console.log(response)
                    }, (request) => {
                        let {errors} = request.responseJSON
                        if(errors.email && errors.email === 'invalid'){
                            $form.find('[name = "email"]').siblings('.error')
                                .text('邮箱格式错误')
                        }
                    })
            })
        </script>
    </body>
    

    在服务器上给sign_in.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']
                let hash = {}
                strings.forEach((string) => {
                    let parts = string.split('=') // ['email', '1']
                    let key = parts[0]
                    let value = parts[1]
                    hash[key] = decodeURIComponent(value) // hash['email'] = '1'
                })
                
                response.end()
            })
        } 
    

    登录页面与注册页面相似,客户端将表单信息收集到一个哈希中,在发起请求时传一个字符串给后端;后端将得到的字符串解析为哈希。

    后端得到包含email和password的哈希后,与数据库进行对比(看数据库里有没有一样的email和password),认证用户:(后端)

                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
                }
    

    登录成功时跳转到首页:(前端)

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

    现在有一个问题:即使没有登录,用户也可以直接访问首页,与登录后看到的页面相同。
    这里我们需要用到CookiesHTTP Set-Cookie
    服务器在认证用户成功后,给返回的响应头中添加Set-Cookie:(后端)

    if (found) {
        response.setHeader('Set-Cookie', `sign_in_email=${email}`)
        response.statusCode = 200 
    } 
    

    选中开发者工具的Preserve log,再次登录。
    我们可以看到,在向sign_in发起请求时,得到的响应头中有一项Set-Cookie: sign_in_email=1@luke.com,而请求成功后接着对主页发起的请求的请求头中带着这个Cookie:Cookie: sign_in_email=1@luke.com

    这就是Cookie的作用:服务器响应中给页面发送一个Cookie,之后同源发起的请求都带着这个Cookie作为识别。
    理解:第一次进公园时售票员给你两天的票(Set-Cookie),票就是Cookie,两天内可以多次进入公园,每次都要带着票给售票员看。

    Cookie特点:

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

    问题:

    1. 我在Chrome登录得到了Cookie,用Safari访问,Safari会带上Cookie吗?
      no
    2. Cookie存在哪?
      Window存在C盘的一个文件里,其他系统也都存在硬盘的一个文件里。
    3. Cookie能作假吗?
      可以。Chrome开发者工具Application-Cookies就可以改。所以Cookie是不安全的。HttpOnly可以限制。
    4. Cookie有有效期吗:
      默认有效期20分钟左右,由浏览器决定。后端可以强制设置有效期。

    现在我们让登录的用户在跳转到首页时可以看到自己的密码:(后台)

    if (path === '/') {
            let string = fs.readFileSync('./index.html', 'utf8')
            
            //从cookies里拿到用户的email
            let cookies = request.headers.cookie.split('; ')   // ['email=1@', '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
           
            //遍历数据库,找到与cookie里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
                }
            }
            
            //如果找到用户信息,将用户的密码显示在页面中
            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()
        }
    

    index.html:

    <body>
        <h1>你的密码是:'__password__'</h1>
    </body>
    

    总结一下流程:

    1. 用户打开sign_up注册,向服务器发送post请求,发送email,password,password_confirmation;
    2. 服务器验证通过,把用户信息写进数据库,并告诉用户注册成功;
    3. 用户打开sign_in登录,发送post请求,发送email和password;
    4. 服务器认证通过,Set-Cookie;
    5. 用户带着Cookie打开首页,发送get请求;
    6. 服务器读取Cookie,从Cookie中得到email,根据email从数据库找到匹配的用户信息写入页面;
    7. 这样登录的用户进入首页时可以看到自己的信息;
    8. 没有登录的访客进入首页看到不同的页面。

    完整代码

    相关文章

      网友评论

        本文标题:Cookie与登录注册

        本文链接:https://www.haomeiwen.com/subject/psyujftx.html