BCrypt
단방향 암호화가 가능한 암호화 방법이다.
유저가 보낸 문자열을 암호화할 순 있지만 다시 같은 문자열로 복호화는 불가능하다.
대신 Bcrypt.checkpw(유저가 보낸 패스워드, DB 저장된 해시값) 을 사용하면 암호화된 문자열과 비교는 하게 해준다.
특징
1. 같은 문자를 해싱하면 항상 같은 결과의 암호화된 문자열을 뱉어낸다.
2. 이로 인해 생긴 문자열은 본래의 문자열을 추론할 수 없다.
RSA
양방향 암호화가 가능한 암호화 방법이다.
공유키와 개인키 두 쌍의 키로 암호화를 진행한다.
암, 복호화 둘 다 가능하기 때문에 본래의 문자열을 돌려받을 수 있다.
=====================================================
회원가입 프로세스
1. 유저가 값을 입력할 프론트 단을 생성한다.
2. 값을 서버로 전달하면 받아온 데이터를 DB와 비교해 유효성 검사를 진행한다.
3. 2번을 통과했다면 비밀번호를 Bcrypt로 단방향 암호화를 진행한다.
(DB에 저장될 때, 복호화를 할 수 없는 문자열을 집어넣기 위함)
4. 저장한다.
비즈니스 로직은 최대한 Service에서 짜봤다.
document.addEventListener("DOMContentLoaded", () => {
let userId = document.getElementById('userName');
let pw = document.getElementById('password');
let userName = document.getElementById('displayName');
document.querySelector('.joinBtn').addEventListener('click', function(event) {
event.preventDefault();
// 간단한 유효성 검사
if(userId.value === null || userId.value === undefined || userId.value.trim() === '' || userId.value.length === 0) {
alert('id 다시');
return false;
}
if(pw.value === null || pw.value === undefined || pw.value.trim() === '' || pw.value.length === 0) {
alert('pw 다시');
return false;
}
if(userName.value === null || userName.value === undefined || userName.value.trim() === '' || userName.value.length === 0) {
alert('name 다시');
return false;
}
if(confirm('회원가입을 진행하시겠습니까?') === true) {
$.ajax({
url: '/joinSubmit',
data: $('#userInfo').serialize(),
type: 'POST',
success: function(result) {
alert(result);
console.log('성공');
if(confirm('로그인 페이지로 이동하겠습니까?') === true) {
window.location.href = '/login';
}
},
error: function(xhr, status, err) {
console.log(err);
}
});
}
});
});
로그인 프로세스
1. 유저가 값을 입력할 프론트 단을 생성한다.
2. RSA를 사용하기 위해 로그인 페이지 진입 시, RSAModulus와 Exponent를 서버에서 프론트로 전달받는다.
(공개키를 설정하기 위한 값)
3. 서버로 요청을 보내면 공개키를 생성하고 패스워드를 암호화한다.
4. 전달받은 데이터를 복호화하고 DB의 저장된 해시값과 비교한다. (Bcrypt.checkpw())
5. 로그인 성공
모두 이해하진 못 했지만 RSA 알고리즘 형식으로 암호화를 진행한다는 코드(?)
request 에 modulus, exponent를 담아 프론트로 전달한다.
히든 인풋에 값을 담았다.
$(document).ready(function() {
let loginBtn = document.querySelector('#loginBtn');
loginBtn.addEventListener('click', function(e) {
e.preventDefault();
// 간단한 ID, PW 유효성 검사
if($('#userName').val() === null || $('#userName').val() === undefined || $('#userName').val().trim() === '' || $('#userName').val().length === 0) {
alert('아이디를 입력하세요.');
return false;
}
if($('#password').val() === null || $('#password').val() === undefined || $('#password').val().trim() === '' || $('#password').val().length === 0) {
alert('패스워드를 입력하세요.');
return false;
}
// 공개키 설정과 서버에 넘기기 전 암호화를 위해 클래스 가져오기
let rsa = new RSAKey();
// 서버에서 전달받은 두 값으로 공개키 설정
rsa.setPublic($('#RSAModulus').val(), $('#RSAExponent').val());
// 암호화를 진행하고 패스워드 인풋에 값을 다시 저장한다.
try {
let encryptedPassword = rsa.encrypt($('#password').val());
$('#password').val(encryptedPassword);
} catch (e) {
// 예기치 않은 오류를 대비한 catch문
console.error("Encryption error:", e.message);
alert("Encryption failed: " + e.message);
}
// 서버와 ajax 통신
$.ajax({
url: '/loginSubmit',
data: $('#loginForm').serialize(),
type: 'POST',
success: function(res) {
alert(res.msg);
console.log(res.status);
if(res.status == 1) window.location.href = '/list';
else {
alert('로그인 실패');
return false;
}
},
error: function(xhr, status, err) {
alert(err);
}
});
});
});
public Map<String, Object> loginSubmit(MemberEntity loginInfo, HttpServletRequest request) throws Exception {
HttpSession session = request.getSession();
// 가져온 ID로 DB에 해당하는 데이터 체크
Optional<MemberEntity> isMember = memberRepository.findByUserName(loginInfo.getUserName());
String msg = "로그인 실패"; // 성공, 실패 여부에 따른 메시지 초기화
int isOk = 0; // 성공, 실패 여부에 따른 리다이렉트 주소를 위한 데이터
Map<String, Object> map = new HashMap<>(); // 위 두 변수를 담을 맵
// 가져온 데이터가 DB에 존재한다면
if(isMember.isPresent()) {
Rsa rsa = new Rsa(); // RSA 클래스 가져오기
PrivateKey privateKey = (PrivateKey) session.getAttribute(Rsa.RSA_WEB_KEY); // 세션에 담긴 privateKey 가져오기
String password = rsa.decryptRsa(privateKey, loginInfo.getPassword()); // privateKey와 유저가 가져온 암호화된 비밀번호로 복호화
loginInfo.setPassword(password); // 복호화된 비밀번호 vo에 세팅
String dbPw = isMember.get().getPassword(); // DB에 저장된 해시값
boolean userPw = BCrypt.checkpw(loginInfo.getPassword(), dbPw); // 복호화 비밀번호, 해시값 비교
// true라면
if(userPw) {
//저장과 설정한 데이터들 맵에 담고 프론트단에 리턴
memberRepository.save(loginInfo);
msg = "로그인 성공";
isOk = 1;
map.put("msg", msg);
map.put("status", isOk);
return map;
}
}
return map;
}
코드가 너무 길어 한 페이지 캡쳐가 안됨
스프링 부트 시큐리티 라이브러리는 다시 공부예정
'spring boot' 카테고리의 다른 글
JPA / spring boot (로그인 구현, RSA) (0) | 2024.09.17 |
---|---|
JPA / spring boot (TemplateInputException, dotenv, spring-security / Bcrypt) 회원가입 완성 (1) | 2024.09.17 |
JPA / spring boot (session, token, oauth) (0) | 2024.08.19 |
JPA / spring boot (JPA , ORM, Hibernate) (0) | 2024.08.19 |
JPA / spring boot (JPA , ORM, Hibernate) (0) | 2024.08.11 |