Koa实战 - 鉴权

此文项目代码:https://github.com/bei-yang/I-want-to-be-an-architect
码字不易,辛苦点个star,感谢!

引言


此篇文章主要涉及以下内容:

  1. Session/Cookie
  2. Token
  3. OAuth

session-cookie方式


  • 原理


实现原理:

  1. 服务器在接受客户端首次访问时在服务器端创建session,然后保存session(我们可以将session保存在内存中,也可以保存在Redis中,推荐使用后者),然后给这个session生成一个唯一的标识字符串,然后在响应头中种下这个唯一标识字符串。
  2. 签名。这一步通过秘钥对sid进行签名处理,避免客户端修改sid。(非必需步骤)
  3. 浏览器中收到请求响应的时候会解析响应头,然后将sid保存在本地cookie中,浏览器在下次http请求的请求头中带上该域名下的cookie信息
  4. 服务器在接受客户端请求时会去解析请求头cookie中的sid,然后根据这个sid去找服务器端保存的该客户端的session,然后判断该请求是否合法。
  • koa中的session使用:npm i koa-session -S
app.keys = ['some secret', 'another secret'];
const SESS_CONFIG = {
  key: 'kkb:sess',
  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 + '次访问';
});
  • 使用哪个Redis存储session
    • 安装:npm i -S koa-redis
    • 配置使用:
    const redisStore = require('koa-redis');
    const redis = require('redis')
    const client = redis.createClient(6379, "localhost");
    app.use(session({
      key: 'kkb:sess',
      store: redisStore({
        client
      })
    }, app));
    app.use(ctx => {
      //...
      client.keys('*', (err, keys) => {
        console.log(keys);
        keys.forEach(key => {
          client.get(key, (err, val) => {
            console.log(val);
          })
        })
      })
    });
    
  • 案例:通过session实现用户鉴权
    • 登录页面,./public/login.html
    <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 @click="login">Login</button>
          <button @click="logout">Logout</button>
          <button @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.defaults.withCredentials = true;
        axios.interceptors.response.use(response => {
          app.logs.push(JSON.stringify(response.data));
          return response;
        });
        var app = new Vue({
          el: "#app",
          data: {
            username: "test",
            password: "test",
            logs: []
          },
          methods: {
            login: async function () {
              await axios.post("/users/login", {
                username: this.username,
                password: this.password
              });
            },
            logout: async function () {
              await axios.post("/users/logout");
            },
            getUser: async function () {
              await axios.get("/users/getUser");
            }
          }
        });
      </script>
    </body>
    
    </html>
    
    • 登录/注销接口,./routes/users.js
    router.post("/login", async ctx => {
      const {
        body
      } = ctx.request;
      console.log("body", body);
      ctx.session.userinfo = body.username;
      ctx.body = {
        ok: 1,
        message: "登录成功"
      };
    });
    router.post("/logout", async ctx => {
      delete ctx.session.userinfo;
      ctx.body = {
        ok: 1,
        message: "登出系统"
      };
    });
    router.get("/getUser", async ctx => {
      ctx.body = {
        ok: 1,
        message: "获取数据成功",
        userinfo: ctx.session.userinfo
      };
    });
    
    • 路由守卫中间件,./middleware/auth.js
    module.exports = async (ctx, next) => {
      if (!ctx.session.userinfo) {
        ctx.body = {
          ok: 0,
          message: "用户未登录"
        };
      } else {
        await next();
      }
    };
    
    • 应用守卫,./routes/users.js
    router.get("/getUser", require("./middleware/auth"), async ctx => {...})
    

Token验证


  • 原理


  • 案例:令牌认证
    • 登录页,public/login-token.html
    <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: {
            login: async function () {
              const res = await axios.post("/users/login-token", {
                username: this.username,
                password: this.password
              });
              localStorage.setItem("token", res.data.token);
            },
            logout: async function () {
              localStorage.removeItem("token");
            },
            getUser: async function () {
              await axios.get("/users/getUser-token");
            }
          }
        });
      </script>
    </body>
    
    </html>
    
    • 登录接口
      1. 安装依赖:npm i jsonwebtoken koa-jwt -S
      2. 接口编写,routes/users.js
      const jwt = require("jsonwebtoken");
      const jwtAuth = require("koa-jwt");
      const secret = "it's a secret";
      router.post("/login-token", async ctx => {
        const {
          body
        } = ctx.request;
        const userinfo = body.username;
        ctx.body = {
          message: "登录成功",
          user: userinfo,
          token: jwt.sign({
              data: userinfo,
              exp: Math.floor(Date.now() / 1000) + 60 * 60 //一小时后过期
            },
            secret
          )
        };
      });
      router.get(
        "/getUser-token",
        jwtAuth({
          secret
        }),
        async ctx => {
          //获取session
          ctx.body = {
            message: "获取数据成功",
            userinfo: ctx.state.user.data
          };
        }
      );
      

OAuth(开放授权)


  • 概述:三方登入主要基于OAuth2.0。OAuth协议为用户资源的授权提供了一个安全的、开放而又简易的标准,与以往的授权方式不同之处是OAuth的授权不会使第三方触及到用户的账户信息(如用户名和密码),即第三方无需使用用户的用户的用户名与密码就可以申请获得该用户资源的授权,因此OAuth是安全的。
  • OAuth登录流程


  • 案例:OAuth登录
    • 登录页面,login-oauth.html
    <html>
    
    <head>
      <title>OAuth</title>
    </head>
    
    <body>
      <div id="app">
        <a href='/users/login-github'>Github登录</a>
      </div>
    </body>
    
    </html>
    
    • 登录接口,routes/user.js
      const config = {
      client_id: "a141111525bac2f1edf2",
      client_secret: "8e37306c1451e60412754ace80edee4ca937564a"
    };
    const axios = require('axios')
    const querystring = require('querystring')
    router.get("/login-github", async ctx => {
      //重定向到认证接口,并配置参数
      const path = `https://github.com/login/oauth/authorize?
    client_id=${config.client_id}`;
      //转发到授权服务器
      ctx.redirect(path);
    });
    router.get("/oauth/github/callback", async ctx => {
      const code = ctx.query.code;
      const params = {
        client_id: config.client_id,
        client_secret: config.client_secret,
        code: code
      };
      let res = await axios.post(
        "https://github.com/login/oauth/access_token",
        params
      );
      console.log(res.data);
      const access_token = querystring.parse(res.data).access_token;
      res = await axios.get(
        "https://api.github.com/user?access_token=" + access_token
      );
      console.log("userAccess:", res.data);
      ctx.redirect("/hello.html");
    });
    

你的赞是我前进的动力

求赞,求评论,求转发...

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,042评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 89,996评论 2 384
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,674评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,340评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,404评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,749评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,902评论 3 405
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,662评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,110评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,451评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,577评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,258评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,848评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,726评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,952评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,271评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,452评论 2 348

推荐阅读更多精彩内容