▸Spring Security

스프링 Security_로그인_DB 패스워드 암호화 [5/9]

코데방 2020. 3. 29.
728x90
- Develop OS : Windows10 Ent, 64bit
- WEB/WAS Server : Tomcat v9.0
- DBMS : MySQL 5.7.29 for Linux (Docker)
- Language : JAVA 1.8 (JDK 1.8)
- Framwork : Spring 3.1.1 Release
- Build Tool : Maven 3.6.3
- ORM : Mybatis 3.2.8

 

커스터마이징 순서대로 총 9개의 포스팅으로 나눠져 있습니다. 순서대로 보면 쉽게 적용할 수 있습니다.

 

[Spring MVC/- 기본 문법] - 스프링 Security_로그인_기본 컨텍스트 설정 [1/9]

[Spring MVC/- 기본 문법] - 스프링 Security_로그인_DB 연동 로직 작성 [2/9]

[Spring MVC/- 기본 문법] - 스프링 Security_로그인_로그인 실패 대응 로직 [3/9]

[Spring MVC/- 기본 문법] - 스프링 Security_로그인_로그인 성공 대응 로직 [4/9]

[Spring MVC/- 기본 문법] - 스프링 Security_로그인_DB 패스워드 암호화 [5/9]

[Spring MVC/- 기본 문법] - 스프링 Security_로그인_암호화된 DB 패스워드로 인증 [6/9]

[Spring MVC/- 기본 문법] - 스프링 Security_로그인_Principal 객체 [7/9]

[Spring MVC/- 기본 문법] - 스프링 Security_로그인_자동 로그인(Remember-me) [8/9]

[Spring MVC/- 기본 문법] - 스프링 Security_로그인_security 태그 라이브러리 [9/9]

 

 


 

 

정보보안규정에 따라, 패스워드는 DB에 단방향 암호화되어 저장해야 합니다. 단방향 암호화란 한 번 암호화해두면 다시 복호화를 할 수 없는 방식이라는 것입니다. 사실 랜덤 알고리즘이 아닌 이상에야 이론적으로 복호화가 불가능한 암호화는 없습니다만, 워낙 복잡해서 현재의 컴퓨팅 능력으로는 뚫리지 않는 암호화 수준을 "단방향 암호화" 라고 합니다. 

 

대표적인 단방향 암호화 방식으로는 "SHA-256" 또는 "SHA-512" 가 있습니다. Hash 방식을 이용해 암호화를 하는 방식이며, 여담이지만 비트코인도 이 방식으로 암호화 되어 있다고 합니다. 암호문에서 평문으로 복호화가 안되니 평문을 랜덤으로 계속 넣어서 같은 암호문이 나오는지 확인하는 노가다 과정을 채굴이라고 하는 것을 얼핏 들었던 것 같습니다. 아닐 수도 있습니다. ㅎㅎ

 

 

 

복호화가 불가능하기 때문에 암호화된 패스워드를 평문 패스워드와 비교할 수 없습니다. 따라서 패스워드 암호화를 위해서는 두 가지 절차가 필요합니다.

 

1. DB에 저장할 때 패스워드를 암호화

2. 사용자가 입력한 패스워드를 같은 방식으로 암호화해 DB에 저장된 데이터와 비교

 

이번 글에서는 1번의 내용을 다루고, 다음 글에서 2번의 내용을 다루도록 하겠습니다.

 

 


 

 

 

[ BCryptPasswordEncoder 클래스를 사용한 암호화 테스트 ]

 

스프링 Security에서 제공해주는 SHA-512 단방향 암호화 기능을 가진 클래스입니다. 이외에도 여러 클래스들이 있지만 저는 이 클래스를 사용하도록 하겠습니다. 매초마다 key값을 달리해 암호화를 진행하므로 매우 강력한 보안을 가진, 패스워드 전용으로 구현된 암호화 기능을 제공해줍니다. 

 

 

1. Bean 등록

 

암호화 담당 객체는 암호화 할 때마다 생성할 필요가 없으므로 컨텍스트 파일에 Bean으로 등록해줍니다. 

	<!-- 패스워드 단방향 암호화 -->
	<bean id="passwordEncoder"
		class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>

 

 

 

2. 암호화 테스트

 

암호화가 잘 되는지 jUnit을 통해 확인해보았습니다.

 

주의할 점은 이 클래스는 아주 강력한 암호화 방식이라, 같은 원본을 암호화할 때마다 매번 다른 값을 가져온다는 것입니다. 따라서 로그인할 때 사용자가 입력한 패스워드를 다시 암호화해서 DB에 저장된 암호화 문자열과 단순 비교를 하게 되면 false가 됩니다.

 

	@Test
	public void passwordEncoding() {

		BCryptPasswordEncoder pwEncoder = 
				(BCryptPasswordEncoder) ctx.getBean("passwordEncoder");

		String pw = "codevang";
		String encodedPw1 = pwEncoder.encode(pw);
		String encodedPw2 = pwEncoder.encode(pw);

		System.out.println("원본 : " + pw);
		System.out.println("첫번 째 인코딩 : " + encodedPw1);
		System.out.println("두번 째 인코딩 : " + encodedPw2);

		System.out.println("단순 문자열 비교 : "
				+ encodedPw1.equals(pwEncoder.encode("codevang")));
	}

 

 

 

3. 원본 문자열과 암호화 문자열 비교 테스트

 

따라서 원본 문자열과 암호화된 문자열을 비교하기 위해서는 BCryptPasswordEncoder 클래스에서 제공하는 기능을 사용해야 합니다. 

 

* boolean matches(평문 문자열, 암호화된 문자열)

 

내부 로직을 정확히 알 순 없지만, 아마 암호화된 문자열의 일부분이 암호화할 때 사용한 key값이 되는 것 같습니다. 비교할 때는 암호화된 문자열에 속한 key값을 참조해 평문 문자열을 다시 암호화한 뒤 비교하는 것이 아닐까 합니다. 그래서 그런지 이 클래스를 통해 암호화되지 않은 문자열을 넣으면 예외가 발생합니다. 키값을 못찾아서 그런것 같습니다.

 

	@Test
	public void passwordEncoding() {

		BCryptPasswordEncoder pwEncoder = 
				(BCryptPasswordEncoder) ctx.getBean("passwordEncoder");

		String pw = "codevang";
		String encodedPw1 = pwEncoder.encode(pw);
		String encodedPw2 = pwEncoder.encode(pw);

		System.out.println("원본 : " + pw);
		System.out.println("첫번 째 인코딩 : " + encodedPw1);
		System.out.println("두번 째 인코딩 : " + encodedPw2);

		System.out.println("matches 메소드 사용 비교 : "
				+ pwEncoder.matches(pw, encodedPw1));
	}

 

 


 

 

4. 회원가입 로직에 암호화 적용

 

암호화 테스트가 완료되었으니 이제 회원가입 로직에 적용해주면 됩니다. 굳이 보여드릴 필요는 없겠으나 샘플로 첨부합니다. 회원가입을 해보면 암호화된 문자열이 패스워드로 입력되는 것을 확인할 수 있습니다. 

 

BCrypt 암호화는 패스워드 컬럼 크기를 char(60)로 잡아줘야 합니다. 고정된 문자열 크기는 varchar보다 char 타입이 빠르게 작동합니다. Hash 방식은 어떤 길이의 문자열도 고정된 길이의 암호문으로 바꿔줍니다.

 

alter table user_info change userPw userPw char(60) not null;

mysql

package hs.spring.hsweb.service.user;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import hs.spring.hsweb.mapper.user.UserMapper;
import hs.spring.hsweb.mapper.vo.user.UserInfoVO;

@Service("userInfoService")
/* 메인화면 및 로그인 관련 */
public class UserService {

	@Autowired
	private UserMapper mapper;
	
	@Autowired
	private BCryptPasswordEncoder pwEncoder;

	/* 회원 가입(회원 정보 입력) */
	public boolean insertUserInfo(UserInfoVO userInfo) {
		
		// 존재하는 ID 여부 확인
		Integer userCount = mapper.selectUserInfoCount(userInfo.getUserId());
		if (userCount > 0) {
			return false;

		} else {

			// userInfo의 내용 중 패스워드를 암호화시켜서 바꿔줌
			userInfo.setUserPw(pwEncoder.encode(userInfo.getUserPw()));

			
			// 회원정보 및 디폴트 권한 DB 입력
			mapper.insertUserInfo(userInfo);
			mapper.insertUserAuthDefault(userInfo.getUserId());
			return true;
		}
	}
}

 

 


 

 

다음 글에서는 스프링 security를 이용한 인증 로직(Provider)을 커스터마이징해서 암호화돼 저장된 패스워드로 인증받는 방법에 대해 포스팅하도록 하겠습니다. 

728x90

댓글

💲 추천 글