如何进行权限管理? 首先咱们要了解一下cookie和sessionjavascript
为何有cookie?咱们说http请求实际上是无状态的,也就是第一次和第二次访问浏览器都没法识别,因而咱们在浏览器第一次访问服务器的时候,服务器会经过set-cookie
响应头给浏览器种个cookie(标识),cookie是为了辩别用户身份,进行会话跟踪而存储在客户端上的数据,如图:html
express对cookie作了封装,咱们先看原生cookie是如何实现通讯的java
let http = require('http');
let server = http.createServer(function (req,res) {
if(req.url === '/read'){
res.end(req.headers['cookie']);
}else if(req.url === '/write'){
res.setHeader('Set-Cookie','name=zdl');
res.end('oks');
}else{
res.end('Not Found');
}
});
server.listen(3000);
复制代码
express-cookie,cookie通常存没用的东西,只是个标识,签名nginx
let express = require('express');
let app = express();
app.get('/read',function (req,res) {
res.send(req.cookies); // 取没有签名的cookie
});
app.get('/write',function (req,res) {
//设置cookie方法res.cookie
res.cookie('name','zdl');
res.cookie('age','9');
res.send('ok')
})
app.listen(3000);
复制代码
设置多个cookie咱们可能须要以对象的形式展现出来,这是咱们须要用到querystring
模块redis
let express = require('express');
let app = express();
let querystring = require('querystring');
app.get('/read',function (req,res) {
res.send(querystring.parse(req.headers.cookie, '; ', '='));
});
app.get('/write',function (req,res) {
//设置cookie方法res.cookie
res.cookie('name','zdl');
res.cookie('age','9');
res.send('ok')
})
app.listen(3000);
复制代码
document.cookie= 'name=zdl'
再刷新以后就会被串改,因此咱们会加个签名(加密),须要引用一个模块(cookie-parser)(须要安装)mongodb
let express = require('express');
let app = express();
let cookieParser = require('cookie-parser')
app.use(cookieParser('zfpx'));
app.get('/read',function (req,res) {
res.send(req.signedCookies); // 签名能够防止cookie的篡改
});
app.get('/write',function (req,res) {
//设置cookie方法res.cookie
res.cookie('name','zdl1',{signed:true});
res.cookie('age','9');
res.send('ok')
})
app.listen(3000);
复制代码
cookie将缓存放在硬盘上了,session放在浏览器上的浏览器关掉就消失了数据库
let express = require('express');
let app = express();
app.get('/write',function (req,res) {
//仅访问read的时候有,且10s以后过时
res.cookie('name','zdl',{path:'/read',expires:new Date(Date.now() + 10*1000),maxAge: 10 *1000,httpOnly:true});
res.end('ok')
})
app.get('/read',function (req,res) {
res.end(JSON.stringify(req.cookies));
});
app.get('/read2',function (req,res) {
res.end(JSON.stringify(req.cookies));
});
app.listen(3000);
复制代码
let querystring = require('querystring');
module.exports = function(){
return function(req,res,next) {
let cookie = req.headers.cookie;
if(cookie) {
req.cookies = querystring.parse(cookie,'; ');
next();
}else{
req.cookies ={};
next();
}
}
}
复制代码
app.use(function(req,res,next){
res.cookie = function(key ,val, options){
let pairs = [`${key} = ${encodeURIComponent(val)}`];
if(options.domain){
pairs.push(`Domain=${options.domain}`)
}
if(options.path){
pairs.push(`Path=${options.path}`)
}
if(options.expires){
pairs.push(`Expires=${options.expires.toUTCString()}`)
}
if(options.maxAge){
pairs.push(`max-Age=${options.maxAge}`)
}
if(options.httpOnly){
pairs.push(`httpOnly=true`)
}
}
let cookie = pairs.jion('; ');
res.setHeader('Set-Cookie',cookie)
})
复制代码
let express = require('express');
let cookieParser = require('cookie-parser');
let app = express();
app.use(cookieParser());
app.get('/visit',function(req,res){
let visit = req.cookies.visit;
console.log(visit);
if(visit){
visit = isNaN(visit) ? 0 : Number(visit) + 1;
}else{
visit = 1;
}
res.cookie('visit',visit,{path:'/visit',httpOnly:true});
res.send(`这是你第${visit}次访问`)
})
app.listen(8080);
//test:localhost:8080/visit
复制代码
为了防止服务器串改,咱们使用{signed:true}也就是加密,如今对req.cookies中间件作加密优化(加密请查看加密详解文章)express
let express = require('express');
let querystring = require('querystring')
// let cookieParser = require('cookie-parser');
let app = express();
function signed(val ,secret){ //值,秘钥 (secret在cookieparser的时候传进来)
return 's:' + val + '.' + require('crypto')
.createHmac('sha256',secret)
.update(val)
.digest('base64')
.replace(/\=+$/,'') //生成秘钥后去掉等号就是最后的结果
}
function unsign(val,secret){
let value = val.slice(2,val.indexOf('.'));
console.log('value=' + value);
console.log(val.indexOf('.'));
console.log(val);
console.log(signed(value,secret));
//signed值没有编码的,包含+的
return signed(value,secret) == val ? value : false;
}
function cookieParser(secret){
return function(req,res,next){
req.secret = secret;//将秘钥付给req,加密须要此对象
let cookie = req.headers.cookie;//name='s:9.0000
if(cookie){ //若是cookie存在则进行对比,这里的cookie拿到的是签名后的值
let cookies = querystring.parse(cookie,'; ')//{name:'s:9.0000'}
let signedCookies = {};
if(secret){//是否解密
for(let key in cookies){
signedCookies[key] = unsign(cookies[key],secret);
}
}
req.signedCookies = signedCookies;
req.cookies = cookies;
next();
}else{//不然
req.cookies = req.signedCookies = {};
next();
}
}
}
app.use(cookieParser('zdl')); //传过去秘钥
app.use(function(req,res,next){
res.cookie = function(key ,val, options){
encodeURIComponent
//req.res能够拿到响应对象,res.req能够拿到请求对象
let pairs = [`${key} = ${encodeURIComponent(signed(String(val),this.req.secret))}`];//值,密码
if(options.domain){
pairs.push(`Domain=${options.domain}`)
}
if(options.path){
pairs.push(`Path=${options.path}`)
}
if(options.expires){
pairs.push(`Expires=${options.expires.toUTCString()}`)
}
if(options.maxAge){
pairs.push(`max-Age=${options.maxAge}`)
}
if(options.httpOnly){
pairs.push(`httpOnly=true`)
}
let cookie = pairs.join('; '); //57hang
res.setHeader('Set-Cookie',cookie)
}
next();
})
app.get('/visit',function(req,res){ //63hang
let visit = req.signedCookies.visit; // let visit = req.cookies.visit;
if(visit){
visit = isNaN(visit) ? 0 : Number(visit) + 1;
}else{
visit = 1;
}
//this.req.secret
res.cookie('visit',String(visit),{signed:true});
res.send(`${visit}`)
})
app.listen(8080);
复制代码
使用cookie注意事项npm
因为cookie存储在硬盘上很容易被用户串改,咱们通常采用session,session是存在服务器上的json
每次访问服务器同一个卡号对应服务器端存储信息
let express = require('express');
let cookiepaser = require('cookie-parser');
let app = express();
app.use(cookiepaser())
let sessions = {};
const SESSION_KEY = 'connect.sid';
app.get('/',function(req, res){
let sessionId = req.cookies[SESSION_KEY];
if(sessionId){
let sessionObj = sessions[sessionId];
if(sessionObj){
sessionObj.balance -= 10;
res.send(`欢迎新顾客,送你一张卡,余额${sessionObj.balance}`)
}else{
genId();
}
}else{
genId();
}
function genId(){
//第一次来发会员卡 卡号惟一,不容易被猜到
let sessionId = Date.now() + Math.random() + '';
//在服务器端开辟内存,记录信息
sessions[sessionId] = {balance:100, name:req.query.name} ; //session对象
//把卡号经过cookie发给客户端
res.cookie(SESSION_KEY , sessionId)
res.send('欢迎新顾客,送你一张卡,余额100')
}
})
app.listen(3000)
复制代码
须要安装$ npm install express-session
express-session是用来获取和绑定session的,当你使用此以后在req商会多一个req.session
let express = require('express');
let querystring = require('querystring')
let session = require('express-session');
let app = express();
//secret, resave,saveUninitialized
app.use(session({
secret: 'zdl',
resave: true,
saveUninitialized: true
}));
app.get('/visit',function(req,res){
let visit = req.session.visit;
if(visit){
visit = isNaN(visit) ? 0 : Number(visit) + 1;
}else{
visit = 1;
}
req.session.visit = visit;
res.send(`${visit}`)
})
app.listen(8080);
//test:localhost:8080/visit
复制代码
name | detailed |
---|---|
name | 设置 cookie 中,保存 session 的字段名称,默认为 connect.sid |
store | session 的存储方式,默认存放在内存中,也可使用 redis,mongodb 等 |
secret | 经过设置的 secret 字符串,来计算 hash 值并放在 cookie 中,使产生的 signedCookie 防篡改 |
cookie | 设置存放 session id 的 cookie 的相关选项,默认为 (default: { path: '/', httpOnly: true, secure: false, maxAge: null }) |
genid | 产生一个新的 session_id 时,所使用的函数, 默认使用 uid2 这个 npm 包 |
rolling | 每一个请求都从新设置一个 cookie,默认为 false |
saveUninitialized | 是指不管有没有session cookie,每次请求都设置个session cookie ,默认给个标示为 connect.sid |
resave | 是指每次请求都从新设置session cookie,假设你的cookie是10分钟过时,每次请求都会再设置10分钟 |
须要安装部分中间件,这里就不一一列举了
let express = require('express');
let path = require('path');
let querystring = require('querystring')
let bodyPaser = require('body-parser')
let session = require('express-session');
let app = express();
app.set('view engine','html');
app.use(bodyPaser.urlencoded({extended:true}))
app.set('views',path.resolve(__dirname,'views'));
app.engine('.html',require('ejs').__express);
//secret, resave,saveUninitialized
app.use(session({
secret: 'zdl',
resave: true,
saveUninitialized: true
}));
let users = [];
//注册
app.get('/reg',function(req,res){
res.render('reg',{title: '注册'})
})
app.post('/reg',function(req,res){
let user = req.body;
users.push(user);
res.redirect('/login');//重定向到login页面
})
//登陆
app.get('/login',function(req,res){
res.render('login',{title: '登陆'})
})
app.post('/login',function(req,res){
let user = req.body;
let oldUser = users.find(item => user.username == item.username && user.password == item.password);
if(oldUser){
req.session.user = oldUser; //跳转以前记录下用户
res.redirect('/user')
}else{
res.render('back')
}
})
function checkuser(req,res,next){
if(req.session.user){
next()
}else{
res.redirect('/login')
}
}
app.get('/user',checkuser,function(req,res){
//在没有登陆的状况下直接访问会报错,须要给她配置一个检查,写个中间件
res.render('user',{username:req.session.user.username,title:'用户中心'})
})
//退出
app.get('/logout',function(req,res){
delete req.session.user;
res.redirect('/login')
})
app.listen(8080);
复制代码
session会话和seesionsttorge不同的
客户端访问服务器,一般在浏览器访问量很大的时候,咱们会有不少台服务器,这首咱们通常会随机分配(其实有些策略像nginx负载均衡,例如轮询等等,后面的文行坑能会更新这一部份内容),大概原理就是咱们请求不一样的服务器,不一样的服务器将数据统一在统一台数据库作处理,这边其实不是服务器,实际上是集群,全部服务器读取一体,下面咱们用redis实现一下
安装connect-redis中间件使用 须要下载此中间件
+redis数据库
//在上面js基础上添加
...
//经过redis存储session
const ResiStore = require('connect-redis')(session);
...
app.use(session({
...
//store制定把会话数据放在哪一个地方
store:new ResiStore({ // 制定redis放在哪一个地方
host: 'localhost',
port: 6379
})
}));
let users = [];
//注册
app.get('/reg',function(req,res){...})
...
app.listen(8080);
复制代码
一样将seesion存到文件,相似
//本身实现一个模块将session数据放在文件系统里
const FileStore = require('./connect-file')(session);
...
app.use(session({
...
store:new FileStore({ // 制定redis放在哪一个地方
dir : path.resolve(__dirname,'sessions')
})
}));
let users = [];
//注册
...
app.listen(8080);
复制代码
const FileStore = require('./connect-file')(session);
store:new FileStore({dir : path.resolve(__dirname,'sessions') })
上述两行代码能够看出
./connect-file
导出的FileStore
是个函数new FileStor
像是个类,也像是个构造函数get
获取sessionset
设置sessiondestory
销毁session,也就是读,写,销毁let util = require('util');
let path = require('path');
let mkdirp = require('mkdirp');//集群建立目录的模块 须要安装
let fs = require('fs');
//返回函数传进去参数session
function createFileStore(session){
//store类是全部自定义存储的基类
let Store = session.Store;
util.inherits(FileStore,Store);//FileStore继承Store类
//此目录存放着全部的session对象,每一个对象都是json文件
function FileStore(options = {}){
// new FileStore({dir : path.resolve(__dirname,'sessions') })
let {dir = path.resolve(__dirname,'session')} = options;
this.dir = dir; //保存到实例上,在portotype可使用
mkdirp(this.dir);//级联建立文件,fs.mkdir父目录不存在不能建立子目录,此模块此须要安装
}
//FileStore是个类
//经过sessonid拿到对应的文件名
FileStore.prototype.resolve = function(sid){
return path.resolve(this.dir,`${sid}.json`)//文件名,每一个session对象对应这样一个文件
}
//经过sessonid保存session到文件中去
FileStore.prototype.set = function(sid , session , callback){
fs.writeFile(this.resolve(sid),JSON.stringify(session),callback)
}
//sid卡号
//经过sessonid获取文件系统中存放的session对象
FileStore.prototype.get = function(sid , callback){
fs.readFile(this.resolve(sid),'utf8',function(err,data){
if(err) callback(err);
else callback(err, JSON.parse(data))
})
}
FileStore.prototype.destroy = function(sid , callback){
fs.unlink(this.resolve(sid),callback)
}
return FileStore;
}
module.exports = createFileStore;
复制代码
最后会生成一个session文件夹,下面存在一个json文件存放session
let util = require('util');
const redis = require('redis');//redis文件须要下载
function createRedisStore(session){
let Store = session.Store;
util.inherits(RedisStore,Store);//子,父类
function RedisStore(options = {}){
let {} = options;
//建立客户端
this.client = redis.createClient(options.port|| 6379,options.host||'localhost');
}
RedisStore.prototype.set = function(sid , session , callback){
this.client.set(sid,JSON.stringify(session),callback)
}
RedisStore.prototype.get = function(sid , callback){
this.client.get(sid,function(err,data){
if(err) callback(err);//第一次文件不存在
callback(err,JSON.parse(data))
})
}
RedisStore.prototype.destroy = function(sid , callback){
this.client.unset(sid,callback)
}
return RedisStore;
}
module.exports = createRedisStore;
复制代码
这就是咱们的cookie,session,利用齐权限管理以及中间件的实现,和redis分布用法,宝宝我啃了两天,你们耐心一点哦