cookie
session
原由
由于 http 是无状态的协议,一旦客户端和服务器的数据交换完毕,就会断开连接,再次请求,会重新连接,这就说明服务器单从网络连接上是没有办法知道用户身份的。为了解决这个问题,就给每次新的用户请求时,发一个身份证,每次访问都要带上身份证,这样服务器就知道是谁来访问了,针对不同的用户做出不同的响应。
会话跟踪是Web程序中常用的技术,用来跟踪用户的整个会话。常用的会话跟踪技术是
Cookie
与Session
。Cookie
通过在客户端记录信息确定用户身份,Session
通过在服务器端记录信息确定用户身份。
cookie是什么?
cookie是服务端发送到用户浏览器存储在客户端的状态,它会在浏览器下次向同一服务器发起请求时,被自动携带到服务器上。
以node为例设置cookie
const Koa = require('koa')
const Router = require('koa-router')
const static = require('koa-static')
const app = new Koa();
const router = new Router();
app.use(static(__dirname,'/'))
router.get('/',async ctx=>{
//观察请求cookie存在
console.log('cookie:',ctx.header.cookie)
//设置cookie
ctx.set('Set-Cookie','cookie1=1234')
})
app.use(router.routes())
app.listen(3000,() => {
console.log('3000端口已开启');
})
session是什么?
session会话机制是一种服务器端机制,这里我们想象一下,如果将用户的一些信息都存入Cookie中的话,一旦信息被拦截,那么我们所有的用户信息都会丢失掉。所以就出现了Session,在一次会话中将重要信息保存在Session中,Cookie只记录SessionId一个SessionId对应一次会话请求。
session实现原理
- 服务器在接受客户端首次访问时在服务器端创建session,然后保存session(我们可以将session保存在内存中,面对服务器集群我们需要将session保存在redis中),然后给这个session生成一个唯一的标识sessionId,然后在响应头种下sessionId
- 签名,这一步通过后端密钥对sessionId进行签名处理,避免客户端修改sessionId
- 浏览器收到请求响应的时候解析响应头,然后将sessionId保存在cookie中,浏览器在下次http请求的请求头中会带上该域名下的cookie信息。
- 服务器在接受客户端请求时会去解析请求头cookie中的sessionId,然后根据这个sessionId在服务端查询该用户信息
koa-session
在koa中使用session,就要用到koa-session中间件
npm i koa-session -S
const Koa = require('koa')
const router = require('koa-router')()
const session = require('koa-session')
const cors = require('koa2-cors')
const bodyParser = require('koa-bodyparser')
const static = require('koa-static')
const app = new Koa();
const redisStore = require('koa-redis');
const redis = require('redis');
const redisClient = redis.createClient(6379,"localhost")
const wrapper = require('co-redis')
const client = wrapper(redisClient)
//签名key keys作用用来对cookie进行签名
app.keys = ['some secret'];
//配置session的中间件
const SESS_CONFIG = {
key: 'kkb:sess', // 名
maxAge: 8640000, // 有效期
httpOnly: true, // 服务器有效
signed: true // 签名
store: redisStore({ client }) //session保存到redis
}
app.use(static(__dirname + '/'));
app.use(bodyParser())
app.use(session(SESS_CONFIG,app));
app.use((ctx, next) => {
if (ctx.url.indexOf('login') > -1) {
next()
} else {
console.log('session', ctx.session.userinfo)
if (!ctx.session.userinfo) {
ctx.body = {
message: "登录失败"
}
} else {
next()
}
}
})
router.post('/login', async (ctx) => {
const {
body
} = ctx.request
console.log('body',body)
//设置session
ctx.session.userinfo = body.username;
ctx.body = {
message: "登录成功"
}
})
router.post('/logout', async (ctx) => {
//删除session
delete ctx.session.userinfo
ctx.body = {
message: "登出系统"
}
})
router.get('/getUser', async (ctx) => {
ctx.body = {
message: "获取数据成功",
userinfo: ctx.session.userinfo
}
})
app.use(router.routes());
app.use(router.allowedMethods());
app.listen(3000,()=>{
console.log('3000端口已开启')
});
这里为什么要将session存储在redis?
kos-session存在弊端,session信息未加密存储在客户端cookie中,浏览器cookie有长度限制,所以我们要将session存储在外部存储中。
上述session-cookie方式是个怎样的过程?
- 用户登录的时候,服务端生成一个唯一的会话标识,并以它为key存储数据
- 会话标识在客户端和服务端之间通过cookie进行传输
- 服务端通过会话标识可以获得会话相关信息,然后对客户端的请求进行响应如果找不到有效的会话,那么认为用户是未登录状态
- 会话会有过期时间,也可以通过一些操作(比如登出)来主动删除
token是什么?
Token 是访问资源接口(API)时所需要的资源凭证。
token与session有哪些不同?
- session要求服务端存储信息,并且能够根据id进行检索,而token不需要(因为信息就在token中,这样就实现了服务器无状态化)。在大规模系统中,对每个请求都检索会话信息可能是一个复杂和耗时的过程。但另外一方面服务端要通过token来解析用户身份需要定义好相对应的协议(比如JWT)
- session一般通过cookie来交互,而token方式更灵活,可以是cookie、header,也可以放在请求的内容中。不使用cookie可以带来跨域的便利性
- token生成的方式更加多样化,可以由第三方模块来提供
- token若被盗用,服务端无法感知,cookie信息存储在用户自己电脑上,被盗用风险略小
token原理
- 客户端使用用户名和密码请求登录
- 服务端收到请求,去验证用户名和密码
- 验证成功后,服务端会签发一个令牌(Token),再把这个令牌发送给客户端
- 客户端收到
Token
以后,把它存储起来,比如放在cookie
或者localStore
里 - 客户端每次向服务端请求资源的时候需要带着服务器签发的
Token
- 服务端收到请求,然后去验证客户端请求里面带着的Token,如果验证成功,就向客户端返回请求的数据
利用koa模拟接口
安装依赖 npm i jsonwebtoken koa-jwt -S
后端代码:
const Koa = require('koa')
const Router = require('koa-router')
const static = require('koa-static')
const bodyParser = require('koa-bodyparser')
const cors = require('koa2-cors')
const jwt = require('jsonwebtoken')
const jwtAuth = require('koa-jwt')
// 后端密钥
const secret = 'balabal ssadhd'
const app = new Koa();
const router = new Router();
app.use(static(__dirname + '/'))
app.use(bodyParser())
router.post('/login-token',async ctx => {
const {body} = ctx.request;
const userinfo = body.username;
ctx.body = {
message:'登录成功',
user:userinfo,
token:jwt.sign( //生成token
{
//用户信息
data:userinfo,
// 设置token有效期
exp:Math.floor(Date.now() / 1000) + 60 * 60
},
secret
)
}
});
router.get('/getUser-token',jwtAuth({secret}),async ctx => {
// 验证token通过
console.log(ctx.state.user)
ctx.body = {
message:'获取数据成功',
userinfo:ctx.state.user.data
}
})
app.use(router.routes())
app.use(router.allowedMethods())
app.listen(3000,() => {
console.log('3000端口已开启')
})
前端代码:
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>
<body>
<div id="app">
<div>
<input v-model="username" />
<input v-model="password" />
</div>
<div>
<button v-on:click="login">Login</button>
<button v-on:click="logout">Logout</button>
<button v-on:click="getUser">GetUser</button>
</div>
<div>
<button @click="logs=[]">Clear Log</button>
</div>
<!-- 日志 -->
<ul>
<li v-for="(log,idx) in logs" :key="idx">
{{ log }}
</li>
</ul>
</div>
<script>
axios.interceptors.request.use(
config => {
const token = window.localStorage.getItem("token");
if (token) {
// 判断是否存在token,如果存在的话,则每个http header都加上token
// Bearer是JWT的认证头部信息
config.headers.common["Authorization"] = "Bearer " + token;
}
return config;
},
err => {
return Promise.reject(err);
}
);
axios.interceptors.response.use(
response => {
app.logs.push(JSON.stringify(response.data));
return response;
},
err => {
app.logs.push(JSON.stringify(response.data));
return Promise.reject(err);
}
);
var app = new Vue({
el: "#app",
data: {
username: "test",
password: "test",
logs: []
},
methods: {
async login() {
const res = await axios.post("/login-token", {
username: this.username,
password: this.password
});
localStorage.setItem("token", res.data.token);
},
async logout() {
localStorage.removeItem("token");
},
async getUser() {
await axios.get("/getUser-token");
}
}
});
</script>
</body>
</html>
让我们看一看实现过程
- 用户登录的时候,服务端生成一个token返回给客户端
- 客户端后续的请求头都带上这个token
- 服务端解析验证token获取用户信息,并响应用户的请求
- token会有过期时间,用户登出的时候会废弃token,但是服务端不需要任何操作
JWT是什么?
JSON Web Token(缩写 JWT)是目前最流行的跨域认证解决方案,JWT代表着用户所有数据都保存在客户端,每次请求都发回服务器。
参考 阮一峰 JWT 入门教程