2022. 3. 22. 09:54ㆍTechnology[Back]
최근 보안이슈가 상당히 중요해지다보니 백앤드 차원에서 비밀번호와 같은 중요정보를 문자열 형태가 아닌 암호화하여 이해하기 힘든 문자열로 DB에 저장하고 이후 로그인 등으로 입력받은 비밀번호와의 비교가 필요한 경우 문자열 직접비교가 아닌 입력받은 비밀번호와 암호화된 DB저장된 비밀번호를 matches함수를 이용해 비교함으로써 비밀번호의 유출가능성을 낮추는 방법에 대해 알아보고자 합니다.
(1) Spring - build.gradle
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-security', version: '2.4.5'
springboot의 spring-boot-starter-security를 implement합니다.
(2) Spring - WebSecurityConfig
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder getPasswordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().disable().csrf().disable().formLogin().disable().headers().frameOptions().disable();
}
}
보안관련 설정을 담당하는 별도의 class를 만들어주신 다음에 WebSecurityConfigurerAdapter를 extends해서 암호화를 구현합니다. 이를 위해 어노테이션으로 Configuration(설정파일)임을 지정해주고 비밀번호 암호화를 시키는 BCryptPasswordEncoder를 return하는 함수를 작성합니다. 이후 configure 메서드를 오버라이드해서 cors 정책 등을 disable을 통해 에러를 방지합니다.
(3) Spring - Controller
import com.example.DigitalGameNomad.Entity.UserInfo;
import com.example.DigitalGameNomad.Repository.UserInfoRepository;
import org.apache.catalina.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.Arrays;
import java.util.HashMap;
@RestController
@CrossOrigin("*")
public class UserController {
@Autowired
UserInfoRepository userInfoRepository;
@Autowired
PasswordEncoder passwordEncoder;
@PostMapping("/signUp")
public String signUp(@RequestBody(required = true) HashMap<String, Object> userData) {
System.out.println(userData);
String id = (String) userData.get("id");
String password = (String) userData.get("password");
String name = (String) userData.get("name");
String phone = (String) userData.get("phone");
String level = (String) userData.get("grade");
String encodedPassword = passwordEncoder.encode(password);
UserInfo newUser = new UserInfo();
newUser.setUser_id(id);
newUser.setUser_pw(encodedPassword);
newUser.setUser_name(name);
newUser.setUser_phone(phone);
newUser.setUser_level(Long.valueOf(level));
userInfoRepository.save(newUser);
return "Spring ===> 회원 가입 성공!";
}
@PostMapping("/login")
public UserInfo login(@RequestBody(required = true) HashMap<String, String> loginData, HttpServletRequest request,
HttpServletResponse response) {
String loginId = loginData.get("userId");
String password = loginData.get("password");
UserInfo loginUser = userInfoRepository.findByUserId(loginId);
if(loginUser == null) {
System.out.println("로그인 컨트롤러 ===> 없는 아이디");
return null;
}
else if(!passwordEncoder.matches(password, loginUser.getUser_pw())) {
System.out.println("로그인 컨트롤러 ===> 비밀번호 다름");
return null;
}
else {
System.out.println("로그인 컨트롤러 ===> 로그인 성공");
return loginUser;
}
}
@PostMapping("/updateUser")
public String updateUser(@RequestBody(required = true) HashMap<String, String> loginData) {
String userKey = (String)loginData.get("loginUserKey");
System.out.println(loginData);
UserInfo userInfo = userInfoRepository.findById(Long.valueOf(userKey)).get();
if(loginData.get("loginUserPw") != null) {
String loginUserPw = (String)loginData.get("loginUserPw");
String encodedPassword = passwordEncoder.encode(loginUserPw);
userInfo.setUser_pw(encodedPassword);
}
if(loginData.get("loginUserPhone") != null) {
userInfo.setUser_phone((String)loginData.get("loginUserPhone"));
}
userInfoRepository.flush();
return "success";
}
}
이후 회원가입, 로그인 등을 관리하는 Controller에서 조금 전에 설정파일에서 사용했던 PasswordEncoder를 AutoWired 어노테이션으로 연결시킨 후 회원가입 진행 시 "signUp"으로 Post요청이 들어오면 받은 password 문자열을 passwordEncoder의 encode메서드를 이용해 암호화를 진행한 후 해당 암호화된 비밀번호를 DB에 JPA를 이용해 저장하고 있습니다. 이후 해당 회원이 로그인을 진행하면 "login"으로 Post요청이 들어오고 이 때 입력받은 비밀번호와 DB상에 저장되어있는 암호화된 비밀번호를 passwordEncoder의 matches메서드를 이용해 비교하게 되면 굳이 복호화를 하지 않아도 비암호화된 비밀번호와 암호화된 비밀번호의 일치여부를 확인할 수 있습니다. 이를 통해 로그인을 구현하시면 됩니다. 마지막으로 비밀번호를 바꾸는 요청이 "updateUser"로 Post요청이 들어오면 회원가입과 동일하게 입력받은 비밀번호 문자열을 passwordEncoder의 encode메서드를 이용해 암호화한 뒤 해당 암호화된 문자열을 DB에 저장하시면 됩니다.
이번 게시글에서는 보안의 기초라고 할 수 있는 비밀번호 암호화에 대해 알아보았습니다.
'Technology[Back]' 카테고리의 다른 글
스프링 배치의 chunk (1) | 2023.08.09 |
---|---|
React - Spring Naver SENS API를 이용해 문자 인증 구현하기 (1) | 2022.03.25 |
이미지파일을 BLOB 형태로 DB에 저장하기 && React - Spring 간 서버통신 시 BLOB 형태를 이용해 이미지파일 데이터 전송하기 (1) | 2022.02.11 |
정적 이미지파일 Spring - React간 서버전송하기 (1) | 2022.02.09 |
Spring JPA 사용 시 나타날 수 있는 문제점 해결법(referencedColumnName) (1) | 2022.02.06 |