Spring

[Spring Security] 비밀번호 암호화

lyndaa 2023. 6. 12. 01:15

Bcrypt 란?

Bcypt는 브루스 슈나이어가 설계한 키(key) 방식의 대칭형 블록 암호에 기반을 둔 암호화 해시 함수다. Niels Provos 와 David Mazières가 설계했다. Bcrypt는 레인보우 테이블 공격을 방지하기 위해 단방향 암호화에 솔팅과 키 스트레칭을 적용한 대표적인 예이며 복호화가 불가하다. 이를 활용하여 비밀번호 암호화를 해볼 예정이다 !

 

1. Spring Security 의존성 주입

우선 비밀번호 암호화에 사용되는 PasswordEncoder를 사용하기 위해서는 Spring Security 의존성을 주입해줘야 한다.

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-security</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-test</artifactId>
			<scope>test</scope>
		</dependency>

 

2. Config 설정

Spring Security를 사용하여 웹 보안을 구성하는 설정 클래스인 SecurityConfig이다. Config 객체는 WebSecurityConfigurerAdapter를 상속받아서 configure()를 구현한다.

@EnableWebSecurity 어노테이션은 Spring Security를 활성화하는 역할을 한다.

 

암호화 방식으로 위에서 말한 BCrypt를 이용하기 위해 BCrypt라는 해시 함수를 이용하여 패스워드를 암호화하는 구현체인BCryptPasswordEncoder를 적용했다.

package com.gld.model.config;

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 SecurityConfig 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();
	}

}

 

3. 회원가입/로그인 구현

회원가입 : 

passwordEncoder.encode() 메서드는 주어진 문자열을 암호화하여 반환하는 메서드이다. 이 메서드를 사용하여 사용자의 비밀번호를 암호화할 수 있다.

 

로그인 : 

passwordEncoder.matches() 메서드는 주어진 원본 문자열과 암호화된 문자열을 비교하여 일치 여부를 확인하는 메서드이다. 이 메서드를 사용하여 사용자가 제공한 비밀번호와 저장된 암호화된 비밀번호를 비교할 수 있다.

 

@Controller

package com.gld.model.controller;

import javax.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import com.gld.model.biz.LoginBiz;
import com.gld.model.dto.UserDto;

@Controller
@RequestMapping("/login")
public class LoginController {

	@Autowired
	private LoginBiz loginBiz;

	@GetMapping("/login")
	public String moveToLogin() {
		System.out.println("go to loginform");
		return "loginform";
	}

	@GetMapping("/join")
	public String moveToJoin() {
		System.out.println("go to joinform");
		return "joinform";
	}

	@GetMapping("/find")
	public String moveToFind() {
		System.out.println("go to findform");
		return "findform";
	}
	
	@PostMapping("/logincheck")
	public String login(String userId, String userPw, HttpSession session,Model model) {
		System.out.println("logincheck");
		System.out.println(userId);
		UserDto user = loginBiz.validationLogin(userId,userPw);
		if (user!=null) {
			session.setAttribute("user", user);
			return "redirect:/challenge/main";
		} else {
			model.addAttribute("error", "아이디 또는 비밀번호가 일치하지 않습니다.");
			return "loginform";
		}
	}

	@PostMapping("/joincheck")
	public String join(UserDto dto, RedirectAttributes redirectAttributes) {
		System.out.println("joincheck");
		loginBiz.insert(dto);
		System.out.println("join");
		redirectAttributes.addFlashAttribute("message","회원가입이 완료되었습니다. 로그인 해주세요.");
		return "redirect:/login/login";
	}

	@GetMapping("/logout")
	public String logout(HttpSession session) {
		session.removeAttribute("user");
		return "redirect:/";
	}
}

@Service

package com.gld.model.biz;

import javax.transaction.Transactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import com.gld.model.dto.UserDto;
import com.gld.model.mapper.UserMapper;
import com.gld.model.repository.UserRepository;

@Service
@Transactional
public class LoginBiz{

	private UserRepository userRepository;
    private PasswordEncoder passwordEncoder;
	private UserMapper usermapper;
	
	public LoginBiz(UserRepository userRepository, PasswordEncoder passwordEncoder, UserMapper usermapper) {
		super();
		this.userRepository = userRepository;
		this.passwordEncoder = passwordEncoder;
		this.usermapper = usermapper;
	}
	
	public UserDto findByUserId(String userId) {
		UserDto user = userRepository.findByUserId(userId);
		return user;
	}

	public int changePw(UserDto user, String userPw) {
		return usermapper.updatePw(user.getId(), passwordEncoder.encode(userPw));
	}
	
	public void insert(UserDto user) {
        String encodedPassword = passwordEncoder.encode(user.getUserPw());
        user.setUserPw(encodedPassword);
        userRepository.save(user);
    }
	
	public UserDto validationLogin(String userId,String userPw) {
	   UserDto user = userRepository.findByUserId(userId);
	   if(user==null) {
	      return null;
	   }
	   else if(!passwordEncoder.matches(userPw, user.getUserPw())) {
	      return null;
	   }else {
		   return user;
	   }
	}
}