스프링 BCryptPasswordEncoder 회원가입/로그인

HOHO·2023년 5월 17일

Spring

목록 보기
15/15

레거시프로젝트로 Spring Security 전체를 써보는건 너무 어렵다....
보안에 관심이 많아서 시작해보려했지만 이제 4개월 배운 초보는 어림도 없지!
그래도 이왕 시작해본거 결과물 간단한거 하나라도 건지자 해서 BCryptPasswordEncoder 하나만 사용해보았다


bcrypt는 일방향 해시 함수로서, 동일한 입력에 대해 항상 다른 해시 값을 생성합니다. 이것은 bcrypt의 보안 강화를 위한 특징입니다. 이러한 특성은 레인보우 테이블 공격(Rainbow table attack)과 같은 공격을 어렵게 만들어줍니다.

bcrypt는 다음과 같은 특징을 가지고 있습니다:

솔트(Salt) 사용: bcrypt는 솔트라고 하는 무작위 데이터를 사용하여 해시를 생성합니다. 이 솔트는 각각의 비밀번호에 대해 고유하게 생성되며, 비밀번호와 결합되어 해시화됩니다. 각 비밀번호마다 다른 솔트를 사용하기 때문에 동일한 비밀번호라도 서로 다른 해시 값이 생성됩니다.

스크립트 합: bcrypt는 스크립트 합(script cost)이라는 개념을 도입하여 연산 비용을 증가시킵니다. 이것은 더 많은 시간과 리소스를 필요로 하게 만들어 brute-force 공격을 어렵게 만듭니다. 스크립트 합을 높이면 해시 생성에 걸리는 시간이 증가하므로, 암호화된 비밀번호를 해독하기 위한 시간도 증가합니다.

임의성: bcrypt는 해시 생성에 무작위성 요소를 도입합니다. 동일한 비밀번호에 대해 여러 번 해시를 생성하면 매번 다른 결과가 생성됩니다. 이것은 보안을 강화하기 위해 사용되는 임의성 요소입니다.

비밀번호를 해독하는 과정에서 알고리즘을 더욱 복잡하게 만들어 brute-force와 다양한 공격을 어렵게 합니다.


.xml

//security-context.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:security="http://www.springframework.org/schema/security"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd">
		
	<bean id="bcryptPasswordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" />

</beans>


//web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee https://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

	<!-- The definition of the Root Spring Container shared by all Servlets and Filters -->
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>
		/WEB-INF/spring/root-context.xml
		/WEB-INF/spring/appServlet/security-context.xml
		</param-value>
	</context-param>
	
	<!-- Creates the Spring Container shared by all Servlets and Filters -->
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>

	<!-- Processes application requests -->
	<servlet>
		<servlet-name>appServlet</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>
			/WEB-INF/spring/appServlet/servlet-context.xml
			/WEB-INF/spring/appServlet/security-context.xml
			</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>
		
	<servlet-mapping>
		<servlet-name>appServlet</servlet-name>
		<url-pattern>/</url-pattern>
	</servlet-mapping>

</web-app>

web.xml에서 둘중 한곳만 추가하는게 아닌지 아직 잘모르겠다...

mapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="ccc.ddd.mapper.SignupMapper">

<insert id="passencode">
insert into secmeminfo(username, password)
values(#{username}, #{password})
</insert>

<select id="login" parameterType="ccc.ddd.model.UserDataVO" resultType="ccc.ddd.model.UserDataVO">
select * from secmeminfo
where username = #{username}
</select>
</mapper>

중요한 사실! BCryptPasswordEncoder를 사용해서 비밀번호에 관한건 전부 쟤한테 담당하게 할거니까 로그인 쿼리를 작성할때는 where절에서 password를 빼준다!

service/mapper.java

@Service
public class SignUpService {
	
	@Autowired
	SignupMapper signMap;
	
	public void passencode(UserDataVO uData) {
		signMap.passencode(uData);
	}
	
	public UserDataVO login(UserDataVO uData) {
		return signMap.login(uData);
	}
}


public interface SignupMapper {
	public void passencode(UserDataVO uData);
	public UserDataVO login(UserDataVO uData);
}

VO.java

public class UserDataVO {
	private String username; // ID
	private String password; // PW
	
	public String getUsername() {
		return username;
	}
	public void setUsername(String username) {
		this.username = username;
	}
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}
	@Override
	public String toString() {
		return "UserDataVO [username=" + username + ", password=" + password + "]";
	}
}

controller

@Controller
public class HomeController {
	
	private static final Logger logger = LoggerFactory.getLogger(HomeController.class);
	
	@Autowired
	SignUpService signServ;
	
	@Autowired
	private BCryptPasswordEncoder bpassencode;
	
	@Autowired
	private PasswordEncoder pwEncoder;
	
	@GetMapping("signup")
	public String gosignup(UserDataVO uData) {
		return "signup";
	}
	
	@GetMapping("login")
	public String gologin(UserDataVO uData) {
		return "login";
	}
	
	@GetMapping("/memsignup")
	public String signup(UserDataVO uData) {
		logger.info("Welcome signup!");
		String clearPw;
		String encodePw;
		clearPw = uData.getPassword();
		encodePw = bpassencode.encode(clearPw);
		uData.setPassword(encodePw);
		signServ.passencode(uData);
		logger.info("Welcome signup! uData is {}.", uData);
		return "login";
	}
	
	@GetMapping("memlogin")
	public String memlogin(UserDataVO uData) {
		String clearPw;
		String encodePw;
		UserDataVO userData = signServ.login(uData);
		if(userData != null) {
			clearPw = uData.getPassword();// 사용자가 제출한 비밀번호
			encodePw = userData.getPassword();// 데이터베이스에 저장한 인코딩 비밀번호
			if (true == bpassencode.matches(clearPw, encodePw)) {//비밀번호 일치 여부 판단
				userData.setPassword("");// 인코딩된 비밀번호 정보 지움
                //session.setAttribute("member", lvo);// session에 사용자 정보 저장
				return "home";
            } else {
                return "/login";
            }
        } else {        // 일치하는 아이디가 존재하지 않을 시
        	return "/login";
        }
	}
}

BCryptPasswordEncoder의 메소드 encode(clearPw)를 사용해서 평문이었던 비밀번호를 암호화해서 회원가입 insert를 DB에 넣어준다

로그인시에는 matches(clearPw, encodePw)메소드를 사용해서 평문비번과 암호화비번을 비교해준다

security-context에 작성해준 BCryptPasswordEncoder를 컨트롤러로 불러와서 의존성 주입해주고 사용하면 된다

private PasswordEncoder pwEncoder;의 구현체가 BCryptPasswordEncoder이 녀석이라고 해서 나중에 따로 사용해보려고 추가 해놓았다

DB의 비밀번호 칼럼은 60자로 설정하는게 좋다고하니 앞으로도 60자 이상으로 설정!


시큐리티의 권한부여에서 너무 헤매고 있다.... 열심히해야지 재밋으니까 ㅎㅎ

profile
기계 그잡채가 되고싶다

0개의 댓글