Koa鉴权

252 阅读5分钟

掌握三种常见的鉴权方式: Session/Cookie、Token+jwt、OAuth

Session/Cookie

Cookie

cookie的原理是,浏览器第一次向服务器发送请求时,服务器在response头部设置Set-Cookie字段,浏览器收到响应就会设置cookie并储存,在下一次该浏览器向服务器发送请求时,就会在repuset头部自动带上Cookie字段,服务器端收到该cookie用以区分不同的浏览器。当然,这个cookie与某个用户的对应关系应该在第一次访问时就存在服务器端,这时就需要session。

Session

session是会话的意思,浏览器第一次访问服务端,服务端就会创建一次会话,在绘画中保存表示该浏览器的信息。它与cookie的区别就是session是缓存在服务端的,cookie则是缓存在客户端,他们都由服务端生成,为了弥补Http协议无状态的缺陷。

下载并引用session

1. 下载并引入koa-session

2. app.keys来加密cookie

//keys作用:用来对cookie进行签名
//app.keys=签名的cookie 密钥数组
app.keys = ['session secret']

3. 配置session对象(具体配置属性用途看注释)

const SESSION_CONFIG = {
  key:'qlq:session',//设置cookie的key的名字
  maxAge: 86400000,//有效期,默认是一天
  httpOnly: true,// 仅服务端修改 防止客户端修改
  signed:true,//签名cookie
}

4. app.use使用session

app.use(session(session配置,要引用的服务器))

app.use(session(SESSION_CONFIG,app));

使用redis来储存session

1. 自行百度下载redis软件(windows端会有些特殊 需要下载特定的版本)

1.1 打开

1.2 如果有下图即打开了redis服务端

2. 下载并引入koa-redis、redis模块

2.1 用redis模块里的redis.createClient创建客户端redis

redis里的createClient方法(redis服务器端口,'主机名')

2.2 koa-redis中的redisStore是用来存储的

const redisStore = require('koa-redis')
const redis = require('redis')
const client = redis.createClient(6379,'localhost')

3. 将redis存储写入session配置中

store:redisStore({解构客户端}) 通过redisStore存储到redis客户端中

const SESSION_CONFIG = {
  ...
  //如果有redis 就注册客户端到这里
  store:redisStore({...client})
}

例子

假设前端通过axios.post向后端传递了数据

async handleLogin(){
  let res = await axios.post('/login',{
    user:xxx,
    pwd:yyy
  })
  console.log(res);
}

在后端中可以直接用ctx.session.userInfo = body.user;来存储session(这里是koa框架的路由文件)

router.post('/login',async (ctx,next)=>{
  // console.log(ctx.request.body);
  const {body} = ctx.request;
  // console.log('body',body);
  //验证数据库 做存储
  ctx.session.userInfo = body.user;
  ctx.body = {
    ok:1,
    message:'登陆成功'
  }
})

然后在需要用户登录才能获得数据的接口做是否有 ctx.session.userInfo的处理 如果有,则获取数据,没有则不获取数据

假设前端有个请求要获取登录后信息

async getUser(){
  let res = await axios.get('/user/getUser')
  console.log(res);
}

后端处理,这里第二个参数是写了一个鉴权文件来判断是否有session.userInfo

router.get('/getUser',require('../middleware/auth'),async (ctx,next)=>{
  ctx.body = {
    ok:1,
    message:'获取数据成功',
    userInfo: ctx.session.userInfo
  }
})

鉴权文件

如果有session则放行 无则直接返回

module.exports = async(ctx,next)=>{
  if(!ctx.session.userInfo){
    ctx.body={
      ok:0,
      message:'用户未登录'
    }
  }else{
    await next();
  }
}

#### 删除session
在需要删除session的对应后端接口 用delete
```js
delete ctx.session.userInfo;

Token+jwt

token存储在localStorage

下载并引入koa-jwt、jsonwebtoken中间件(在对应路由)

const jwt = require('jsonwebtoken') //生产令牌
const jwtAuth = require('koa-jwt') //验证令牌

用户登录获取信息实例

前端请求代码

async handleLogin(){
          let res = await axios.post('/user/login-token',{
            user:this.user,
            pwd:this.pwd
          })
          localStorage.setItem('token',res.data.token);
          // console.log(res);
        },

1. 后端接受到前端请求时要给其创建一个token

1.1 秘钥签名

const secret = 'this is a secreat' //签名

1.2 在ctx.body里用token属性:jwt.sigh()创建

jwt.sign() 两个参数 第一个参数是包含当前数据以及过期时间的对象 第二个参数是秘钥签名

router.post('/login-token',async (ctx,next)=>{
  const {body} = ctx.request;
  const userInfo = body.user;
  ctx.body = {
    ok:1,
    message:'登陆成功',
    user:userInfo,
    token: jwt.sign({
      data:userInfo, //由于签名不加密,令牌不要生成敏感信息如密码
      exp:Math.floor(Date.now()/1000) + 60 * 60, //过期时间一分钟
    },secret)
  }
})

2. 前端对token进行操作

2.1 在前端登录的post请求中将token添加到localStorage

        async handleLogin(){
          let res = await axios.post('/user/login-token',{
            user:this.user,
            pwd:this.pwd
          })
          localStorage.setItem('token',res.data.token);
        },

2.2 将token放入请求头中

创建一个请求拦截器,判断localStorage是否有token,如果有用
config.headers.common['authorization'] = 'Bearer ' + token;给每个http header都加上token
Bearer是jwt的认证头部信息 记得要加空格

    axios.interceptors.request.use(config=>{
      //对请求数据做点什么
      const token = localStorage.getItem('token');
      if(token){
        // Bearer是jwt的认证头部信息 记得要加空格
        config.headers.common['authorization'] = 'Bearer ' + token;
      }
      return config;
    },err=>{
      return Promise.reject(error);
    })

3. 对需要鉴权的接口鉴权

假设前端有个获取用户信息需要权限

        async getUser(){
          let res = await axios.get('/user/getUser-token')
        }

3.1 在后端接口用jwtAuth({验证签名})来验证

如果没有token或验证不通过会自动报错

router.get('/getUser-token', jwtAuth({ secret }),async (ctx,next)=>{
  console.log(ctx.state);
  ctx.body = {
    userInfo: ctx.state.user.data,
    message:'数据获取成功'
  }
})

4.移除token

只需要在需要移除token的操作直接把token从localStorage移除,甚至都不需要访问后端

localStorage.removeItem('token')

OAuth(第三方鉴权)

做一个github第三方登录

首先一个前端页面登录

<a href="/user/login-github">github登录</a>

1.先模拟一个已登录用户

1.1 登录github 在setting中找到developer settings

1.2 找到OAuth Apps 创建一个OAuth App

下面的回调url就是oauth流程图里的

1.3 设置好后会有对应的id和secret

1.4 后端设置一个配置含有这两个值

const config = {
  client_id: '7853729cd163dc5bfa58',
  client_secret: 'c0495c79c022afc992930bc580fe5ebafb080dfa'
}

2. 后端收到get请求后跳转到github授权页面

注意这里是拿到授权id跟secret

router.get('/login-github', async (ctx, next) => {
  //重定向到github授权页面
  const path = `https://github.com/login/oauth/authorize?client_id=${config.client_id}`
  ctx.redirect(path);
})

3. 后端创建一个github回调的接口(步骤1中回调url)来获取授权码

  • 这里可以用ctx.query.code来获取code授权码
router.get('/oauth/github/callback', async (ctx, next) => { //这是一个授权回调,用于获取授权码
  const code = ctx.query.code; //github 回调传回的code授权码
  //带着授权码code、client_id、client_secret向github认证服务器请求token
  const params = {
    client_id: config.client_id,
    client_secret: config.client_secret,
    code: code
  }
})

4. 携带client_id、client_secret、code向github要令牌token

这里用了queryString 要引入该模块

queryString.parse转化res.data 获取对应的access_token

这步骤卸载回调接口里

let res = await axios.post('https://github.com/login/oauth/access_token', params)
  //要令牌token
const access_token = querystring.parse(res.data).access_token;
console.log(access_token);

5. 带着token从Github获取用户信息

注意要设置请求头 其它方式有可能会报错

  res = await axios({
    method: 'get',
    url: 'https://api.github.com/user',
    headers:{
      accept:'application/json',
      Authorization:`token ${access_token}`
    }
  })

后端的示例代码

const config = {
  client_id: '7853729cd163dc5bfa58',
  client_secret: 'c0495c79c022afc992930bc580fe5ebafb080dfa'
}

//OAuth授权登录
router.get('/login-github', async (ctx, next) => {
  //重定向到github授权页面
  const path = `https://github.com/login/oauth/authorize?client_id=${config.client_id}`
  ctx.redirect(path);
})

router.get('/oauth/github/callback', async (ctx, next) => { //这是一个授权回调,用于获取授权码
  const code = ctx.query.code; //github 回调传回的code授权码
  //带着授权码code、client_id、client_secret向github认证服务器请求token
  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)
  //要令牌token
  const access_token = querystring.parse(res.data).access_token;
  console.log(access_token);
  //带着token从Github获取用户信息
  res = await axios({
    method: 'get',
    url: 'https://api.github.com/user',
    headers:{
      accept:'application/json',
      Authorization:`token ${access_token}`
    }
  })
  // res = await axios.get('https://api.github.com/user?access_token=' + access_token)
  console.log('userAccess', res.data);
  ctx.redirect('/hello.html')
})
文章参考致 路飞学城的小马哥老师