be based on Vue and Quasar The front end of the SPA User login of the project ( Two )

review

Through the last article be based on Vue and Quasar The front end of the SPA The construction of the actual combat environment of the project ( One ) Introduction to , We have set up the local development environment and run it successfully , Today, we mainly introduce the login function .

brief introduction

Usually for the sake of safety , Users need to log in before they can access .crudapi admin web The project also needs to introduce the login function , After the user logs in successfully , Jump to the administration page , Otherwise, the prompt does not have permission .

Technical research

SESSION

SESSION It's usually used Cookie,Cookie Sometimes in the plural Cookies. The type is “ Small text files ”, It's some websites to identify users , Conduct Session Tracking data stored on the user's local terminal ( Usually encrypted ), Information temporarily or permanently saved by the user's client computer .
After the user logs in successfully , Background service records login status , And use SESSIONID Make unique identification . Browser pass Cookie Recorded SESSIONID after , The next time you visit any web page under the same domain name, you will automatically bring with you SESSIONID The information of Cookie, In this way, the background can judge whether the user has logged in , So we can take the next step . The advantage is easy to use , Browser automatic processing Cookie, The disadvantage is that it is vulnerable to XSS***.

JWT Token

Json web token (JWT), Is a kind of implementation based on the JSON Open standards for ((RFC 7519). The token Designed to be compact and safe , Especially for single sign in of distributed sites (SSO) scene .JWT The declaration of is generally used to pass the authenticated user identity information between the identity provider and the service provider , To get resources from the resource server , You can also add some additional declaration information that other business logic requires , The token It can also be used directly for authentication , It can also be encrypted .
JWT The verification method is more simple and convenient , No need to cache , It's directly based on token Take out the saved user information , And right token Availability check , Single sign on is easier . The disadvantage is that it's not very convenient to log off , And because JWT Token yes base64 encryption , There may be security risks .
Because the current system is mainly used in the browser environment , So I chose SESSION Login method of , Consider using later JWT Login mode ,JWT More suitable for APP And small program scenarios .

The login process

 Login flowchart
The main process is as follows :

  1. When the user opens the page , First, judge whether it belongs to the white list , If it belongs to , such as /login, /403, Direct release .
  2. Local local Storage If the login information is saved , I've logged in before , Direct release .
  3. If you haven't logged in , Local local Storage It's empty , Go to the login page .
  4. Although I have logged in locally , But it may be out of date , Visit any one at this time API When , It will automatically determine whether to log in according to the returned results .

UI Interface

 The login page
The login page is simple , It mainly includes user name 、 Password input box and login button , Click the login button to call login API.

The code structure

 The code structure

  1. api: adopt axios And backstage api Interaction
  2. assets: Mainly some pictures and so on
  3. boot: Dynamic load Library , such as axios、i18n etc.
  4. components: Custom components
  5. css:css style
  6. i18n: Multilingual information
  7. layouts: Layout
  8. pages: page , It includes html,css and js Three parts
  9. router: Routing related
  10. service: Business service, Yes api encapsulate
  11. store:Vuex State management ,Vuex Is to implement the global state of the component ( data ) A mechanism of Management , Data sharing between components can be easily realized

The configuration file

quasar.conf.js It's a global profile , All configuration related content can be set in this file .

Core code

To configure quasar.conf.js

plugins: ['LocalStorage','Notify','Loading']


Because you need local storage LocalStorage, Message tip Notify And wait for the prompt Loading plug-in unit , So in plugins Add inside .

Configure global styles

Modify file quasar.variables.styl and app.styl, For example, set the main color to light blue

$primary = #35C8E8


encapsulation axios

import Vue from 'vue'import axios from 'axios'import { Notify } from "quasar";import qs from "qs";import Router from "../router/index";import { permissionService } from "../service";Vue.prototype.$axios = axios// We create our own axios instance and set a custom base URL.// Note that if we wouldn't set any config here we do not need// a named export, as we could just `import axios from 'axios'`const axiosInstance = axios.create({
  baseURL: process.env.API});axiosInstance.defaults.transformRequest = [
  function(data, headers) {// Do whatever you want to transform the datalet contentType = headers["Content-Type"] || headers["content-type"];if (!contentType) {  contentType = "application/json";  headers["Content-Type"] = "application/json";}if (contentType.indexOf("multipart/form-data") >= 0) {  return data;} else if (contentType.indexOf("application/x-www-form-urlencoded") >= 0) {  return qs.stringify(data);}return JSON.stringify(data);
  }];// Add a request interceptoraxiosInstance.interceptors.request.use(
  function(config) {if (config.permission && !permissionService.check(config.permission)) {  throw {message: "403 forbidden"  };}return config;
  },
  function(error) {// Do something with request errorreturn Promise.reject(error);
  });function login() {
  setTimeout(() => {Router.push({  path: "/login"});
  }, 1000);}// Add a response interceptoraxiosInstance.interceptors.response.use(
  function(response) {// Any status code that lie within the range of 2xx cause this function to trigger// Do something with response datareturn response;
  },
  function(error) {// Any status codes that falls outside the range of 2xx cause this function to trigger// Do something with response errorif (error.response) {  if (error.response.status === 401) {Notify.create({  message:  error.response.data.message,  type: 'negative'});login();  } else if (error.response.data && error.response.data.message) {Notify.create({  message: error.response.data.message,  type: 'negative'});  } else {Notify.create({  message: error.response.statusText || error.response.status,  type: 'negative'});  }} else if (error.message.indexOf("timeout") > -1) {  Notify.create({message: "Network timeout",type: 'negative'  });} else if (error.message) {  Notify.create({message: error.message,type: 'negative'  });} else {  Notify.create({message: "http request error",type: 'negative'  });}return Promise.reject(error);
  });// for use inside Vue files through this.$axiosVue.prototype.$axios = axiosInstance// Here we define a named export// that we can later use inside .js files:export { axiosInstance }


axios Configure an instance , Do some unified treatment , For example, network request data preprocessing , Verify permissions ,401 Jump ,403 Tips, etc .

user api and service

import { axiosInstance } from "boot/axios";const HEADERS = {
  "Content-Type": "application/x-www-form-urlencoded"};const user = {
  login: function(data) {return axiosInstance.post("/api/auth/login",  data,  {headers: HEADERS  });
  },
  logout: function() {return axiosInstance.get("/api/auth/logout",  {headers: HEADERS  });
  }};export { user };


Sign in api by /api/auth/login, Cancellation api by /api/auth/logout

import { user} from "../api";import { LocalStorage } from "quasar";const userService = {
  login: async function(data) {var res = await user.login(data);return res.data;
  },
  logout: async function() {var res = await user.logout();return res.data;
  },
  getUserInfo: async function() {return LocalStorage.getItem("userInfo") || {};
  },
  setUserInfo: function(userInfo) {LocalStorage.set("userInfo", userInfo);
  }};export { userService };


user service It's mainly about api Encapsulation , And then save the information to the user LocalStorage Interface

Vuex Manage login status

import { userService } from "../../service";import { permissionService } from "../../service";export const login = ({ commit }, userInfo) => {
  return new Promise((resolve, reject) => {userService      .login(userInfo)  .then(data => {  //session How to log in , It doesn't need to be token, Here for JWT Login reservation , use username Instead of .  // adopt Token If it is empty, judge whether you have logged in locally , Convenient for subsequent processing .  commit("updateToken", data.principal.username);  const newUserInfo = {username: data.principal.username,realname: data.principal.realname,avatar: "",authorities: data.principal.authorities || [],roles: data.principal.roles || []  };  commit("updateUserInfo", newUserInfo);  let permissions = data.authorities || [];  let isSuperAdmin = false;  if (permissions.findIndex(t => t.authority === "ROLE_SUPER_ADMIN") >= 0) {isSuperAdmin = true;  }  permissionService.set({permissions: permissions,isSuperAdmin: isSuperAdmin          });  resolve(newUserInfo);  })  .catch(error => {reject(error);  });
  });};export const logout = ({ commit }) => {
  return new Promise((resolve, reject) => {userService      .logout()  .then(() => {resolve();  })  .catch(error => {reject(error);  })  .finally(() => {commit("updateToken", "");commit("updateUserInfo", {  username: "",  realname: "",  avatar: "",  authorities: [],  roles: []});permissionService.set({  permissions: [],  isSuperAdmin: false});  });
  });};export const getUserInfo = ({ commit }) => {
  return new Promise((resolve, reject) => {userService      .getUserInfo()  .then(data => {commit("updateUserInfo", data);resolve();  })  .catch(error => {reject(error);  });
  });};


After successful login , Will make use of Vuex Save the user and permission information in the global state , then LocalStorage And keep a copy of , So when you refresh the page, it will start from LocalStorage Read Vuex in .

Route hop Management

import Vue from 'vue'import VueRouter from 'vue-router'import routes from './routes'import { authService } from "../service";import store from "../store";Vue.use(VueRouter)/*
 * If not building with *** mode, you can
 * directly export the Router instantiation;
 *
 * The function below can be async too; either use
 * async/await or return a Promise which resolves
 * with the Router instance.
 */const Router = new VueRouter({
  scrollBehavior: () => ({ x: 0, y: 0 }),
  routes,
  // Leave these as they are and change in quasar.conf.js instead!
  // quasar.conf.js -> build -> vueRouterMode
  // quasar.conf.js -> build -> publicPath
  mode: process.env.VUE_ROUTER_MODE,
  base: process.env.VUE_ROUTER_BASE});const whiteList = ["/login", "/403"];function hasPermission(router) {
  if (whiteList.indexOf(router.path) !== -1) {return true;
  }
  return true;}Router.beforeEach(async (to, from, next) => {
  let token = authService.getToken();
  if (token) {let userInfo = store.state.user.userInfo;if (!userInfo.username) {  try {await store.dispatch("user/getUserInfo");next();  } catch (e) {if (whiteList.indexOf(to.path) !== -1) {  next();} else {  next("/login");}  }} else {  if (hasPermission(to)) {next();  } else {next({ path: "/403", replace: true });  }}
  } else {if (whiteList.indexOf(to.path) !== -1) {  next();} else {  next("/login");}
  }});export default Router;


By copying Router.beforeEach Method , Preprocessing before page Jump , Realize the function in the previous login flow chart .

The login page

submit() {
  if (!this.username) {this.$q.notify(" The username cannot be empty !");return;
  }
  if (!this.password) {this.$q.notify(" The password cannot be empty !");return;
  }
  this.$q.loading.show({message: " Logon "
  });
  this.$store.dispatch("user/login", {  username: this.username,  password: this.password,}).then(async (data) => {  this.$router.push("/");  this.$q.loading.hide();}).catch(e => {  this.$q.loading.hide();  console.error(e);});}


submit Method execution this.                        s                    t                    o                    r                    e                    .                    d                    i                    s                    p                    a                    t                    c                    h                    (                    "                    u                    s                    e                    r                    /                    l                    o                    g                    i                    n                    "                    )                     Into the                     That's ok                     deng                     record                    ,                     surface                     in                     transfer                     use                    u                    s                    e                    r                    s                    t                    o                    r                    e                    a                    c                    t                    i                    o                    n                     in                     Noodles                     Of                    l                    o                    g                    i                    n                     Fang                     Law                    ,                     Such as                     fruit                     become                     work                    ,                     Of board                     That's ok                    t                    h                    i                    s                    .                         store.dispatch("user/login") Log in , Represents a call to user store action Inside login Method , If it works , perform this.           store.dispatch("user/login") Log in , Represents a call to userstoreaction Inside login Method , If it works , perform this.router.push("/").

To configure devServer agent

devServer: {
  https: false,
  port: 8080,
  open: true, // opens browser window automatically
  proxy: {"/api/*": {  target: "xx.xx.xx.xx",  changeOrigin: true}
  }}


To configure proxy after , be-all api The first request will be forwarded to the background server , In this way, the problem of cross domain access can be solved .

verification

 Login failed
First , Deliberately enter a wrong user name , Prompt login failed .

 Login successful
Enter the correct user name and password , Login successful , Automatically jump to the background management page .

localstorage
F12 Turn on chrome browser debug Pattern , see localstorage, Find out userInfo,permission,token The content is in line with expectations , Among them, authority permission The relevant content is in the following rbac The chapter details .

Summary

This paper mainly introduces the user login function , Yes axios Network request ,Vuex State management ,Router route ,localStorage Local storage, etc Vue Basic knowledge , And then we used Quasar Three plug-ins for ,LocalStorage, Notify and Loading. Although the login function is relatively simple , But it completely implements the interaction process from the front end to the back end .
Complete source code can be run in the Vue and Quasar The front end of the SPA The construction of the actual combat environment of the project ( One ) View in !