Java에서 비밀번호 검증하기
Overview
웹 서비스를 개발할 때 회원 기능을 구현한다면 비밀번호 검증 기능은 필수로 넣어야한다.
웹 브라우저에서 javascript로 검증을 한 후 서버에 request할 수 있겠지만 변조의 위험으로 인해 서버 사이드(여기서는 Spring Framework)에서 비밀번호를 검증하는 것이 안전하다.
Environment
Java Version: zulu openjdk “1.8.0_252”
기준 일자 : 2020-09-18
필요한 기능
안전한 비밀번호는 다음 조건을 충족해야한다.
- 영문 / 숫자 / 특수문자 혼용하여 8자리 이상
- 공란(Blank) 비밀번호 사용 금지
- 단순 비밀번호 사용 금지
- 3자리 이상 같은 문자(111, aaa)
- 3자리 이상 연속되는 문자(abc, cba, 123, 321)
이 외에도 사용자 정보의 보안을 위해 비밀번호는 다음 기준을 만족해야한다.
- 변경주기: 3개월에 1회
- 비밀번호 변경 시, 이전 비밀번호 재사용 금지
- 자동저장 금지
- SHA256이상 단방향 암호화
- 20자 이상 SALT 적용
- SSL 기반 전송 암호화
- 비밀번호 분실 시 사용자에 의한 Reset 및 임시비밀번호 발급
- 검증 로직을 서버사이드에 구현
구현
이 포스팅에서는 비밀번호 문자열의 검증을 구현한다.
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RegexTest {
public static void main(String[] args) {
// Test Set
String[] testSet = {
"votmdnj&em123"
, "kjs@aldkjfklj43"
, "QBWfklj4543"
, "abct438983"
, "acdf@sabcer9182"
, "alfl234kdd"
, "asd@fasdf987"
// Blank 테스트 문자열
, "xp@tmxm85 84"
// 공백 테스트 문자열
, ""
// 문자 길이 테스트 문자열
, "OJHDSJK@HFzDLKDJLJoiejwf42^%wij"
, "xyz47@"
, "1lkjvneim@"
// ASCII Overflow 테스트 문자열
, "/01alkjdffn"
, "9:;aslkdjfkja2"
, "?@alakjlkiie3"
, "Z[\\ekjmvkfd4"
, "@abieofinv2"
, "89:8973589723dfasb"
, "YZ[qoeirnvk235"
};
for (String s : testSet) {
System.out.println("Password: \"" + s + "\"");
System.out.println(isValidPassword(s));
System.out.println("--------------------------------");
}
}
/**
* 비밀번호 검증 메소드
*
* @param password 비밀번호 문자열
* @return 오류 메시지
*/
public static String isValidPassword(String password) {
// 최소 8자, 최대 20자 상수 선언
final int MIN = 8;
final int MAX = 20;
// 영어, 숫자, 특수문자 포함한 MIN to MAX 글자 정규식
final String REGEX =
"^((?=.*\\d)(?=.*[a-zA-Z])(?=.*[\\W]).{" + MIN + "," + MAX + "})$";
// 3자리 연속 문자 정규식
final String SAMEPT = "(\\w)\\1\\1";
// 공백 문자 정규식
final String BLANKPT = "(\\s)";
// 정규식 검사객체
Matcher matcher;
// 공백 체크
if (password == null || "".equals(password)) {
return "Detected: No Password";
}
// ASCII 문자 비교를 위한 UpperCase
String tmpPw = password.toUpperCase();
// 문자열 길이
int strLen = tmpPw.length();
// 글자 길이 체크
if (strLen > 20 || strLen < 8) {
return "Detected: Incorrect Length(Length: " + strLen + ")";
}
// 공백 체크
matcher = Pattern.compile(BLANKPT).matcher(tmpPw);
if (matcher.find()) {
return "Detected: Blank";
}
// 비밀번호 정규식 체크
matcher = Pattern.compile(REGEX).matcher(tmpPw);
if (!matcher.find()) {
return "Detected: Wrong Regex";
}
// 동일한 문자 3개 이상 체크
matcher = Pattern.compile(SAMEPT).matcher(tmpPw);
if (matcher.find()) {
return "Detected: Same Word";
}
// 연속된 문자 / 숫자 3개 이상 체크
if {
// ASCII Char를 담을 배열 선언
int[] tmpArray = new int[strLen];
// Make Array
for (int i = 0; i < strLen; i++) {
tmpArray[i] = tmpPw.charAt(i);
}
// Validation Array
for (int i = 0; i < strLen - 2; i++) {
// 첫 글자 A-Z / 0-9
if ((tmpArray[i] > 47
&& tmpArray[i + 2] < 58)
|| (tmpArray[i] > 64
&& tmpArray[i + 2] < 91)) {
// 배열의 연속된 수 검사
// 3번째 글자 - 2번째 글자 = 1, 3번째 글자 - 1번째 글자 = 2
if (Math.abs(tmpArray[i + 2] - tmpArray[i + 1]) == 1
&& Math.abs(tmpArray[i + 2] - tmpArray[i]) == 2) {
char c1 = (char) tmpArray[i];
char c2 = (char) tmpArray[i + 1];
char c3 = (char) tmpArray[i + 2];
return "Detected: Continuous Pattern: \"" + c1 + c2 + c3 + "\"";
}
}
}
// Validation Complete
return ">>> All Pass";
}
}
}
결과
Password: "votmdnj&em123"
Detected: Continuous Pattern: "123"
--------------------------------
Password: "kjs@aldkjfklj43"
>>> All Pass
--------------------------------
Password: "QBWfklj4543"
Detected: Wrong Regex
--------------------------------
Password: "abct438983"
Detected: Wrong Regex
--------------------------------
Password: "acdf@sabcer9182"
Detected: Continuous Pattern: "ABC"
--------------------------------
Password: "alfl234kdd"
Detected: Wrong Regex
--------------------------------
Password: "asd@fasdf987"
Detected: Continuous Pattern: "987"
--------------------------------
Password: "xp@tmxm85 84"
Detected: Blank
--------------------------------
Password: ""
Detected: No Password
--------------------------------
Password: "OJHDSJK@HFzDLKDJLJoiejwf42^%wij"
Detected: Incorrect Length(Length: 31)
--------------------------------
Password: "xyz47@"
Detected: Incorrect Length(Length: 6)
--------------------------------
Password: "1lkjvneim@"
Detected: Continuous Pattern: "LKJ"
--------------------------------
Password: "/01alkjdffn"
Detected: Continuous Pattern: "LKJ"
--------------------------------
Password: "9:;aslkdjfkja2"
>>> All Pass
--------------------------------
Password: "?@alakjlkiie3"
>>> All Pass
--------------------------------
Password: "Z[\ekjmvkfd4"
>>> All Pass
--------------------------------
Password: "@abieofinv2"
>>> All Pass
--------------------------------
Password: "89:8973589723dfasb"
>>> All Pass
--------------------------------
Password: "YZ[qoeirnvk235"
>>> All Pass
--------------------------------
정상적으로 비밀번호 검증이 완료된 모습을 볼 수 있다.