专栏首页一Li小麦前后端权限机制

前后端权限机制

本项目将在GitHub上维护更新。

https://github.com/dangjingtao/FeRemarks


选择合适的ui库

本项目使用vue全家桶,axios和cube-ui cube-ui文档地址:https://didi.github.io/cube-ui/#/zh-CN/docs/quick-start

// 插件式安装
vue add cube-ui

相关模板说明地址https://github.com/cube-ui/cube-template/wiki

在我的电脑里,按官网的思路装怎么都不行。那可以基于这个脚手架进行: https://github.com/cube-ui/cube-template 令牌思路

  • 传统时后端存放session,对于spa应用来说,并不适合
  • 令牌,有效期。前端会携带令牌(存在session),令牌有有效期。后端根据非对称加密

前端登录(login.vue)

新建三个页面:about,home和login 在登录about时,给一个meta,提示需要做校验:

{
    path:'about',
    meta:{auth:true}
}

从本地拿token,如果拿不到,不显示登录成功态。

router.beforeEach((to,from,next)=>{
  if(to.meta.auth){
    //只要本地有token就认为登录了
    const token=sessionStorage.getItem('token');
    if(token){
      next();
    }else{
      next({
        path:'/login',
        query:{
          redirect:to.path
        }
      })
    }
  }else{
    next()
  }
})

这个功能就实现了。

登录态的保存

这是一种比较low的方式。最好放在vuex里。 在vuex中还要设置module

// 用户登录 user.js
export default {
    state:{
        isLogin:!!localStorage.getItem('token')
    },
    mutations:{
        setLogin(state,val){
            state.isLogin=val
        }
    },
    actions:{
        login(){

        }
    },
}  

接下来就是写一套登录界面:

<template>
    <div style="padding-top:200px;">
        <h2 style="font-size:30px;line-">Login</h2>
        <cube-form
        :model="model"
        :schema="schema"
        :immediate-validate="false"
        @validate="validateHandler"
        @submit="submitHandler"></cube-form>
    </div>
</template>

<script>
    export default {
  data() {
    return {
      validity: {},
      valid: undefined,
      model: {
          username:'',
          password:''
      },
      schema: {
        groups: [
          {
            fields: [
              {
                type: 'input',
                modelKey: 'username',
                label: 'username',
                props: {
                  placeholder: 'Please input username'
                },
                rules: {
                  required: true
                },
                trigger: 'blur'
              },
              {
                type: 'input',
                modelKey: 'password',
                label: 'password',
                props: {
                  type:'password',
                  placeholder: 'Please input your password'
                },
                rules: {
                  required: true
                },
                trigger: 'blur'
              },
              {
                type: 'submit',
                label: 'Submit'
              },
            ]
          }]},
      options: {
        scrollToInvalidField: true,
        layout: 'standard' // classic fresh
      }
    }
  },
  methods: {
    submitHandler(e) {
      e.preventDefault()
      console.log('submit', e)

    },
    validateHandler(result) {
      this.validity = result.validity
      this.valid = result.valid
      console.log('validity', result.validity, result.valid, result.dirty, result.firstInvalidFieldIndex)
    },
  }
}

</script>

<style scoped></style>

cube-ui采用纯数据驱动的ui。很像虚拟dom树。此时的界面是:

再处理一下登录逻辑:

submitHandler(e) {
      e.preventDefault()
      let params={
          username:this.model.username,
          password:this.model.password
      }

      console.log(params)
      this.$store.dispatch('login',params).then(sucess=>{
          if(success){
              const path=this.$route.query.redirect||'/';
              this.$router.push(path)
          }
      }).catch(err=>{
          const toast=this.$createToast({
              time:2000,
              txt:'login failed',
              type:'error'
          }).show
      });
    }

接下来是调用store内的action,根据函数式编程的处理思路,action只作为类似控制器,最后只返回异步结果,不负责具体的数据处理。因此可在src根目录中写一个service文件夹,加一个user.js:另写一个:

// service/user.js
import axios from 'axios';
const path='http://localhost:3000'

export default {
    login(user){
        return axios.post(path+'/api/login',user).then(({data})=>{
            return data;
        });
    }
}

然后再loginAction中执行这个接口

// ./store/user.js
actions:{
        login(ctx,params){
            return us.login(params).then(({token})=>{
                //只关心是否拿到token
                if(token){
                    commit('setLogin',true);
                    sessionStorage.setItem('token',token);
                    return true;
                }
                
                return false;
            });
        }

nodejs服务搭建(koa2)

这时候请求接口,肯定是404的。还记得vue.config.js吗?可以实现。 但现在是写一个真正的服务器(基于koa2)。 新建一个文件夹be, koa2系列并不是一套完整的脚手架。需要router接收请求,bodyparser来获取post参数。 npm init完成后,执行安装:

npm i koa koa-router koa-bodyparser

然后新建一个index.js

const koa=require('koa');
const Route=require('koa-router')
const bodyparser=require('koa-bodyparser')
const router=new Route();
// 接口
router.post('/api/login',async (ctx,next)=>{
    ctx.response.body='aaa'
})

const app=new koa();
app.use(cors())
app.use(router.routes())
app.use(bodyparser)
app.listen('3000',()=>{
    console.log('server is runing at http://localhost:3000')
})

node app.js,即可启动。

vue-cli和koa2开发环境跨域配置

现在的前后端联调是跨域的。

在koa2中引入中间件:

npm install koa-cors
var cors = require('koa2-cors');
app.use(cors())

即可实现本地跨域。

接口实现

如果是get请求,你可以用ctx.query拿到。 如果是post请求,ctx.request.query可以拿到请求。 假设用户名和密码叫做djtao123,发回token叫做iamtoken

router.post('/api/login',async (ctx,next)=>{
    console.log(ctx)
    const {username,password}=ctx.request.body;
    if(username=='djtao'&&password=='123'){
        ctx.response.body={code:1,token:'iamtoken'}
    }else{
        ctx.response.body={
            code:1,
            message:'check your username or password'
        }
    }  
})

请求拦截器

有了token之后,每次http请求发出,都要加载header上。这样就不用每次都带token字段去请求东西了。拿到token,就放到请求头中。 在src根目录下创建一个interceptor.js

import axios from 'axios';
export default function(){
    axios.interceptors.request.use(config=>{
        const token=sessionStorage.getItem('token');
        if(token){
            config.headers.token=token
        }
        return config;
    })
}

在main中执行这个函数:

import interceptor from './interceptor'
interceptor();

为了验证一下,写一个请求用户信息的接口,这个接口需要返回

//service/user.js
getUserInfo(){
        return axios.get(path+'/api/userinfo').then(res=>{
            return res;
        })
    }
    
 // store/user.js
  getUserInfo(ctx){
            return us.getUserInfo().then(res=>{
                return res
            })
        }

在home.vue页面请求调用这个接口:

<template>
    <div>
        this is Home page

        <div v-if="$store.state.user.isLogin">
            welcome back <span>{{this.user}}</span>
        </div>
        <div v-else>
            unlisted.
        </div>
    </div>
</template>

<script>
    export default {
        data() {
            return {
                user: ''
            }
        },
        created () {
            this.$store.dispatch('getUserInfo').then(res=>{
                if(res.data.code==1){
                    this.user=res.data.data.name
                }
            });
        },
    }
</script>

在后端:

// be/app.js
var auth=function(ctx,next){
    console.log(ctx)
    if(ctx.header.token){
        next();
    }else{
        // 直接返回401
        ctx.status = 401
    }
}
// 无法通过auth中间件认证,这个接口将不会得到预期结果
router.get('/api/userinfo',auth,async (ctx,next)=>{
    ctx.response.body={code:1,data:{name:'dangjingtao'}}
})

这里自己写了一个了auth中间件。它是一个函数,接收两个参数,分别对应ctxnext。 你可以从ctx上下文中拿到请求相关的数据。 现在请求头都带token字段了:

注销

目前login页面还是存在的,现在要把它去掉了:如果登录后,将返回注销。 注销做两件事:

  • 把session清空;
  • 把isLogin变为false 在app.vue里派发一个'logout'
logout() {
        this.$store.dispatch('logout')
    }

然后在store/user.js中写:

        logout(ctx){
            sessionStorage.removeItem('token');
            ctx.commit('setLogin',false)
        }

在写一个注销方法:

logout(ctx){
            sessionStorage.removeItem('token');
            ctx.commit('setLogin',false)
        }

主动过期

响应拦截

对于401错误,目前拦截器没有处理。 现在在interceptor中设置:

// 设置401的响应操作
    axios.interceptors.response.use((res)=>{
        return res
    },err=>{
        if(err.response.status=401){
            //清空缓存
            vm.$store.dispatch('logout');
            vm.$router.push('/login')
        }
        
        return Promise.reject(err)
    })

在main.js中,把app实例拿出来::

const app=new Vue({
  el: '#app',
  router,
  store,
  template: '<App/>',
  components: { App }
})

interceptor(app);

令牌机制

bearer token规范

服务器不关心令牌的所有者是谁。如果token被盗走了。就没法识别。以上实现本质上是bearer token规范。

Authorization:`Bearer`+你的token

防止窃取token: 别乱点,有效期要短一点:

json web token规范(jwt规范)

JWT 是 JSON Web Token 的简写,它定义了一种在客户端和服务器端安全传输数据的规范。通过 JSON 格式 来传递信息。 基本格式:

头.载荷.签名
  • 头部:加密类型,令牌类型
  • 载荷:用户信息,签发事件和过期时间(base64编码,不加密)
  • 签名:由前二者和服务器独有的密钥得到的哈希串:Hmac Sha1 256 签名是前端无法获取的,

实现jwt:主要在后端操作:安装对应依赖后:

const koa=require('koa');
const Route=require('koa-router')
var cors = require('koa2-cors');
var bodyParser = require('koa-bodyparser');


const jwt=require('jsonwebtoken');
const jwtAuth=require('koa-jwt');
const valid=require('jwt-simple')
const secret ='talk is cheap,show me the code';

const router=new Route();

router.post('/api/login',async (ctx,next)=>{
    // console.log(ctx)
    const {username,password}=ctx.request.body;
    // console.log(ctx.request.body)
    if(username=='djtao'&&password=='123'){
        let content ={name:'dangjingtao'}
        const token = jwt.sign(
            {
            data: { name: "dangjingtao" }, // 用户信息数据
            exp: Math.floor(Date.now() / 1000) + 60 * 60 // 过期时间
            },
            secret
            );
        ctx.response.body={code:1,token}
    }else{
        ctx.response.body={
            code:1,
            message:'check your username or password'
        }
    }  
});

let auth=function(ctx,next){
    let token = ctx.header.token

    try {
        valid.decode(token, secret);
        next()
    } catch (error) {
        ctx.status = 401;
        ctx.response.body={
            code:1,
            message:'check your username or password'
        }
    }

}
// 无法通过auth中间件认证,这个接口将不会得到预期结果
router.get('/api/userinfo',auth,async (ctx,next)=>{
   
    ctx.response.body={code:1,data:{name:'dangjingtao'}}
})


// router.get

const app=new koa();
app.use(cors())
app.use(bodyParser());
app.use(router.routes())


app.listen('3000',()=>{
    console.log('server is runing at http://localhost:3000')
})

由此,功能基本完成。

本文分享自微信公众号 - 一Li小麦(gh_c88159ec1309),作者:一li小麦

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-05-13

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • koa实践及其手撸

    Koa 是一个新的 web 框架,由 Express 幕后的原班人马打造, 致力于成为 web 应用和 API 开发领域中的一个更小、更富有表现力、更健壮的基石...

    一粒小麦
  • 微信网页开发

    套用《围城》里老学究的的一句开场白:"兄弟我刚入行的时候…“兄弟我是很不喜欢微信这样一款应用的——尽管我在2011年就已经是微信的注册用户。在我看来,第一个,能...

    一粒小麦
  • ​eggjs实战

    swagger会扫描配置的API文档格式自动生成一份json数据,而swagger官方也提供了ui来做通常的展示,当然也支持自定义ui的。不过对后端开发者来说,...

    一粒小麦
  • 常见登录认证 DEMO

    basic auth 是最简单的一种,将用户名和密码通过 form 表单提交的方式在 Http 的 Authorization 字段设置好并发送给后端验证

    JS菌
  • iKcamp|基于Koa2搭建Node.js实战(含视频)☞ HTTP请求

    原创作者:大哼、阿干、三三、小虎、胖子、小哈、DDU、可木、晃晃 文案校对:李益、大力萌、Au、DDU、小溪里、小哈 风采主播:可木、阿干、Au、DDU、小...

    iKcamp
  • iKcamp|基于Koa2搭建Node.js实战(含视频)☞ HTTP请求

    POST/GET请求——常见请求方式处理 ?? iKcamp 制作团队 原创作者:大哼、阿干、三三、小虎、胖子、小哈、DDU、可木、晃晃 文案校对:李益、大力...

    iKcamp
  • 蚂蚁金服估值过万亿?融资超过200亿美金,马云或将再次成为首富

    说起马云,中国网民都很熟悉,可以说是无人不知、无人不晓。每天都有数亿的用户在使用马云旗下的产品,他们亲切地称他为“马云爸爸”。除了阿里巴巴以外,马云的另外一张王...

    光荣与梦想1987
  • jboss EAP 6.2+ 通过代码控制JNDI数据源

    通过Jboss提供的API,可以操控JBoss,效果跟在管理控制台手动操作完全一样,下面是示例代码: 一、pom.xml添加依赖项 <dependency> ...

    菩提树下的杨过
  • FastAPI--中间件(6)

    所谓的中间件,其实和我们bottle中的中间件作用是一致。有些方法或操作需要在所有路由之前执行,比如要加一个http访问的拦截器,可以对部分接口API需要授权才...

    py3study
  • SAP GUI里的收藏夹事务码管理工具

    Jerry的老家,从成都乘坐高铁只要十五分钟就能到达,所以从来不会遭受春运长途跋涉之苦。这里我提前祝愿广大SAP从业者在除夕之前,都能够平安顺利到家,和自己的亲...

    Jerry Wang

扫码关注云+社区

领取腾讯云代金券