본문 바로가기
TIL(Today I Learned)

Spring Security를 이용한 비밀번호 암호화 로직 구현 과정 기록

by kkkdh 2023. 2. 28.
728x90

오늘은 회원 관리 시스템에서 평문(plain text)으로 암호를 저장하는 것은 보안상 굉장히 위험하기 때문에, 이를 Spring Security를 이용해 암호화(Encryption)하는 과정을 진행하고, 기록하려 합니다.


Spring Security 의존성 추가

build.gralde에 의존성을 추가한다.


양방향과 단방향 암호화란 뭘까?

제가 적용할 암호화는 두 가지 중에서 단방향 암호화입니다.

 

그전에 양방향과 단방향 암호화가 각각 무엇을 의미하는지 찾아봤습니다.

  • 양방향 암호화: 암호화된 암호문을 복호화할 수 있는 암호화
  • 단방향 암호화: 암호화된 암호문을 복호화 불가능한 암호화

양방향 암호화는 또 대칭키를 사용하는 방식과 비대칭키를 사용하는 방식으로 나뉜다고 하는데, 이번에 필요한 개념은 아니기 때문에, 여기까지만 정리하고 넘어가겠습니다.

 

단방향 암호화는 위에서 정리한 바와 같이 복호화 불가능한 암호화 방식입니다. 

 

복호화 불가능한 방식으로 암호를 처리하는 건 당연히 보안적으로 더 좋은 방식이지만, 비밀번호를 사용자 말고는 모르기 때문에, 비밀번호 찾기가 불가능하고 비밀번호 재설정만 가능하다는 단점이 있습니다.

 

어쨌든 단방향 암호화를 사용하기로 선택했고, 단방향 암호화는 주로 Hash 기법을 활용한다고 합니다.

 

그중에서도 Spring Security에서 제공하는 hahsing 알고리즘 중 BCrypt 알고리즘을 이용해 단방향 암호화를 구현하는 코드 예시들을 참고했습니다.


이제 코드를 짜보자

Spring Security에서는 비밀번호 암호화(encoding)를 위해 PasswordEncoder interface를 제공하며, 그 구현체 중 하나로 BCryptPasswordEncoder를 제공합니다. (BCrypt hashing 알고리즘을 이용하는 구현체)

 

BCrypt hashing 알고리즘을 적용한 암호화 과정을 위해 설정을 해주겠습니다.

프로젝트의 소스 폴더에 configuration 패키지를 만든 뒤 패키지 안에 SecurityConfig라는 이름으로 새로운 클래스를 추가한 뒤 위와 같이 코드를 짰습니다.

 

다른 개발자 분들의 블로그를 보아하니 WebSecurityConfigurerAdapter라는 interface를 상속해서 SpringSecurity를 이용한 URI에 따른 인증과 인가 방식을 정의했던 모양인데

 

 

Spring | Home

Cloud Your code, any cloud—we’ve got you covered. Connect and scale your services, whatever your platform.

spring.io

이 글에 따르면, Spring Security 5.4부터는 WebSecurityConfigurerAdapter가 deprecated 되어 다음과 같은 방식을 사용해야 한다고 합니다.

filterChain method를 직접 선언해 Bean으로 등록해 사용해야 해서 위와 같이 작성해 봤습니다.

 

csrf method와 disable method가 의미하는 바는 CSRF(크로스 사이트 요청 위조)라는 인증된 사용자를 이용해 서버에 위험을 끼치는 요청들을 보내는 공격을 방어하기 위해 post 요청마다 token이 필요한 과정을 생략하겠음을 의미한다고 합니다.

 

이 부분을 명확하게 이해하려면, csrf에 대해서도 정리해 봐야 될 것 같습니다.

 

다시 본론으로 돌아와서 PasswordEncoder 추상체의 구현체 등록은 앞선 코드 작성을 통해 수동 빈 등록 과정을 구현할 수 있었습니다.

 

예제에 따라 메서드 형태는 다른데, 저는 추상화인 PasswordEncoder라는 이름으로 스프링 빈을 생성하고 관리하기 위해서(DIP를 지키기 위해) 위와 같이 메서드를 작성해 봤습니다.

 

(이 부분은 김영한 님의 스프링 핵심 원리 기본편의 가르침을 적용해 봤습니다.)


이제 사용해보자!

이제 설정 정보 세팅을 완료했으니, 암호화를 적용한 인증 과정과 DB에 암호화된 비밀번호가 잘 저장되는지 확인해보려고 합니다.

 

참고로 PasswordEncoder에서 두 개의 method를 대표적으로 활용 가능한데, 바로 encode와 matches입니다.

 

처음에는 matched method의 존재를 모르고 encode method만을 이용해서 DB에 저장된 암호화된 비밀번호와 매번 새롭게 입력으로 들어온 비밀번호를 비교했는데, 이렇게 하면 안 된다고 합니다.

 

왜 그런지 보기 위해서, BCryptPasswordEncoder 구현체 클래스의 encode, getSalt method 코드를 확인해 알 수 있었습니다.

함수 호출시마다 salt라는 값이 랜덤하게 생성되어 비밀번호를 암호화하는 과정에 포함되는 것 같습니다.

encode method를 실행할 때마다 getSalt method를 호출해 반환된 값으로 hashing 함수를 거쳐 암호화 과정을 진행하는데, 이 salt 값이 호출시마다 매번 새로 생성되기 때문에, 같은 비밀 번호를 넣어도 같은 해싱 결과를 기대할 수 없다는 것이 그 이유였습니다. 

 

따라서 matches method를 사용해야만, 올바른 비밀번호 비교가 가능한 것이었죠

 

처음에는 PasswordEncoder가 다른 객체에서 의존성 주입 시점에 차이가 생겨서 그런가? 싶었는데, 생각해 보니 싱글톤 패턴으로 스프링 빈이 관리되니 그럴 리가 없어 찾아보니 위와 같은 이유가 있었습니다.

MemberRepository의 login method입니다.

위 코드는 회원 관리를 위한 repository component 구현체의 login method 코드입니다.

 

PasswordEncoder를 생성자 주입을 통해 주입받아 matched method를 이용해 비밀번호를 확인합니다.

 

기존에는 JPQL을 이용해 비밀번호 일치 여부까지 확인하는 로직을 거쳤는데, JPQL은 이메일에 맞는 회원 조회 과정까지 관여하고, 애플리케이션에서 암호 확인 로직을 확인하도록 변경했습니다.

 

controller에서 암호화 적용

회원 가입 과정은 controller에서 암호화가 진행되게 구현했는데, 지금 생각하니 뭔가 통일성이 없는 것 같기는 하지만 가볍게 공부하는 사이드 프로젝트이기 때문에 그냥 이렇게 짰습니다.

 

실행 결과 회원 가입과 로그인 모두 잘 작동함을 확인할 수 있었습니다.

로그인, 회원 가입 화면은 부트스트랩을 참고해서 대강 만들어 봤습니다.

DB에도 잘 저장됩니다.

다음에는 지난번에 jwt 토큰을 만드는 코드를 작성한 부분을 활용해서 아까 작성했던 configuration 부분에 인증과 인가 파트를 구현해보려고 합니다!


참고한 글과 자료들

 

[Spring Boot] Spring Security 적용하기 - 암호화

프로젝트를 진행하면서 사용자 시스템을 구축한다면 필연적으로 인증 로직도 구현해야한다. 이 과정에서 만약 사용자의 비밀번호를 평문(Plain Text)으로 저장한다면, 심각한 보안상 문제를 초래

hou27.tistory.com

 

 

[Spring] Security WebSecurityConfigurerAdapter Deprecated 해결하기

WebSecurityConfigurerAdapter Deprecated 해결하기 최근에 Spring Security를 설정해보려고 WebSecurityConfigurerAdapter를 사용하려 보니 Deprecated가 되어 있었는데요. @RequiredArgsConstructor @EnableWebSecurity public class WebSecur

devlog-wjdrbs96.tistory.com

 

 

Spring Password Encoder

Spring에서는 인증/권한인가 등의 처리가 필요할 때 사용하라고 만든 Spring Security 패키지가 존재한다. 그 중 유저가 입력하는 Password를 암호화해서 저장하는 방법에 대해서 알아보자 아, 그 전에

gompangs.tistory.com

 

728x90

댓글