node项目的鉴权和密码管理

http是无状态的请求,上一次请求和下一次请求之间没有什么必然的联系,且是明文的协议,,每一次http请求,都至关于拿着喇叭在网络上喊,个人帐号密码是什么。为了解决这个问题,引入了cookie和session机制node

用户每一次的请求都会带有cookie,当咱们拿到用户的cookie以后,再去数据库里或者内存里边看看,这个用户是否已经登录过了,这个cookie是否是我给他的,若是是我给的,他就有权限使用一些功能git

使用cookie可以访问咱们用户相应的内容,这样看起来和帐号密码功能差很少了。可是和帐号密码的区别是,cookie是由后端加密的,信息虽然能被攻击者拿到,可是不能被篡改(若是在客户端作加密,也等因而透明了,差很少也等因而广播)github

本文使用express框架,使用import语法由于我是在typescript项目内作的,看成require来看就行,不影响食用web

cookie

cookie使用

下面使用setCookie更改cookieredis

// 这里是路由
app.get('/auth', authController.auth);	
app.get('/hello', authController.sayHello);

// 如下是controller
// 这里存储用户信息
const users = [];
// 设置cookie
export const auth = async function(req, res, next) {
  const { username } = req.query;
  if (!users.find(u => u.username === username)) {
    res.set('Set-Cookie', `username=${username}`);
    users.push({ username });
  }
  res.send();
};

export const sayHello = async function(req, res, next) {
  const { username } = req.cookies;
  if (users.find(u => u.username === username)) {
    res.send(req.cookies.username);
  } else {
    res.send('please login');
  }
};

复制代码

用户端访问/auth?username="你的用户名",返回header的cookie中会有username值是你输入的名字,用户再访问/hello会获得后端返回的值,也就是访问/auth时输入的username的值算法

会话管理

会话管理表示一个用户和服务器的交互的过程当中要求用户先登录,才能进行操做(例如敏感操做,已登录用户才能访问的页面),如何判断用户信息是不是真实,这时候就涉及到会话管理,会话管理有几种形式,上边代码也是其中一种,它是最初级的一种会话管理,它没有正常cookie过时时间,当服务器重启后用户的会话信息就会丢掉。更好的解决方案是把用户的登陆信息放到数据库里边mongodb

cookieSession

cookiesession是一个会话管理解决方案,express官网上有推荐typescript

下边使用cookieSession来改造上边的例子数据库

// 安装库
npm install cookie-session

// 引入cookie-session
const cookieSession = require('cookie-session')

//使用中间件
app.use(
  cookieSession({
    name: 'testCookieSession',	// cookie名称
    keys: ['nuo', 'blog'],	// 秘钥
    // Cookie Options
    maxAge: 24 * 60 * 60 * 1000 // cookie过时时间
  })
);

// 改造的controller
export const auth = async function(req, res, next) {
  const { username } = req.query;
  req.session.user = { username };
  res.send('success');
};

export const sayHello = async function(req, res, next) {
  const { username } = req.session.user;
  res.send(username);
};
复制代码

此时访问/auth?username=nuo返回的cookie就和以前明文返回的cookie有区别了,返回内容header的cookie部分以下express

cookie名称: testCookieSession
cookie值: eyJ1c2VyIjp7InVzZXJuYW1lIjoibnVvIn19	// base64简单加密的传输内容

cookie名称: testCookieSession.sig	// 这里是cookie的签名,根据上面设置的密钥和解密出来的内容获得的
cookie值: gTty0Cinxf8r0FwhmS8lz-TdM7I
复制代码

这是一个通过简单加密的cookie,再访问/hello,依然返回咱们设置的username,值是nuo

cookieSession这个中间件使用的是一套成熟的体系,帮咱们解决了最开始代码咱们处理的那些细节。在这里的体现:拿着同一套cookie的浏览器发过来的请求,自动把session给挂到request上。

jsonWebToken

Jsonwebtoken是经常使用的会话管理,它的存储方式不像setcookie方法那么简单

import JWT from 'jsonwebtoken';

export const auth = async function(req, res, next) {
  const { username } = req.query;
  const user = { username };
  // 使用jsonwebtoken生成token
  const token = JWT.sign(user, 'signKey_18902379108701');
  res.send(token);
};
复制代码

此时访问/auth?username=nuo会返回一串字符串,以.做为分割,有三个部分

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Im51byIsImlhdCI6MTU0NTc0NTcyNH0._hCXHeZBrd0uZYhvQxbQylDtC_1UC2hcXHBOK1rR0Kc
// 第一部分里边包含了加密信息
new Buffer('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9','base64').toString()
//'{"alg":"HS256","typ":"JWT"}'		// 加密算法,加密方式
// 第二部分包含了传递的内容和生成时间
new Buffer('eyJ1c2VybmFtZSI6Im51byIsImlhdCI6MTU0NTc0NTcyNH0','base64').toString()
// {"username":"nuo","iat":1545745724}	// 加密内容,加密时间
复制代码

第三部分是没法解密的,相似于cookieSession中的签名,下面咱们来看如何在服务端解析jsonwebtoken的信息

import JWT from 'jsonwebtoken';

export const auth = async function(req, res, next) {
  const { username } = req.query;
  const user = { username };
  const token = JWT.sign(user, 'signKey_18902379108701');
  res.send(token);
};

export const sayHello = async function(req, res, next) {
  const auth = req.get('Authorization');
  if (!auth) return res.send('no auth');
  if (auth.indexOf('Bearer') < 0) return res.send('no auth');
  const token = auth.split('Bearer ')[1];
  const user = JWT.verify(token, 'signKey_18902379108701');
  res.send(user);
};

复制代码

这里请求/auth记录用户信息以后,须要在postman进行一下操做

在postman,Headers中输入key:Authorization,value为Bearer+以前返回的token,以下

key: Authorization
value: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Im51byIsImlhdCI6MTU0NTc0NTcyNH0._hCXHeZBrd0uZYhvQxbQylDtC_1UC2hcXHBOK1rR0Kc
复制代码

此时发送请求,返回的是上边解密出来的值

为何要使用cookieSession或JWT

把数据存到会话管理系统里边的缘由是由于,无状态的http请求,对于分布式管理的系统很是不友好,若是登陆信息存在内存里,服务器重启,用户须要从新登录。或者有多台服务器,用户在一台服务器上登陆后,想访问另外一台服务器上的内容,又须要进行从新登陆,也会很是的麻烦。解决这种状况有几种方式:

  1. 经过集群的数据库,好比redis,mongodb,把session统一的管理起来,或者每一个服务器存储惟一的用户信息,其余服务器须要的时候来这台服务器上取(分片式存储)

  2. 把用户的不敏感信息存放到cookie里边或发送到客户端的token里,客户端能够在任什么时候候,经过token作一些不敏感操做,由于这些信息是明文的,当须要作敏感操做的时候,须要加入其余验证方式,如交叉验证,当用户作如更改密码这些操做,给用户一个更高级,过时时间更短的token来进行相应的处理

    import JWT from 'jsonwebtoken';
    
    export const auth = async function(req, res, next) {
      const { username } = req.query;
      const user = {
        username,
        // 设置token过时时间
        expireAt: `${Date.now().valueOf() + 20 * 60 * 1000}`
      };
      const token = JWT.sign(user, 'signKey_18902379108701');
      res.send(token);
    };
    
    export const sayHello = async function(req, res, next) {
      const auth = req.get('Authorization');
      if (!auth) return res.send('no auth');
      if (auth.indexOf('Bearer') < 0) return res.send('no auth');
      const token = auth.split('Bearer ')[1];
      const user = JWT.verify(token, 'signKey_18902379108701');
      // 判断过时时间
      if (user.expireAt < Date.now().valueOf()) return res.send('time out');
      res.send(user);
    };
    复制代码
  3. token虽然不能保证被泄密,可是能够保证攻击者只能拿到token,并不能修改token。

用户帐号密码存储

简单的帐号密码存储

export const login = async function(req, res, next) {
  const { username, password } = req.body;
  user.push({ username, password });
};
复制代码

上面的示例是很是危险的,只要数据库被攻击,大量用户名和密码会被泄漏,或有数据库访问权限的人拿到数据库的信息就拿到了全部的信息

密码的加密

node的密码管理最佳实践之一

import crypto from 'crypto';	// crypto加密
import blueBird from 'bluebird'
// pbkdf2加密是一个很是难破解的加密方式
const pbkdf2Async = blueBird.promisify(crypto.pbkdf2)
// 模拟user,实际开发中使用数据库
const user = [];
export const login = async function(req, res, next) {
  const { username, password } = req.query;
  const cipher = await pbkdf2Async(
    password,
    'test_key_akjsdhaksjdhakjsdh',
    10000,
    512,
    'sha256'
  );
  user.push({ username, cipher });
  res.send(cipher);
};
复制代码

此时cipher就是一个加密的串,固然除了密码的加密以外,咱们还须要作密码和用户名的分表分库,防止被脱裤。

那么如何进行crypto加密状况下的密码验证呢:

import crypto from 'crypto';
import blueBird from 'bluebird';
const pbkdf2Async = blueBird.promisify(crypto.pbkdf2);
// 模拟user
const user = [];
export const login = async function(req, res, next) {
  const { username, password } = req.query;
  const cipher = await pbkdf2Async(
    password,
    'test_key_akjsdhaksjdhakjsdh',
    10000,
    512,
    'sha256'
  );
  user.push({ username, password: cipher });
  res.send(cipher);
};

export const getUserByNamePass = async function(req, res, next) {
  const { username, password } = req.query;
  const cipher = await pbkdf2Async(
    password,
    'test_key_akjsdhaksjdhakjsdh',
    10000,
    512,
    'sha256'
  );
  // 经过加密后的密码进行对比,来验证,增强了密码的安全
  const truePassword = user.find(k => {
    return k.username === username && k.password.toString() === cipher.toString();
  });
  if (truePassword) {
    res.send('login');
  } else {
    res.send('wrong password');
  }
};
复制代码

此时,只须要把加密配置的文件从一个安全的文件夹引入,就能安全的保护你的用户密码数据了

欢迎访问个人博客

相关文章
相关标签/搜索