Cookie與登錄注冊

首先,寫一個簡單的注冊頁面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>

把用戶輸入的內(nèi)容放到一個哈希中(前端)

        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,就顯示當(dāng)前目錄下的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就可以看到我們寫的注冊頁面。
嘗試發(fā)送一個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, 服務(wù)器讀取到請求的第四部分(Form Data),得到字符串'email=1&password=2&password_confirmation=3'

//由于請求的第四部分(Form Data)是分段上傳的,Node.js無法直接讀到其內(nèi)容,封裝以下函數(shù):
//作用是獲取請求第四部分數(shù)據(jù),并返回一個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)
    })
  })
}

現(xiàn)在用戶向sign_up發(fā)送post請求,服務(wù)器得到的請求的第四部分是一個字符串'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'
})

以上所做的事情總結(jié)起來就是:
客戶端收集form表單內(nèi)容到一個hash中,作為一個字符串傳給服務(wù)端,
服務(wù)端以一個字符串的形式拿到表單內(nèi)容,
再將其還原為一個hash。

這就是前端向后臺傳數(shù)據(jù)的方式:前端把所有東西變成一個字符串傳給后臺,后臺從字符串里按照所要的結(jié)構(gòu)解析出來。

現(xiàn)在服務(wù)器有了一個包含表單信息的hash,繼續(xù)進行驗證:(后臺)

let {email, password, password_confirmation} = hash
if(email.indexOf('@') === -1){ //驗證用戶輸入的郵箱中是否有@
    response.statusCode = 400
    response.setHeader('Content-Type', 'application/json;charset=utf-8') //jQuery只要發(fā)現(xiàn)響應(yīng)中說了這是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('郵箱格式錯誤')
                    }
                })

前端也可以在發(fā)起請求之前進行一些驗證:(前端)jQuery.each()

            $form.find('.error').each( (index, span) => {
                $(span).text('')
            })
            if(hash['email'] === ''){
                $form.find('[name = "email"]').siblings('.error')
                    .text('填郵箱呀同學(xué)')
                return
            }
            if(hash['password'] === ''){
                $form.find('[name = "password"]').siblings('.error')
                    .text('設(shè)密碼呀同學(xué)')
                return
            }
            if(hash['password_confirmation'] === ''){
                $form.find('[name = "password_confirmation"]').siblings('.error')
                    .text('驗密碼呀同學(xué)')
                return
            }
            if(hash['password'] !== hash['password_confirmation']){
                $form.find('[name = "password_confirmation"]').siblings('.error')
                    .text('密碼不匹配')
                return
            }

現(xiàn)在,我們實現(xiàn)的功能是郵箱、密碼、驗證密碼必填,密碼與驗證密碼相同(前端驗證),郵箱中必須有'@'(后端驗證)。
由于前后端代碼都是JS,所以后端的驗證前端都能做到。但是前端可以不驗,后端必須要驗。例如用crul發(fā)請求的話,可以直接與服務(wù)器交流。所以不能依賴瀏覽器上的JS,必須確保后端是安全的。

驗證成功之后,服務(wù)器需要將得到的信息保存到數(shù)據(jù)庫中。這里我們在當(dāng)前目錄下創(chuàng)建db/users文件作為數(shù)據(jù)庫,初始化為一個空數(shù)組[]。(后端)

var users = fs.readFileSync('./db/users', 'utf8')
try{
  users = JSON.parse(users) // 如果users不符合JSON語法就放棄它,把它變成空數(shù)組
}catch(exception){
  users = []
}
users.push({email: email, password: password})
var usersString = JSON.stringify(users) //對象不能直接存,將其變成字符串
fs.writeFileSync('./db/users', usersString)
response.statusCode = 200

現(xiàn)在有個問題,同一個郵箱可以多次注冊。服務(wù)器可以在將信息存入數(shù)據(jù)庫之前驗證郵箱是否已注冊:(后端)

var users = fs.readFileSync('./db/users', 'utf8')
try {
    users = JSON.parse(users) // 如果users不符合JSON語法就放棄它,把它變成空數(shù)組
} 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('填郵箱呀同學(xué)')
                return
            }
            if(hash['password'] === ''){
                $form.find('[name = "password"]').siblings('.error')
                    .text('填密碼呀同學(xué)')
                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>

在服務(wù)器上給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()
        })
    } 

登錄頁面與注冊頁面相似,客戶端將表單信息收集到一個哈希中,在發(fā)起請求時傳一個字符串給后端;后端將得到的字符串解析為哈希。

后端得到包含email和password的哈希后,與數(shù)據(jù)庫進行對比(看數(shù)據(jù)庫里有沒有一樣的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
            }

登錄成功時跳轉(zhuǎn)到首頁:(前端)

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

現(xiàn)在有一個問題:即使沒有登錄,用戶也可以直接訪問首頁,與登錄后看到的頁面相同。
這里我們需要用到Cookies。HTTP Set-Cookie
服務(wù)器在認證用戶成功后,給返回的響應(yīng)頭中添加Set-Cookie:(后端)

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

選中開發(fā)者工具的Preserve log,再次登錄。
我們可以看到,在向sign_in發(fā)起請求時,得到的響應(yīng)頭中有一項Set-Cookie: sign_in_email=1@luke.com,而請求成功后接著對主頁發(fā)起的請求的請求頭中帶著這個Cookie:Cookie: sign_in_email=1@luke.com。

這就是Cookie的作用:服務(wù)器響應(yīng)中給頁面發(fā)送一個Cookie,之后同源發(fā)起的請求都帶著這個Cookie作為識別。
理解:第一次進公園時售票員給你兩天的票(Set-Cookie),票就是Cookie,兩天內(nèi)可以多次進入公園,每次都要帶著票給售票員看。

Cookie特點:

  1. 服務(wù)器通過Set-Cookie響應(yīng)頭設(shè)置Cookie
  2. 瀏覽器得到Cookie之后,每次請求都要帶上Cookie
  3. 服務(wù)器讀取Cookie就知道登錄用戶的信息

問題:

  1. 我在Chrome登錄得到了Cookie,用Safari訪問,Safari會帶上Cookie嗎?
    no
  2. Cookie存在哪?
    Window存在C盤的一個文件里,其他系統(tǒng)也都存在硬盤的一個文件里。
  3. Cookie能作假嗎?
    可以。Chrome開發(fā)者工具Application-Cookies就可以改。所以Cookie是不安全的。HttpOnly可以限制。
  4. Cookie有有效期嗎:
    默認有效期20分鐘左右,由瀏覽器決定。后端可以強制設(shè)置有效期。

現(xiàn)在我們讓登錄的用戶在跳轉(zhuǎn)到首頁時可以看到自己的密碼:(后臺)

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
       
        //遍歷數(shù)據(jù)庫,找到與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>

總結(jié)一下流程:

  1. 用戶打開sign_up注冊,向服務(wù)器發(fā)送post請求,發(fā)送email,password,password_confirmation;
  2. 服務(wù)器驗證通過,把用戶信息寫進數(shù)據(jù)庫,并告訴用戶注冊成功;
  3. 用戶打開sign_in登錄,發(fā)送post請求,發(fā)送email和password;
  4. 服務(wù)器認證通過,Set-Cookie;
  5. 用戶帶著Cookie打開首頁,發(fā)送get請求;
  6. 服務(wù)器讀取Cookie,從Cookie中得到email,根據(jù)email從數(shù)據(jù)庫找到匹配的用戶信息寫入頁面;
  7. 這樣登錄的用戶進入首頁時可以看到自己的信息;
  8. 沒有登錄的訪客進入首頁看到不同的頁面。

完整代碼

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容