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