로그인 기능에 유효성 검사를 해보자.
로그인 유효성 검사
express-validator 모듈을 활용해 POST 요청으로 로그인 할 때 데이터가 서버나 데이터베이스 보내지기 전에 조건에 부합하는지 확인, 검증하는 작업을 구현해보겠습니다.
조건 설정
id
- 공백 확인
- 4글자 이상 12글자 미만
- 숫자와 영어 대소문자로 구성
password
- 공백 확인
- 8글자 이상 15글자 미만
프론트엔드 측 유효성 검사
먼저 프론트엔드 측에서 1차적으로 검사를 진행합니다.
login.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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
"use strict";
const id = document.querySelector("#id"),
password = document.querySelector("#password"),
loginBtn = document.querySelector("#button");
if (loginBtn) loginBtn.addEventListener("click", login);
async function login() {
// 경고 메세지 생성 함수
const createMsg = (message) => {
var p = document.createElement("p");
var text = document.createTextNode(message);
p.append(text);
p.className = "validationMsg";
return p;
};
// 아이디 검사 함수
const validationId = () => {
var msg = "";
if (!id.value) {
// 아이디 공백 확인
msg = "아이디를 입력해주세요.";
} else if (id.value.length < 4 || id.value.length > 12) {
// 아이디가 공백이 아니라면 글자 수 확인
msg = "아이디는 4글자 이상 12글자 미만입니다.";
} else {
// 아이디 구성 확인
let checkRegExp = /^[A-Za-z0-9]+$/;
if (checkRegExp.test(id.value) == false) {
msg = "아이디는 영어 대소문자, 숫자로 구성됩니다.";
}
}
// 사용자가 입력한 아이디가 조건에 맞지 않을 떄
if (msg) {
if (id.nextElementSibling.className !== "validationMsg") {
// 먼저 출력된 경고 메세지가 없을 때
const p = createMsg(msg);
id.after(p);
} else {
// 먼저 출력된 경고 메세지가 있을 때
id.nextElementSibling.innerHTML = msg;
}
return 0;
} else {
// 사용자가 입력한 아이디가 조건에 맞을 떄
const validationMsg = id.nextElementSibling;
// 이전에 출력된 경고 메세지가 있을 때
if (validationMsg.className == "validationMsg") validationMsg.remove();
return 1;
}
};
// 비밀번호 검사 함수
const validationPassword = () => {
var msg = "";
if (!password.value) {
msg = "비밀번호를 입력해주세요.";
} else {
if (password.value.length < 8 || password.value.length > 15) {
msg = "비밀번호는 8글자 이상 15글자 미만입니다.";
}
}
if (msg) {
if (password.nextElementSibling.className !== "validationMsg") {
const p = createMsg(msg);
password.after(p);
} else {
password.nextElementSibling.innerHTML = msg;
}
return 0;
} else {
const validationMsg = password.nextElementSibling;
if (validationMsg.className == "validationMsg") validationMsg.remove();
return 1;
}
};
// 반환 값을 유효성 검사 통과시 1로 설정해 * 연산으로 서버에 요청
// 아래의 백엔드 측 유효성 검사 테스트 시 조건을 1로 설정
if (validationId() * validationPassword()) {
const req = {
id: id.value,
password: password.value,
};
try {
const res = await axios({
url: "/user/login",
method: "post",
data: req,
});
...
} catch (err) {
// 에러 처리
console.log(err);
}
}
}
백엔드 측 유효성 검사
프론트엔드 측만 검사할 경우 쉽게 뚫릴 수 있습니다.
쉬운 예로 브라우저의 개발자 도구에서 간단한 수정만으로 검사과정을 삭제 시킬 수 있습니다.
따라서, express-validator 모듈을 이용해 백엔드 측에서도 검사를 진행해보겠습니다.
api
폴더에 validation
폴더를 만들고 validator_user.js
파일을 만들어 route_user.js
파일에서 import해 사용해보겠습니다.
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
// validator_user.js
import { body, validationResult } from "express-validator";
const validator = async (req, res, next) => {
let errors = await validationResult(req);
// validationResult 메서드를 통해 결과를 받습니다.
if (!errors.isEmpty()) {
const req = {};
const tmp = errors.array();
for (var element of tmp) {
req[element.path] = element.msg;
}
return res.json({ errors: req });
}
next();
};
const validatorUser = {
login: [
body("id") // req.body 로 들어온 데이터를 선택합니다.
.notEmpty() // id값이 공백이 아닌지
.withMessage("아이디를 입력하세요.") // id 값이 공백이라면 메세지 생성
.bail() // 메세지 생성 시 종료
.trim() // 양쪽 공백 제거
.isLength({ min: 4, max: 12 }) // 길이 확인 메서드
.withMessage("아이디는 4글자 이상 12글자 미만입니다.")
.bail()
.isAlphanumeric() // 영어 대소문자, 숫자 구조 확인 메서드
.withMessage("아이디는 숫자와 영어 대소문자로만 구성되어야 합니다."),
body("password")
.notEmpty()
.withMessage("비밀번호를 입력하세요.")
.bail()
.isLength({ min: 8, max: 15 })
.withMessage("비밀번호는 8글자 이상 15글자 미만입니다.")
.bail(),
validator,
],
};
export default validatorUser;
routes
폴더의 route_user.js
파일을 수정해줍니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import express from "express";
import ctrl from "../controllers/controller_user.js";
import validator_user from "../middleware/validator_user.js";
const Router = express.Router();
Router.get("/info", jwt_auth, ctrl.output.info);
Router.get("/login", ctrl.output.login);
Router.get("/register", ctrl.output.register);
Router.get("/logout", ctrl.process.logout);
Router.post("/login", validator_user.login, ctrl.process.login);
Router.post("/register", ctrl.process.register);
export default Router;
login.js
파일에서 axios 요청 후 응답 데이터 작업을 추가합니다.
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
"use strict";
const id = document.querySelector("#id"),
password = document.querySelector("#password"),
loginBtn = document.querySelector("#button");
if (loginBtn) loginBtn.addEventListener("click", login);
async function login() {
...
if (validateId() * validatePassword()) {
const req = {
id: id.value,
password: password.value,
};
try {
const res = await axios({
url: "/user/login",
method: "post",
data: req,
});
const data = res.data;
if (data.errors) {
if (data.errors.id) {
// 사용자가 입력한 아이디가 조건에 맞지 않을 떄
if (id.nextElementSibling.className !== "validationMsg") {
// 먼저 출력된 경고 메세지가 없을 때
const p = createMsg(data.errors.id);
id.after(p);
} else {
// 먼저 출력된 경고 메세지가 있을 때
id.nextElementSibling.innerHTML = data.errors.id;
}
}
if (data.errors.password) {
// 사용자가 입력한 아이디가 조건에 맞지 않을 떄
if (password.nextElementSibling.className !== "validationMsg") {
// 먼저 출력된 경고 메세지가 없을 때
const p = createMsg(data.errors.password);
password.after(p);
} else {
// 먼저 출력된 경고 메세지가 있을 때
password.nextElementSibling.innerHTML = data.errors.password;
}
}
}
if (data.msg) {
alert(data.msg);
}
if (data.success) {
location.href = "/user/login";
}
} catch (err) {
// 에러 처리
console.log(err);
}
}
}
회원가입 유효성 검사
로그인 할 때와 마찬가지로 express-validator
모듈을 활용해 POST 요청
으로 회원가입할 때 데이터가 서버나 데이터베이스 보내지기 전에 조건에 부합하는지 확인, 검증하는 작업을 구현해보겠습니다.
조건 설정
id
- 공백 확인
- 4글자 이상 12글자 미만
- 숫자와 영어 대소문자로 구성
password
- 공백 확인
- 8글자 이상 15글자 미만
nickname
- 공백 확인
- 3글자 이상 10글자 미만
- 한글, 숫자, 영어 대소문자로 구성
프론트엔드 측 유효성 검사
먼저 프론트엔드 측에서 1차적으로 검사를 진행합니다.
register.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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
"use strict";
const id = document.querySelector("#id"),
nickName = document.querySelector("#nickName"),
password = document.querySelector("#password"),
confirmPassword = document.querySelector("#confirmPassword"),
registerBtn = document.querySelector("#button");
registerBtn.addEventListener("click", register);
async function register() {
// 경고 메세지 생성 함수
const createMsg = (message) => {
var p = document.createElement("p");
var text = document.createTextNode(message);
p.append(text);
p.className = "validationMsg";
return p;
};
// 아이디 검사 함수
const validateId = () => {
var msg = "";
if (!id.value) {
// 아이디 공백 확인
msg = "아이디를 입력해주세요.";
} else if (id.value.length < 4 || id.value.length > 12) {
// 아이디가 공백이 아니라면 글자 수 확인
msg = "아이디는 4글자 이상 12글자 미만입니다.";
} else {
// 아이디 구성 확인
let checkRegExp = /^[A-Za-z0-9]+$/;
if (checkRegExp.test(id.value) == false) {
msg = "아이디는 영어 대소문자, 숫자로 구성됩니다.";
}
}
if (msg) {
// 사용자가 입력한 아이디가 조건에 맞지 않을 떄
if (id.nextElementSibling.className !== "validationMsg") {
// 먼저 출력된 경고 메세지가 없을 때
const p = createMsg(msg);
id.after(p);
} else {
// 먼저 출력된 경고 메세지가 있을 때
id.nextElementSibling.innerHTML = msg;
}
return 0;
} else {
// 사용자가 입력한 아이디가 조건에 맞을 떄
let validationMsg = id.nextElementSibling;
// 이전에 출력된 경고 메세지가 있을 때
if (validationMsg.className == "validationMsg") validationMsg.remove();
return 1;
}
};
// 비밀번호 검사 함수
const validatePassword = () => {
var msg = "";
if (!password.value) {
msg = "비밀번호를 입력해주세요.";
} else if (password.value.length < 8 || password.value.length > 15) {
msg = "비밀번호는 8글자 이상 15글자 미만입니다.";
}
if (msg) {
if (password.nextElementSibling.className !== "validationMsg") {
const p = createMsg(msg);
password.after(p);
} else {
password.nextElementSibling.innerHTML = msg;
}
return 0;
} else {
const validationMsg = password.nextElementSibling;
if (validationMsg.className == "validationMsg") validationMsg.remove();
return 1;
}
};
// 비밀번호 확인 검사 함수
const validateConfirmPassword = () => {
var msg = "";
if (validatePassword() && password.value !== confirmPassword.value) {
msg = "비밀번호가 다릅니다.";
}
if (msg) {
if (confirmPassword.nextElementSibling.className !== "validationMsg") {
const p = createMsg(msg);
confirmPassword.after(p);
}
return 0;
} else {
const validationMsg = confirmPassword.nextElementSibling;
if (validationMsg.className == "validationMsg") validationMsg.remove();
return 1;
}
};
// 닉네임 검사 함수
const validateNickName = () => {
var msg = "";
// 공백 확인
if (!nickName.value) {
msg = "닉네임을 입력해주세요.";
} else if (nickName.value.length < 3 || nickName.value.length > 10) {
msg = "닉네임는 3글자 이상 10글자 미만입니다.";
} else {
// 구성 확인
let checkRegExp = /^[가-힣A-Za-z0-9]+$/;
if (checkRegExp.test(nickName.value) == false) {
msg = "닉네임은 한글, 숫자, 영어 대소문자로 구성됩니다.";
}
}
if (msg) {
if (nickName.nextElementSibling.className !== "validationMsg") {
const p = createMsg(msg);
nickName.after(p);
} else {
nickName.nextElementSibling.innerHTML = msg;
}
return false;
} else {
const validationMsg = nickName.nextElementSibling;
if (validationMsg.className == "validationMsg") validationMsg.remove();
return true;
}
};
// 반환 값을 유효성 검사 통과시 1로 설정해 * 연산으로 서버에 요청
// 아래의 백엔드 측 유효성 검사 테스트 시 조건을 1로 설정
if (
validateId() *
validatePassword() *
validateConfirmPassword() *
validateNickName()
) {
const req = {
id: id.value,
name: nickName.value,
password: password.value,
confirmPassword: confirmPassword.value,
};
try {
const res = await axios({
url: "/user/register",
method: "post",
data: req,
});
...
} catch (err) {
// 에러 처리
console.log(err);
}
}
}
백엔드 측 유효성 검사
express-validator 모듈을 이용해 백엔드 측에서도 검사를 진행합니다.
validator_user.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
// validator_user.js
import { body, validationResult } from "express-validator";
const validator = async (req, res, next) => {
var errors = await validationResult(req);
// validationResult 메서드를 통해 결과를 객체로 받습니다.
if (!errors.isEmpty()) {
const req = {};
const tmp = errors.array();
for (var element of tmp) {
req[element.param] = element.msg;
}
return res.json({ errors: req });
}
next();
};
const validatorUser = {
login: [
body("id") // req.body 로 들어온 데이터를 선택합니다.
.trim() // 양쪽 공백 제거
.notEmpty() // id값이 공백이 아닌지
.withMessage("아이디를 입력") // id 값이 공백이라면 메세지 생성
.bail() // 메세지 생성 시 종료
.isLength({ min: 4, max: 12 }) // 길이 확인 메서드
.withMessage("아이디는 4글자 이상 12글자 미만")
.bail()
.isAlphanumeric() // 영어 대소문자, 숫자 구조 확인 메서드
.withMessage("아이디는 숫자와 영어 대소문자로 구성"),
body("password")
.notEmpty()
.withMessage("비밀번호를 입력")
.bail()
.isLength({ min: 8, max: 15 })
.withMessage("비밀번호는 8글자 이상 15글자 미만")
.bail(),
validator,
],
register: [
body("id")
.trim()
.notEmpty()
.withMessage("아이디를 입력")
.bail()
.isLength({ min: 4, max: 12 })
.withMessage("아이디는 4글자 이상 12글자 미만")
.bail()
.isAlphanumeric()
.withMessage("아이디는 숫자, 영어 대소문자로 구성"),
body("password")
.notEmpty()
.withMessage("비밀번호를 입력")
.bail()
.isLength({ min: 8, max: 15 })
.withMessage("비밀번호는 8글자 이상 15글자 미만"),
body("confirmPassword")
.notEmpty()
.withMessage("비밀번호를 한번 더 입력")
.bail()
.custom((value, { req }) => {
return value === req.body.password;
})
.withMessage("비밀번호가 같지 않습니다."),
body("nickName")
.notEmpty()
.withMessage("닉네임을 입력")
.bail()
.isLength({ min: 3, max: 10 })
.withMessage("닉네임은 3글자 이상 10글자 미만")
.bail()
.custom((value) => {
let checkRegExp = /^[가-힣A-Za-z0-9]+$/;
return checkRegExp.test(value);
})
.withMessage("아이디는 한글, 숫자, 영어 대소문자로 구성"),
validator,
],
};
export default validatorUser;
routes
폴더의 route_user.js
파일을 수정해줍니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import express from "express";
import ctrl from "../controllers/controller_user.js";
import validator_user from "../middleware/validator_user.js";
const Router = express.Router();
Router.get("/info", jwt_auth, ctrl.output.info);
Router.get("/login", ctrl.output.login);
Router.get("/register", ctrl.output.register);
Router.get("/logout", ctrl.process.logout);
Router.post("/login", validator_user.login, ctrl.process.login);
Router.post("/register", validator_user.register, ctrl.process.register);
export default Router;
register.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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
"use strict";
const id = document.querySelector("#id"),
nickName = document.querySelector("#nickName"),
password = document.querySelector("#password"),
confirmPassword = document.querySelector("#confirmPassword"),
registerBtn = document.querySelector("#button");
registerBtn.addEventListener("click", register);
async function register() {
...
if (validateId() * validatePassword() * validateConfirmPassword() * validateNickName()) {
const req = {
id: id.value,
name: nickName.value,
password: password.value,
confirmPassword: confirmPassword.value,
};
try {
const res = await axios({
url: "/user/register",
method: "post",
data: req,
});
const data = res.data;
if (data.errors) {
if (data.errors.id) {
// 사용자가 입력한 아이디가 조건에 맞지 않을 떄
if (id.nextElementSibling.className !== "validationMsg") {
// 먼저 출력된 경고 메세지가 없을 때
const p = createMsg(data.errors.id);
id.after(p);
} else {
// 먼저 출력된 경고 메세지가 있을 때
id.nextElementSibling.innerHTML = data.errors.id;
}
} else {
const validationMsg = id.nextElementSibling;
if (validationMsg.className == "validationMsg") validationMsg.remove();
}
if (data.errors.password) {
// 사용자가 입력한 아이디가 조건에 맞지 않을 떄
if (password.nextElementSibling.className !== "validationMsg") {
// 먼저 출력된 경고 메세지가 없을 때
const p = createMsg(data.errors.password);
password.after(p);
} else {
// 먼저 출력된 경고 메세지가 있을 때
password.nextElementSibling.innerHTML = data.errors.password;
}
} else {
const validationMsg = password.nextElementSibling;
if (validationMsg.className == "validationMsg") validationMsg.remove();
if (data.errors.confirmPassword) {
if (confirmPassword.nextElementSibling.className !== "validationMsg") {
// 먼저 출력된 경고 메세지가 없을 때
const p = createMsg(data.errors.confirmPassword);
confirmPassword.after(p);
} else {
// 먼저 출력된 경고 메세지가 있을 때
confirmPassword.nextElementSibling.innerHTML = data.errors.confirmPassword;
}
} else {
const validationMsg = confirmPassword.nextElementSibling;
if (validationMsg.className == "validationMsg") validationMsg.remove();
}
}
if (data.errors.nickName) {
// 사용자가 입력한 아이디가 조건에 맞지 않을 떄
if (nickName.nextElementSibling.className !== "validationMsg") {
// 먼저 출력된 경고 메세지가 없을 때
const p = createMsg(data.errors.nickName);
nickName.after(p);
} else {
// 먼저 출력된 경고 메세지가 있을 때
nickName.nextElementSibling.innerHTML = data.errors.nickName;
}
} else {
const validationMsg = nickName.nextElementSibling;
if (validationMsg.className == "validationMsg") validationMsg.remove();
}
}
if (data.msg) {
alert(data.msg);
}
if (data.success) {
location.href = "/user/login";
}
} catch (err) {
// 에러 처리
console.log(err);
}
}
}