Koa 实战 - 鉴权 川长思鸟来 2023-06-20 09:29 119阅读 0赞 ### 学习目标 ### * 掌握三种常见鉴权方式 * Session/Cookie * Token * OAuth ### session-cookie方式 ### * cookie原理解析 // cookie.js const http = require("http") http.createServer((req,res)=>{ if(req.url === '/favicon.ico'){ return }else{ console.log(req.headers.cookie) // cx-abc res.setHeader('Set-Cookie','cx-abc') res.end('hello cookie') } }).listen(3000) * session原理解析 const http = require("http") const session = require("session") http.createServer((req,res)=>{ const sessionKey ='sid' if(req.url === '/favicon.ico'){ return }else{ console.log(req.headers.cookie) // cx-abc const cookie = req.headers.cookie if(cookie&&cookie.indexOf(sessionKey) > -1){ res.end('Come Back') console.log('cookie',req.headers.cookie) // 简略写法未必具有通用性 const pattern = new RegExp(`${ sessionKey}=([^;]+;?\s*)`) const sid = pattern.exec(cookie)[1] console.log('session:',sid,session,session[sid]) } else { const sid = (Math.random()*9999999).toFixed() res.setHeader('Set-Cookie',`${ sessionKey}=${ sid}`) session[sid] = { name:"laowang"} res.end('hello cookie') } } }).listen(3000) \[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r2mNJGJQ-1575905396219)(/Users/shangjiawei/Library/Application Support/typora-user-images/image-20191207102602827.png)\] * 实现原理 1. 服务器在接受客户端首次访问时在服务器创建session,然后保存在session(我们可以将session保存在内存中,也可以保存在redis中,推荐使用后者),然后给这个session生成一个唯一的表示是字符串,然后在响应头中种下这个唯一标识字符串 2. 签名。这一步通过密钥对sid进行签名处理,避免客户修改sid。(非必须) 3. 浏览器中收到请求响应的时候会解析响应头,然后将sid保存在本地cookie中,浏览器在下次http请求的请求头中会带上该域名下的cookie信息。 4. 服务器在接受客户端请求时会去解析请求头cookie中的sid,然后根据这个sid去找服务器端保存的该客户端的session,然后判断请求是否合法。 * koa-session实现 const Koa = require('koa') const app = new Koa() const session = require('koa-session') app.keys=['som secret'] const SESS_CONFIG = { key:'rockShang', // cookie的键名 maxAge:86400000, // 有效期 httpOnly:true, // 服务器有效 signed:true // 签名 } app.use(session(SESS_CONFIG,app)) app.use(ctx=>{ if(ctx.path ==='/favicon.ico') return let n = ctx.session.count || 0 ctx.session.count = ++n ctx.body = '第'+n+'次访问' }) app.listen(3000) * redis介绍 * 是一个高性能的key-value数据库 * Redis与其他key-value缓存产品有以下三个特点: * Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。 * Redis不仅仅支持简单的key-vaue类型的数据,同时还提供list,set,zset,hash等数据结构的存储 * Redis支持数据的备份,即master-slave模式的数据备份 * Redis优势 * 性能极高 - Redis能读的速度是110000次/s,写的速度时81000次/s * 丰富的数据类型-Redis支持二进制案例的String,List,Heashes * Redis支持数据的备份,即master-slave模式的数据备份。 * 安装 npm i -S koa-redis * 配置使用 // 简单使用 const redis = require('redis') const client = redis.createClient(6379,'localhost') client.set('hello','hahahhaa') client.get('hello',function(err,v){ console.log('redis key:',v) }) // 启动 redis-server // koa中的使用 const Koa = require('koa') const app = new Koa() const session = require('koa-session') const redis = require('redis') const redisClient = redis.createClient(6379,'localhost') const redisStore = require('koa-redis') const wrapper = require('co-redis') const client = wrapper(redisClient) app.keys=['som secret'] const SESS_CONFIG = { key:'rockShang', // cookie的键名 // maxAge:86400000, // 有效期 // httpOnly:true, // 服务器有效 // signed:true // 签名 store:redisStore({ client }) } app.use(session(SESS_CONFIG,app)) app.use(ctx=>{ // 查看redis redisClient.keys('*',(err,keys)=>{ console.log('keys:',keys) keys.forEach(key => { redisClient.get(key,(err,val)=>{ console.log(val) }) }) }) if(ctx.path ==='/favicon.ico') return let n = ctx.session.count || 0 ctx.session.count = ++n ctx.body = '第'+n+'次访问' }) app.listen(3000) * session可以实现信息存储,为什么还要使用redis ? 放到session内存里横向扩展不足,如果node部署在多台机器里内存是不共享的,所有需要键值服务器去存取一下session的信息,在其他node服务器中也可以获得信息。 http://localhost:3001/login-session.html Users.js router.post("/login", async ctx => { const { body } = ctx.request; if (body.name && body.pwd === "123456") { ctx.session.username = body.name; ctx.body = { ok: 1, message: "登录成功" }; } else { ctx.body = { ok: 0, message: "登录失败" }; } }); router.post("/loginout", async ctx => { delete ctx.session.username; ctx.body = { ok: 1, message: "退出登录" }; }); // 使用中间件 router.get("/info", isLogin, async ctx => { ctx.body = { ok: 1, message: ctx.session.username }; }); ### Token方式 ### * 因为只有浏览器才有cookie,才能使用session/cookie,没有浏览器的情况下,无法使用,token应用密码学 * 流程 1、 客户端使用用户密码请求登录 2、服务器收到请求,去验证用户名密码 3、验证成功后,服务器会签发一个令牌(Token),再把这个Token发送给客户端 4、客户端收到Token以后,可以把它存起来,比容放在Cookie里,或者 LocalStore里面 5、客户端每次向服务器请求资源的时候需要带着服务器签发的Token 6、服务端收到请求,然后去验证客户端请求里面带着的Token,如果验证成功,就向客户端返回请求的数据 * 案例:令牌认证 http://localhost:3001/login-token.html Login-token.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta http-equiv="X-UA-Compatible" content="ie=edge" /> <title>to do list- vue+express</title> <!-- 引入vue --> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <!-- 引入axios --> <script src="https://unpkg.com/axios/dist/axios.min.js"></script> <meta http-equiv="Access-Control-Allow-Origin" content="*" /> <!-- 引入样式 --> <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css" /> <!-- 引入组件库 --> <script src="https://unpkg.com/element-ui/lib/index.js"></script> </head> <body> <div id="app"> login-token<br /> <input v-model="name" /> <input v-model="pwd" /> <button @click="login">login</button> <button @click="loginout">loginOut</button> <button @click="clear">clearLog</button> <button @click="getInfo">getUserInfo</button> <div>{ {JSON.stringify(log)}}</div> </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 => { console.log(app, "app"); app.log.push(JSON.stringify(response.data)); return response; }, err => { app.log.push(JSON.stringify(response.data)); return Promise.reject(err); } ); </script> <script> const host = "http://localhost:3001/api"; const app = new Vue({ el: "#app", data: { name: "", pwd: "", log: [] }, created() { }, methods: { login: async function() { console.log(this.name, "this.name"); let res = await axios.post(host + "/login-token", { name: this.name, pwd: this.pwd }); window.localStorage.setItem("token", res.data.token); }, loginout: async function() { window.localStorage.removeItem("token"); }, getInfo: async function() { console.log(12); let res = await axios.get(host + "/info-token"); }, clear: async function() { this.log = []; } }, mounted: function() { this.getInfo(); } }); </script> </body> </html> Users.js // token 方式 const jwt = require("jsonwebtoken"); const jwtAuth = require("koa-jwt"); const secret = "@wewe44K"; router.post("/login-token", async ctx => { const { body } = ctx.request; // 数据库验证 const userinfo = body.name; if (userinfo) { console.log(body, "body"); ctx.body = { ok: 1, message: "登录成功", token: jwt.sign( { data: userinfo, exp: Math.floor(Date.now() / 1000) + 60 * 60 // 1小时后过期 }, secret ) }; } else { ctx.body = { ok: 0, message: "登录失败" }; } }); // 使用中间件 jwtAuth 1、验证是否正确 2、 把写入的值返回来ctx.state.user router.get("/info-token", jwtAuth({ secret }), async ctx => { console.log("state:", ctx.state.user); ctx.body = { ok: 1, message: "获取数据成功", userinfo: ctx.state.user.data }; }); * JWT(Json WEB TOKEN) 原理解析 * 1、Bearer Token包含三个组成部分:令牌头、payload、哈希 * 2、签名:默认使用base64对payload编码,使用hs256算法对令牌头、payload和密钥进行签名生成哈希 * 3、验证:默认使用hs256算法对令牌中数据签名并将结果和令牌中哈希对比 // jsonwebtoken.js const jwt = require("jsonwebtoken"); const secret = "@wewe44K"; const user = { username: "abc", password: "1111" }; const token = jwt.sign( { data: user, exp: Math.floor(Date.now() / 1000) + 60 * 60 // 1小时后过期 }, secret ); console.log("生成token+", token); //eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjp7InVzZXJuYW1lIjoiYWJjIiwicGFzc3dvcmQiOiIxMTExIn0sImV4cCI6MTU3NTg2NzMwNywiaWF0IjoxNTc1ODYzNzA3fQ.ruJ_hJdaAqS8SlyaOkwoO7rnaVX2gsf3NOFZZS9VILE console.log("解码:", jwt.verify(token, secret)); // { data: { username: 'abc', password: '1111' },exp: 1575867307,iat: 1575863707 } * 比较cookie/session与token登录方式的不同 * 都是做登录鉴权的方式,前者只能用于浏览器端,后者都可以 * 存取用户信息方面,前者通过ctx.session存取,后者通过jwt读取,通过token: jwt.sign()存储 ,ctx.state.user获得 * 前者将验证过期时间放到了cookie中,存到了后端,前端每次发送请求会通过cookie带来验证,后者通过前端把token放到header中,通过jwt鉴别 ### OAuth(开放授权) ### * 概述:三方登录主要基于OAuth2.0。OAth协议为用户资源的授权提供了一个安全的、开放而又简单的标准。与以往的授权方式不同之处是OAuth的授权不会使用第三方触及到用户的账号信息(如用户名与密码),即第三方无需使用用户的用户名与密码就可以申请获得该用户资源的授权,因此OAth是安全的。 * OAuth登录流程 ![format_png][] http://localhost:3001/login-session.html Login-session.html <a href="/api/github/login">github登录</a><br/> Users.js // github OAth登录 http://localhost:3001/login-session.html 测试页面 const axios = require("axios"); const querystring = require("querystring"); const config = { client_id: "4bfc24796006099e8c30", client_secret: "aadcc88e2d532babc28e11fff4e7f1843be7f9b9" }; router.get("/github/login", async ctx => { console.log("github login"); // const dataStr = (new Date()).valueOf(); // 重定向到认证接口,并配置参数 let path = "https://github.com/login/oauth/authorize"; // https://github.com/login/oauth/authorize?client_id=6de90ab270aea2bdb01c&redirect_uri=https://biaochenxuying.cn/login path += "?client_id=" + config.client_id + "&redirect_uri=" + "http://localhost:3001/api/github/callback"; // 转发到授权服务器 ctx.redirect(path); }); router.get("/github/callback", async ctx => { console.log("callback.."); const code = ctx.query.code; const params = { client_id: "4bfc24796006099e8c30", client_secret: "aadcc88e2d532babc28e11fff4e7f1843be7f9b9", code: code }; let res = await axios.post( "https://github.com/login/oauth/access_token", params ); const access_token = querystring.parse(res.data).access_token; res = await axios.get( "https://api.github.com/user?access_token=" + access_token ); if (res.data.name) { // 通过session存储登录信息 跳转到页面 ctx.session.username = res.data.name; ctx.redirect("http://localhost:3001/login-session.html"); } // ctx.body = ` // <h1>Hello${res.data.name}</h1> // <img src="${res.data.avatar_url}" alt=""/> // <img src="https://avatars2.githubusercontent.com/u/41536679?v=4" alt=""/> // ` }); [项目GitHub地址][GitHub] [format_png]: https://imgconvert.csdnimg.cn/aHR0cHM6Ly9zczEuYmFpZHUuY29tLzZPTlhzamlwMFFJWjh0eWhucS9pdC91PTIwMDU5NjM0MDUsMTIyMzExOTYyNyZmbT0xNzMmYXBwPTQ5JmY9SlBFRw?x-oss-process=image/format,png [GitHub]: https://github.com/s2265681/SchoolNode/tree/master/Project
相关 SpringSecurity实战(一)-认证鉴权流程 文章目录 目标 spring security 实现认证鉴权的流程 认证 授权 小灰灰/ 2022年12月27日 07:42/ 0 赞/ 181 阅读
相关 前端鉴权 前端鉴权 前端权限的控制从本质上来说,就是控制前端视图层的展示和前端所发送的请求 ,但是只有前端权限控制没有后端权限控制是万万不可的,前端权限只是达到一个锦上添花的效果 不念不忘少年蓝@/ 2022年12月21日 04:47/ 0 赞/ 232 阅读
相关 JWT鉴权实战 目录 目标 JWT简介 JWT的优点 JWT的数据结构 JWT登录鉴权流程 代码 官网解析Token ------------ 淡淡的烟草味﹌/ 2022年11月21日 14:02/ 0 赞/ 248 阅读
相关 JWT鉴权 jwt是什么? JWTs是JSON对象的编码表示。JSON对象由零或多个名称/值对组成,其中名称为字符串,值为任意JSON值。JWT有助于在clear(例如在URL中)发 我不是女神ヾ/ 2022年09月11日 05:11/ 0 赞/ 256 阅读
相关 JWT鉴权 涉及到3个微服务: geteway网关微服务、用户授权中心微服务、用户微服务; > 用户登录、请求进来, > ![3][] [3]: /images/202204 末蓝、/ 2022年04月10日 05:31/ 0 赞/ 529 阅读
相关 鉴权案例 项目背景 本项目为销售运营体系的中的一个子系统,系统中的权限分为两个部分 1、 ACL系统统一对人员进行行为鉴权 2、 数据层面 深碍√TFBOYSˉ_/ 2022年04月08日 09:44/ 0 赞/ 360 阅读
相关 Node.js 应用:Koa2 使用 JWT 进行鉴权 前言 在前后端分离的开发中,通过 Restful API 进行数据交互时,如果没有对 API 进行保护,那么别人就可以很容易地获取并调用这些 API 进行操作。那么服务器 阳光穿透心脏的1/2处/ 2022年03月22日 11:53/ 0 赞/ 246 阅读
相关 动态鉴权原理 [2019独角兽企业重金招聘Python工程师标准>>> ][2019_Python_] ![hot3.png][] 开放的平台,需要对每个客户进行鉴权,一般会提供acces 怼烎@/ 2022年01月14日 01:17/ 0 赞/ 316 阅读
相关 spring cloud jwt用户鉴权及服务鉴权 用户鉴权 客户端请求服务时,根据提交的token获取用户信息,看是否有用户信息及用户信息是否正确 服务鉴权 微服务中,一般有多个服务,服务与服务之间相互调用时,有 旧城等待,/ 2021年10月26日 13:24/ 0 赞/ 472 阅读
还没有评论,来说两句吧...