Problem description
For security reasons, the product says, users go back to the login page and log in again after a long period of inaction, just like the bank’s app. This article records the realization of this effect of two ways, respectively front-end control and back-end control, each has details and applicable scenarios
Front end control (Mode 1)
Train of thought
First of all, what are the specific manifestations of prolonged user inactivity? It is simply whether the event has not been triggered for a long time.
For example, if the user does not operate for a long time, there will be no mouse click event, mousewheel event, mouse movement event and so on. We only need to monitor these events. If these events are not triggered for a long time, it means that the user has not operated for a long time, and then the route jumps to the login page.
As we know, generally speaking, the first page of the project is the login page, so when the user clicks the login button on the login page, the time of clicking the login button will be recorded and stored in sessionStorage. When jumping to the main page, When the user every click a page, update the storage time in sessionStorage, but also to the page bound a cycle timer, interval a period of time to the current time and sessionStorage last click event time to do a difference comparison, when the difference exceeds a certain time, Force the user to exit to the login page.
code
The login. The vue web page
// html
<el-button type="primary" @click="loginIn"</el-button>// js
methods: {
loginIn() {
// Save the first click time
sessionStorage.setItem("lastClickTime".new Date().getTime());
// The simulated backend returns to store a token
sessionStorage.setItem('token'."token")
this.$router.push({
path: "/"}); }},Copy the code
Home. Vue web page
<template>
<div class="homeBox">
<! To the left is the menu hierarchy -->
<div class="left">
<div class="leftNav">
<el-menu
:default-active="activeIndex"
class="elMenu"
background-color="# 333"
text-color="#B0B0B2"
active-text-color="#fff"
:unique-opened="true"
router
ref="elMenu"
>
<el-menu-item index="/vue">
<i class="el-icon-location-outline"></i>
<span slot="title">Vue page</span>
</el-menu-item>
<el-menu-item index="/react">
<i class="el-icon-star-off"></i>
<span slot="title">The react page</span>
</el-menu-item>
<el-menu-item index="/angular">
<i class="el-icon-pear"></i>
<span slot="title">Presents the page</span>
</el-menu-item>
</el-menu>
</div>
</div>
<! -- View hierarchy on the right -->
<div class="right">
<div class="rightTop">
<el-button type="primary" plain @click="loginOut">logout</el-button>
</div>
<div class="rightBottom">
<router-view></router-view>
</div>
</div>
</div>
</template>
<script>
export default {
name: "Home".data() {
return {
activeIndex: this.$route.path,
timer: null}; },created() {
/* The first step is to bind to the click event when the component is initially loaded. Note that the third parameter of addEventListener is added here. Because the third parameter is determines the bubbling or capture (false bubbling default, true capture), because the binding the click event, we are at the top of the DOM position to capture the click event, so the third parameter to true, to add, in this case, the inner any place of the click event We can listen to, Then store the click time */
window.addEventListener(
"click".() = > {
// For convenience, we store the click event time directly to sessionStorage for easy comparison
sessionStorage.setItem("lastClickTime".new Date().getTime());
},
true
);
},
mounted() {
/* Step 2: bind a timer when the component is initially loaded, and compare the difference between the current time and the last time clicked */ through the timer polling
this.isTimeOut();
},
methods: {
isTimeOut() {
// Clear the timer before using it
clearInterval(this.timer);
this.timer = setInterval(() = > {
let lastClickTime = sessionStorage.getItem("lastClickTime") * 1; // Convert the string time from the last click to a numeric time
let nowTime = new Date().getTime(); // Get the current time
console.log("Current time and previous click time", nowTime, lastClickTime);
// Let's say we need a prompt to log in and log out without clicking for 5 seconds
if (nowTime - lastClickTime > 1000 * 5) {
// Prompt the user
this.$message({ type: "warning".message: "Timed out, logged out." });
// Clear the timer and end the task
clearInterval(this.timer);
// Finally return to the login page
this.$router.push({ path: "/login"}); }},1000); }},beforeDestroy() {
// Finally, when leaving the page, clear the timer and unbind the click event
clearInterval(this.timer);
window.removeEventListener("click".() = > {}, true); }};</script>
Copy the code
Here, notice the hierarchical relationship. The hierarchical relationship built by my project is that the home. vue page is the inner layer of the app. vue page, and there are corresponding views, which are also the relationship of the whole page. Select an appropriate level to bind click events and timers according to the relationship between the level and the router-view of the routing table.
The hierarchy must be selected at the next level parallel to the login.vue hierarchy, otherwise timers and click-bind events will also be executed on the login.vue page
rendering
Back-end control (Mode 2)
Train of thought
This back-end control is not as restrictive as the front-end control, but it can be used.
We know that users will not send requests if they do not operate for a long time. In this way, we have agreed with the backend as follows:
When the interval between the current request and the last request exceeds a certain time, such as more than half an hour. So the back-end return a status code, it is not 200, is a special status code, such as the status code is 4567, so we are on the front end of response in the interceptor can add a judgment, if a status code is 4567 means the request timeout, indicating the user for a long time not operation, this time routing jump directly to the login page
The back end controls the returned status code through the JWT mechanism
code
Here we mount the instance object of Vue in main.js to the global object Window so that we can use the route jump method on the VM object in the response interceptor
The main js file
// Mount to the window object
window.vm = new Vue({
store,
router,
render: h= > h(App),
}).$mount('#app')
Copy the code
Response interceptor file
http.interceptors.response.use((res) = > {
console.log('Register globally',vm);
var code = res.data.code;
if(code == 4567) {// 4567 is the timeout status code
$router. Push (); // This.$router. Push ()
vm._router.push({ path: "/login" });
}
return res.data
}, (error) = > {
// console.log(error)
return Promise.reject(error);
})
Copy the code
Print the VM instance object
So the redirect in the response interceptor becomes vm._router.push({path: “/login”})
conclusion
Both approaches can be used, depending on the situation