在 Vue 2.5 實作「認證節流閥」功能

前言

當使用者發生過多的錯誤認證時,則禁止使用者繼續操作一段時間。

做法

mixinx 資料夾新增一個 throttle.js 混入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
import moment from 'moment';
import cookie from '@/helpers/cookie';

const throttle = {
data() {
return {
// 嘗試上限
limit: 3,
// 間隔時間
period: 10,
// 倒數計時
counter: 0,
// 每一次的嘗試時間戳
attempts: cookie.get('attempts') || [],
};
},
watch: {
attempts(value) {
// 當嘗試時間戳歸零時,關閉警告視窗
if (!value.length) {
this.setError(null);
}
// 如果使用者被禁用,則開始倒數
if (this.isSuspended) {
this.countdown();
}
},
},
created() {
// 從 cookie 獲取嘗試時間戳
const attempts = cookie.get('attempts');
this.setAttempts(attempts ? attempts.data : []);
},
computed: {
// 嘗試的次數
times() {
return this.attempts.length;
},
// 最初的嘗試時間戳
begin() {
return this.attempts[this.times - this.limit];
},
// 最後的嘗試時間戳
end() {
return this.attempts[this.times - 1];
},
// 剩餘秒數
remaining() {
return this.period - this.isExpended();
},
// 是否被禁用
isSuspended() {
// 嘗試次數小於嘗試上限則不做事
if (this.times < this.limit) {
return false;
}
// 判斷最初與最後的嘗試時間戳間距是否超過間格時間
return moment.duration(moment(this.end).diff(this.begin)).seconds() < this.period;
},
},
methods: {
setCounter(counter) {
this.counter = counter;
},
setAttempts(attempts) {
this.attempts = attempts;
},
// 計算最後的嘗試時間戳到當前所花費的時間
isExpended() {
return moment.duration(moment().diff(this.end)).seconds();
},
// 當使用者發生錯誤認證時,就存入當前的時間戳
suspend() {
const attempts = [...this.attempts, Date.now()];
cookie.set('attempts', attempts, moment().add(this.period, 's').toDate());
this.setAttempts(attempts);
},
// 倒數
countdown() {
this.setCounter(this.remaining);
const counter = setInterval(() => {
this.setCounter(this.counter - 1);
}, 1000 * 1);
setTimeout(() => {
this.setAttempts([]);
clearInterval(counter);
}, 1000 * this.remaining);
},
},
};

export default throttle;

Login.vue 元件使用節流閥,只需要在使用者發生錯誤認證時,使用 suspend() 方法即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import throttle from '@/mixins/throttle';

export default {
mixins: [
throttle,
],
methods: {
...mapMutations([
'setSettings',
]),
...mapActions('auth', [
'fetchToken',
]),
async login() {
await this.beforeProcess();
await this.fetchToken({
params: {
username: this.username,
password: this.password,
grant_type: 'password',
client_id: process.env.VUE_APP_API_CLIENT_ID,
client_secret: process.env.VUE_APP_API_CLIENT_SECRET,
},
})
.then(() => {
setTimeout(() => {
this.process();
}, 1000 * 0.25);
})
.catch((error) => {
// 存入時間戳
this.suspend();
this.setError(error);
this.setNoData(true);
this.setPassword('');
})
.finally(() => {
setTimeout(() => {
this.setLoading(false);
}, 1000 * 0.25);
});
},