• 基于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)
  • 相关阅读:
    C struct 中字节对齐问题(转)
    蚁群算法,PSO算法以及两种算法可以融合的几种方法
    遗传及蚁群算法
    ListBox FAQ常用问题
    关于C#中ListBox控件重绘Item项
    创业艰难,问题多多
    asp.net客户端脚本验证小技巧
    防止ASP.NET按钮多次提交的办法
    鼠标点到文本框时的提示信息
    枚举的转换
  • 原文地址:https://www.cnblogs.com/jec1999/p/11010630.html
Copyright © 2020-2023  润新知