基于token的前后端分离认证实现

1 主要思路

  1. 前端编写导航守卫,如果没有localStorage中没有获取到token,则跳转登录页。

  2. 登录页,向后端登录发送,获取token,然后将token存储在localStorage中,跳转首页。

  3. 在前端请求拦截器上加上为header加上token,如果有的话。

  4. 后端的登录接口,验证完账号密码后,用itsdangerous工具提供的方法生成token,将用户名dump到token中,将此token返回前端。

  5. 后端的全局请求验证器(before_request)上,从header中获取出token,用工具解析出token中的用户名,通过request域传递到待执行的函数。

2 相关代码

  router.js

import Vue from 'vue'
import Router from 'vue-router'
import Home from './components/Home.vue'

Vue.use(Router);

const router = new Router({
  routes: [
    {
      path: '/',
      name: 'home',
      component: Home
    },
    {
      path: '/about',
      name: 'about',
      // route level code-splitting
      // this generates a separate chunk (about.[hash].js) for this route
      // which is lazy-loaded when the route is visited.
      component: () => import(/* webpackChunkName: "about" */ './components/About.vue')
    },
    {
      path: '/login',
      name: 'login',
      component: () => import('./components/Login.vue')
    },

  ]
});


// 导航守卫
// 使用 router.beforeEach 注册一个全局前置守卫,判断用户是否登陆
router.beforeEach((to, from, next) => {
  if (to.path === '/login') {
    next();
  } else {
    let token = localStorage.getItem('Authorization');
    if (!token) {
      next('/login');
    } else {
      next();
    }
  }
});

export default router;

  request.js 

import axios from 'axios'


// 创建 axios 实例
axios.defaults.headers = {
  'Content-Type': 'application/json'
};

const service = axios.create({
  timeout: 5000 // 请求超时时间
});


// request interceptor  请求拦截器,如果有token则在请求上加上token
service.interceptors.request.use(
  config => {
    if (localStorage.getItem('Authorization')) {
      config.headers.token = localStorage.getItem('Authorization');
    }

    return config;
  },
  error => {
    return Promise.reject(error);
  });

// response interceptor 返回拦截器
service.interceptors.response.use(
  response => {
    //如果返回401,则清除token,跳转登录
    if (response.data.code === 401) {
      localStorage.removeItem('Authorization');
      return this.$router.push('/login');
    }
    return response
  },
  error => {
    console.log('error' + error) // for debug
    return Promise.reject(error)
  });

export default service

  Login.vue

<template>
  <div>

    <div>
      <input type="text" v-model="loginForm.username" placeholder="用户名"/>
      <input type="text" v-model="loginForm.password" placeholder="密码"/>
      <button @click="login">登录</button>
    </div>

  </div>

</template>

<script>

import { mapMutations } from 'vuex';
import { login } from '@/api'
import { showMessage } from '@/utils'

export default {
  name: 'Login',
  components: {},
  data () {
    return {
      loginForm: {
        username: '',
        password: ''
      }
    }
  },
  methods: {
    ...mapMutations(['changeLogin']), //这样声明后,可以直接使用this.changeLogin 调用定义在store中的mutations中的方法
    login () {
      if (this.loginForm.username === '' || this.loginForm.password === '') {
        alert('账号或密码不能为空');
      } else {

        login(this.loginForm).then(response => {
          if (response.data.code === "0000") {
            this.changeLogin({Authorization: response.data.token});
            this.$router.push('/');
          } else if (response.data.code === "0001") {
            showMessage(response.data.msg,"error")
          }else{
            showMessage("发生系统级错误,请联系110","error")
          }

        })
      }
    }

  }
}
</script>

  后端代码

def login():
    result = {"code": "0003"}

    username = request.json.get("username")
    password = request.json.get("password")

    user = models.User.filter(username=username).first()

    if user and user.password != password:
        # 密码错误
        result["code"] = "0001"
        result["msg"] = "您可能忘记了密码"
        return jsonify(result)

    if not user:
        # 用户不存在,直接创建新用户,同时创建默认的project
        newUser = models.User.create(username=username, password=password)
        models.Project.create(name="default", user_id=newUser.id)

        logger.info("创建用户:{}成功,创建项目:{}成功".format(username, "default"))

    token = generate_auth_token(username)
    result["token"] = token

    return jsonify(result)
    
    
##########################################################################################

    
# 用于生成和验证token

from itsdangerous import TimedJSONWebSignatureSerializer as Serializer, SignatureExpired, BadSignature

SECRET_KEY = "SECRET_KEY"


def generate_auth_token(data,expiration=3600 * 24 * 30):
    """
    生成token
    :param data: 用于加密在token中的数据
    :param expiration: 过期时间
    :return: 
    """
    s = Serializer(SECRET_KEY, expires_in=expiration)
    return s.dumps(data).decode("utf-8")


def verify_auth_token(token):
    """
    验证token
    :param token: 包含数据的token
    :return: 返回token中包含的数据
    """
    s = Serializer(SECRET_KEY)
    try:
        data = s.loads(token)
    except SignatureExpired as e:
        return None  # valid token, but expired
    except BadSignature as e:
        return None  # invalid token

    return data
    

##########################################################################################
    
# 此模块是为了便于管理url

from flask import request, session, jsonify
from mainApp.api import views
from mainApp.settings import NOT_LOGIN_CODE
from mainApp.utils.its import verify_auth_token


class Router(object):
    app = None

    def __init__(self):
        pass

    def init(self, app):
        self.app = app
        self.add_url_rule()
        self.register_before_request()  # 注册路由拦截

    def path(self, rule, view_func, endpoint=None):
        self.app.add_url_rule(rule, endpoint, view_func, methods=["GET", "POST"])

    def is_login(self):
        if request.path == "/login" or request.path == "/logout":
            return None

        token = request.headers.get("token")

        verify_value = verify_auth_token(token)
        if verify_value:
            request.username = verify_value
        else:
            return jsonify({"code": NOT_LOGIN_CODE})

    def register_before_request(self):
        self.app.before_request(self.is_login)

    def add_url_rule(self):
        path = self.path
        path('/', views.home)
        path('/login', views.login)
        path('/<method>', views.run)
        path('/mock/<path:i>', views.mock)

 

posted @ 2019-06-12 16:46  jec1999  阅读(3394)  评论(0编辑  收藏  举报