Node.js+MongoDB對於RestfulApi中用戶token認證明踐

關於安全性方面的建議

能夠參考這篇總結 開發安全的 API 所須要覈對的清單javascript

安裝

git clone https://github.com/Nicksapp/nAuth-restful-api.git前端

運行

npm installjava

具體數據庫配置信息在config.js中設置node

總體構架

開發前先進行咱們設計的構想git

  • 路由設計github

    • POST /api/signup: 用戶註冊
    • POST /api/user/accesstoken: 帳號驗證,獲取token
    • GET /api/users/info: 得到用戶信息,需驗證
  • user 模型設計web

    • name : 用戶名
    • password: 密碼
    • token: 驗證相關token

關於RESTful API

網上已經有了不少關於RESTful的介紹,我這裏也不過多重複了。想說的就是它的主要做用,就是對於現現在的網絡應用程序,分爲前端和後端兩個部分,然而當前的發展趨勢就是應用平臺需求的擴大(IOS、Android、Webapp等等)mongodb

所以,就須要一種統一的機制,方便不一樣的應用平臺的前端設備與後端進行通訊,也就是先後端的分離。這致使了API架構的流行,甚至出現"API First"的設計思想。RESTful API則是目前比較成熟的一套互聯網應用程序的API設計理論。數據庫

技術棧

使用Node.js上的Express框架進行咱們的路由設計,Mongoose來與Mongodb數據庫鏈接交互,使用Postman對咱們設計的Api進行調試,快動起手來吧!express

API設計中的token的思路

在API設計中,TOKEN用來判斷用戶是否有權限訪問API.TOKEN首先不須要編解碼處理. 通常TOKEN都是一些用戶名+時間等內容的MD5的不可逆加密.而後經過一個USER_TOKEN表來判斷用戶請求中包含的TOKEN與USER_TOKEN表中的TOKEN是否一致便可.

具體實踐過程主要爲:

  1. 設定一個密鑰好比key = ‘2323dsfadfewrasa3434'。
  2. 這個key 只有發送方和接收方知道。
  3. 調用時,發送方,組合各個參數用密鑰 key按照必定的規則(各類排序,MD5,ip等)生成一個access_key。一塊兒post提交到API接口。
  4. 接收方拿到post過來的參數以及這個access_key。也和發送同樣,用密鑰key 對各個參數進行同樣的規則(各類排序,MD5,ip等)也生成一個access_key2。
  5. 對比 access_key 和 access_key2 。同樣。則容許操做,不同,報錯返回或者加入黑名單。

token設計具體實踐

廢話很少說,先進入看咱們的乾貨,此次選用Node.js+experss配合Mongoose來進入REST的token實踐

項目地址: GitHub地址

git clone https://github.com/Nicksapp/nAuth-restful-api.git

新建項目

先看看咱們的項目文件夾

- routes/
---- index.js
---- users.js
- models/
---- user.js
- config.js
- package.json
- passport.js
- index.js

npm init建立咱們的package.json

接着在項目根文件夾下安裝咱們所需的依賴

npm install express body-parser morgan mongoose jsonwebtoken bcrypt passport passport-http-bearer --save
  • express: 咱們的主要開發框架
  • mongoose: 用來與MongoDB數據庫進行交互的框架,請提早安裝好MongoDB在PC上
  • morgan: 會將程序請求過程的信息顯示在Terminal中,以便於咱們調試代碼
  • jsonwebtoken: 用來生成咱們的token
  • passport: 很是流行的權限驗證庫
  • bcrypt: 對用戶密碼進行hash加密

-- save會將咱們安裝的庫文件寫入package.json的依賴中,以便其餘人打開項目是可以正確安裝所需依賴.

用戶模型

定義咱們所需用戶模型,用於moogoose,新建models/user.js

const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const bcrypt = require('bcrypt');

const UserSchema = new Schema({
  name: {
    type: String,
    unique: true, // 不可重複約束
    require: true // 不可爲空約束
  },
  password: {
    type: String,
    require: true
  },
  token: {
    type: String
  }
});

// 添加用戶保存時中間件對password進行bcrypt加密,這樣保證用戶密碼只有用戶本人知道
UserSchema.pre('save', function (next) {
    var user = this;
    if (this.isModified('password') || this.isNew) {
        bcrypt.genSalt(10, function (err, salt) {
            if (err) {
                return next(err);
            }
            bcrypt.hash(user.password, salt, function (err, hash) {
                if (err) {
                    return next(err);
                }
                user.password = hash;
                next();
            });
        });
    } else {
        return next();
    }
});
// 校驗用戶輸入密碼是否正確
UserSchema.methods.comparePassword = function(passw, cb) {
    bcrypt.compare(passw, this.password, (err, isMatch) => {
        if (err) {
            return cb(err);
        }
        cb(null, isMatch);
    });
};

module.exports = mongoose.model('User', UserSchema);

配置文件

./config.js 用來配置咱們的MongoDB數據庫鏈接和token的密鑰。

module.exports = {
  'secret': 'learnRestApiwithNickjs', // used when we create and verify JSON Web Tokens
  'database': 'mongodb://localhost:27017/test' // 填寫本地本身 mongodb 鏈接地址,xxx爲數據表名
};

本地服務器配置

./index.js 服務器配置文件,也是程序的入口。

這裏咱們主要用來包含咱們程序須要加載的庫文件,調用初始化程序所須要的依賴。

const express = require('express');
const app = express();
const bodyParser = require('body-parser');// 解析body字段模塊
const morgan = require('morgan'); // 命令行log顯示
const mongoose = require('mongoose');
const passport = require('passport');// 用戶認證模塊passport
const Strategy = require('passport-http-bearer').Strategy;// token驗證模塊
const routes = require('./routes');
const config = require('./config');

let port = process.env.PORT || 8080;

app.use(passport.initialize());// 初始化passport模塊
app.use(morgan('dev'));// 命令行中顯示程序運行日誌,便於bug調試
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json()); // 調用bodyParser模塊以便程序正確解析body傳入值

routes(app); // 路由引入

mongoose.Promise = global.Promise;
mongoose.connect(config.database); // 鏈接數據庫

app.listen(port, () => {
  console.log('listening on port : ' + port);
})

路由配置

./routes 主要存放路由相關文件

./routes/index.js 路由總入口,引入所使用路由

module.exports = (app) => {
  app.get('/', (req, res) => {
    res.json({ message: 'hello index!'});
  });

  app.use('/api', require('./users')); // 在全部users路由前加/api
};

./routes/users.js

const express = require('express');
const User = require('../models/user');
const jwt = require('jsonwebtoken');
const config = require('../config');
const passport = require('passport');
const router = express.Router();

require('../passport')(passport);

// 註冊帳戶
router.post('/signup', (req, res) => {
  if (!req.body.name || !req.body.password) {
    res.json({success: false, message: '請輸入您的帳號密碼.'});
  } else {
    var newUser = new User({ // 在庫中建立一個新用戶
      name: req.body.name,
      password: req.body.password
    });
    // 保存用戶帳號
    newUser.save((err) => {
      if (err) {
        return res.json({success: false, message: '註冊失敗!'});
      }
      res.json({success: true, message: '成功建立新用戶!'});
    });
  }
});

// 檢查用戶名與密碼並生成一個accesstoken若是驗證經過
router.post('/user/accesstoken', (req, res) => {
  User.findOne({ // 根據用戶名查找是否存在該用戶
    name: req.body.name
  }, (err, user) => {
    if (err) {
      throw err;
    }
    if (!user) {
      res.json({success: false, message:'認證失敗,用戶不存在!'});
    } else if(user) {
      // 檢查密碼是否正確
      user.comparePassword(req.body.password, (err, isMatch) => {
        if (isMatch && !err) {
          var token = jwt.sign({name: user.name}, config.secret,{
            expiresIn: 10080 // token 過時銷燬時間設置
          });
          user.token = token;
          user.save(function(err){
            if (err) {
              res.send(err);
            }
          });
          res.json({
            success: true,
            message: '驗證成功!',
            token: 'Bearer ' + token,
            name: user.name
          });
        } else {
          res.send({success: false, message: '認證失敗,密碼錯誤!'});
        }
      });
    }
  });
});

// passport-http-bearer token 中間件驗證
// 經過 header 發送 Authorization -> Bearer  + token
// 或者經過 ?access_token = token
router.get('/user/user_info',
  passport.authenticate('bearer', { session: false }),
  function(req, res) {
    res.json({username: req.user.name});
});

module.exports = router;

passport配置

./passport.js 配置權限模塊所需功能

const passport = require('passport');
const Strategy = require('passport-http-bearer').Strategy;

const User = require('./models/user');
const config = require('./config');

module.exports = function(passport) {
    passport.use(new Strategy(
        function(token, done) {
            User.findOne({
                token: token
            }, function(err, user) {
                if (err) {
                    return done(err);
                }
                if (!user) {
                    return done(null, false);
                }
                return done(null, user);
            });
        }
    ));
};

主要驗證發送的token值與用戶服務器端token值是否匹配,進行信息驗證。

具體調試

如今就能夠運行咱們的代碼看具體運做過程了!爲了便於調試與參數的收發,咱們使用postman(可在Chrome上或Mac上安裝)來操做.

node index運行咱們的本地服務器,訪問 [localhost:8080/]()
應該就能夠看到咱們所返回的初始json值了,然咱們繼續深刻測試。

POST訪問[localhost:8080/api/signup](),咱們來註冊一個新用戶,注意要設置bodyContent-Typex-www-form-urlencoded 以便咱們的body-parser可以正確解析,好的咱們成功模擬建立了咱們的新用戶。

鏈接一下數據庫看下咱們的用戶信息是否也被正確存儲(注:我使用的是MongoChef,十分強大MongoDB數據庫管理軟件),咱們能夠看到,個人password也被正確加密保存了。

接着POST訪問[localhost:8080/api/user/accesstoken](),來爲個人用戶得到專屬token,POST過程與註冊相關,能夠看到也正確生成咱們的token值。

再看下咱們的數據庫中的用戶信息,token值也被存入了進來,便於咱們以後進行權限驗證。

GET訪問[localhost:8080/api/user/user_info](),同時將咱們的token值在Header中以 Authorization: token 傳入,正確得到用戶名則表示咱們訪問請求經過了驗證。

若是token值不正確,則返回HTTP狀態碼 401 Unauthorized 並拒絕訪問請求。到這裏咱們的權限驗證功能也就基本實現了。

總結

但願在看完這篇教程後可以對你在RESTful Api開發上有所啓發,小生才疏學淺,過程當中有什麼不足的地方也歡迎指正。

相關文章
相關標籤/搜索