基於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)

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM