前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >前后端权限机制

前后端权限机制

作者头像
一粒小麦
发布2019-07-18 17:08:47
1.3K0
发布2019-07-18 17:08:47
举报
文章被收录于专栏:一Li小麦一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

代码语言:javascript
复制
// 插件式安装
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,提示需要做校验:

代码语言:javascript
复制
{
    path:'about',
    meta:{auth:true}
}

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

代码语言:javascript
复制
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

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

        }
    },
}  

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

代码语言:javascript
复制
<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树。此时的界面是:

再处理一下登录逻辑:

代码语言:javascript
复制
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:另写一个:

代码语言:javascript
复制
// 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中执行这个接口

代码语言:javascript
复制
// ./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完成后,执行安装:

代码语言:javascript
复制
npm i koa koa-router koa-bodyparser

然后新建一个index.js

代码语言:javascript
复制
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中引入中间件:

代码语言:javascript
复制
npm install koa-cors
代码语言:javascript
复制
var cors = require('koa2-cors');
app.use(cors())

即可实现本地跨域。

接口实现

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

代码语言:javascript
复制
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

代码语言:javascript
复制
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中执行这个函数:

代码语言:javascript
复制
import interceptor from './interceptor'
interceptor();

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

代码语言:javascript
复制
//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页面请求调用这个接口:

代码语言:javascript
复制
<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>

在后端:

代码语言:javascript
复制
// 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'
代码语言:javascript
复制
logout() {
        this.$store.dispatch('logout')
    }

然后在store/user.js中写:

代码语言:javascript
复制
        logout(ctx){
            sessionStorage.removeItem('token');
            ctx.commit('setLogin',false)
        }

在写一个注销方法:

代码语言:javascript
复制
logout(ctx){
            sessionStorage.removeItem('token');
            ctx.commit('setLogin',false)
        }

主动过期

响应拦截

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

代码语言:javascript
复制
// 设置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实例拿出来::

代码语言:javascript
复制
const app=new Vue({
  el: '#app',
  router,
  store,
  template: '<App/>',
  components: { App }
})

interceptor(app);

令牌机制

bearer token规范

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

代码语言:javascript
复制
Authorization:`Bearer`+你的token

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

json web token规范(jwt规范)

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

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

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

代码语言:javascript
复制
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')
})

由此,功能基本完成。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-05-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 一Li小麦 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 选择合适的ui库
  • 前端登录(login.vue)
    • 登录态的保存
    • nodejs服务搭建(koa2)
      • vue-cli和koa2开发环境跨域配置
        • 接口实现
        • 请求拦截器
        • 注销
        • 主动过期
          • 响应拦截
          • 令牌机制
            • bearer token规范
              • json web token规范(jwt规范)
              相关产品与服务
              消息队列 TDMQ
              消息队列 TDMQ (Tencent Distributed Message Queue)是腾讯基于 Apache Pulsar 自研的一个云原生消息中间件系列,其中包含兼容Pulsar、RabbitMQ、RocketMQ 等协议的消息队列子产品,得益于其底层计算与存储分离的架构,TDMQ 具备良好的弹性伸缩以及故障恢复能力。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档