除了cookie,你還可以用jwt(json web token)!

Jeffywin發表於2019-03-03

1. 認識jwt(json web token)

  • jwt是為了在網路應用環境傳遞宣告而執行的一種基於json的開放標準。
  • jwt被用來在身份提供者和服務提供者間傳遞被認證的使用者身份資訊,簡單來說,就是用來驗證身份的手段,例如登入校驗,像我們之前用的cookie。
  • jwt可以使用HMAC演算法或者是RSA的公私祕鑰對來進行簽名,來保證資訊的可靠性。

2. 應用場景

在例如身份驗證場景中,使用者一旦登入,接下來的每個請求都會包含jwt,用來驗證身份資訊。由於通訊雙方使用jwt對資料進行編碼,它的資訊是經過簽名的,所以可以確保資訊的安全性。

3. jwt對比cookie

cookie缺點

  • 客戶端發請求給伺服器,伺服器種植cookie後,每次請求都會帶上cookie,浪費頻寬
  • cookie不能跨伺服器訪問,不支援跨域
  • 伺服器要對登入的使用者物件進行儲存,浪費伺服器記憶體

jwt優點

  • jwt是不基於狀態的,不需要每次請求都帶上token,節約流量
  • 伺服器不需要佔用記憶體,資訊相對於可靠些
  • 可以跨服務端,可以共用

4. jwt結構

  • Header頭部:{typ:`jwt`,alg:`HS256`} alg:當前用的什麼演算法加密的;使用Base64Url編碼組成了JWT結構的第一部分
  • PlyLoad負載:存放有效資訊的地方
  • Signature簽名:建立簽名需要使用編碼後的header和payload以及一個祕鑰;例如如果希望使用HMAC SHA256演算法,那麼簽名應該使用下列方式建立
    HMACSHA256( base64UrlEncode(header) + “.” + base64UrlEncode(payload), secret)

完整的jwt格式的輸出是以 . 分隔的三段Base64編碼
金鑰secret是儲存在服務端的,服務端會根據這個金鑰進行生成token和驗證,所以需要保護好。

5. 舉個例子

express+vue+mongoose
後端app.js,包括註冊,登入,獲取訂單介面

let express = require(`express`)
let bodyParser = require(`body-parser`)//中介軟體
let jwt = require(`jwt-simple`)//jwt庫
//資料庫
let User = require(`./model/user`)
//監聽函式
let app = express()
let {secret} = require(`./config`)
//中介軟體一定是函式,處理髮回來的json型別,還有text,urlencoded(a=b&c=d)
app.use(bodyParser.json())

//防止跨域 request請求 response響應
app.use(function(req, res, next){
    res.setHeader(`Access-Control-Allow-Origin`,`*`);//簡單點,接收所有
    res.setHeader(`Access-Control-Allow-Headers`,`Content-type,Authorization`);
    res.setHeader(`Access-Control-Allow-Methods`,`GET,POST,DELETE,PUT,OPTIONS`);
    if(req.method === `OPTIONS`) {
        res.end()
    }else {
        next()
    }
})

//註冊
app.post(`/reg`, async function(req, res, next){
    let user = req.body;
    try {
        user = await User.create(user) //在資料庫中插入資料
        res.json({
            code: 0,
            data: {
                user: {
                    id: user._id,
                    username: user.username
                }
            }
        })
    } catch (error) {
        res.json({
            code: 1,
            data: `註冊失敗`
        })
    }  
})
//登入
app.post(`/login`, async function(req,res,next){
    let user = req.body;
    user = await User.findOne(user)//資料庫中查詢
    if(user) {
        let token = jwt.encode({//編碼
            id: user._id,
            username: user.username
        },secret);
        res.json({//返回資訊
            code: 0,
            data: { token }
        })
    }else {
        res.json({
            code: 1,
            data: `使用者不存在`
        })
    }
}) 
// 使用者校驗 中介軟體
let auth = function(req, res, next){
    //post模擬時 新增Headers Authorization: Bearer token的值
    let authorization = req.headers[`authorization`]
    if(authorization) {
        let token = authorization.split(` `)[1];
        try {
            //看token是否合法,解碼,如果串改過token就解不出來,進入異常頁面
            let user = jwt.decode(token, secret);
            req.user = user;//後面就可以拿到user,中介軟體用法 
            next();//下一步
        } catch (error) {
            console.log(error)
            res.status(401).send(`Not Allowed`)
        }
    } else {
        res.status(401).send(`Not Allowed`);
    }
}
//傳送請求,看看能不驗證成功auth,如果可以拿到返回資料
app.get(`/order`, auth, function(req,res,next){
    res.json({
        code: 0,
        data: {
            user: req.user
        }
    })
})
app.listen(3000)
複製程式碼

資料庫頁面

// 運算元據庫
let mongoose = require(`mongoose`);
let {DB_URL} = require(`../config`);

mongoose.connect(DB_URL,{useNewUrlParser:true})

/**
  * 連線成功
  */
 mongoose.connection.on(`connected`, function () {    
    console.log(`Mongoose connection open to ` + DB_URL);  
});    

/**
 * 連線異常
 */
mongoose.connection.on(`error`,function (err) {    
    console.log(`Mongoose connection error: ` + err);  
});

//建立Schema資料模型
let UsrSchema = new mongoose.Schema({
    username: String,
    password: String
});
module.exports = mongoose.model(`User`, UsrSchema);
複製程式碼

axios簡單封裝

import axios from `axios`
import router from `../src/router`
axios.defaults.baseURL = `http://localhost:3000`
//axios 攔截器對拿到的資料進行攔截
axios.interceptors.response.use(function(res){
    if(res.data.code !== 0) {
        return Promise.reject(res.data.data)
    }
    return res.data;
},res=>{
    if(res.response.status === 401){ // 沒許可權跳到登入頁
        router.history.push(`/login`);
    }
    return Promise.reject(`Not Allowed`);
});

//對傳送的請求統一加上token,來驗證是否是本人登入
axios.interceptors.request.use(function(config){
    let token = localStorage.getItem(`token`)
    if(token) {
        config.headers.Authorization = `Bearer ${token}`
    }
    return config;
})

export default axios
複製程式碼

config.js

module.exports = {
    `DB_URL`: `mongodb://localhost:27017/jwt`,
    `secret`: `jeffywin`//祕鑰 加鹽
}
複製程式碼

前臺介面vue-cli腳手架,沒什麼說的,登入介面

<template>
  <div class="main">
    <div class="item">
      <div style="width:100px">登入頁</div> 
      <input type=`text` v-model=`user.username`/>
    </div>
    <div class="item">
      <div style="width:100px">密碼</div> 
      <input type=`text` v-model=`user.password`/>
    </div>
    <button @click="login">提交</button>  
  </div>
</template>

<script>
import axios from `../../utils/axios`
export default {
  data() {
      return {
        user: {
          username: ``,
          password: ``
        }
      }
  },
  methods: {
    login() {
      axios.post(`/login`,this.user).then(res => {
        localStorage.setItem(`token`, res.data.token)//登入後儲存token
        this.$router.push(`/order`)
      })
    }
  }
}
</script>

<style scoped lang="scss">
.main {
  margin: 0 auto;
  width: 300px;

  .item {
    display: flex;
    margin-bottom: 10px;
  }
  }
</style>

複製程式碼

order介面

<template>
  <div class="order">
    <h1>This is an order page</h1>
    {{username}}//如果登入成功,跳轉order介面,拿到登入的使用者
  </div>
</template>

<script>
import axios from `../../utils/axios`
  export default {
    data() {
      return {
        username: ``
      }
    },
    mounted() {
      axios.get(`/order`).then(res => {
        this.username = res.data.user.username
      })
    },
  }
</script>

複製程式碼

原始碼在本人github
github.com/jeffywin/jw…

相關文章