반응형

스프링 시큐리티 공식문서를 참고해서 정리해본 시큐리티 관련 내용

인증(비밀번호)

Password Storage :: Spring Security

스프링 시큐리티에 있는 passwordEncoder인터페이스는 비밀번호의 단방향 변환의 기능을 갖고있다.

유저데이터의 비밀번호 보안 역사

제일 처음 유저데이터의 정보는 평문으로 저장했었다. why? : 유저데이터는 DB에 들어가있으니 DB 어드민이 털리지 않는 이상 문제 없을거라 판단.

이후 악의적인 사용자는 SQL Injection같은 공격을 통해 사용자 이름과 비밀번호가 포함된 데이터를 얻을 방법을 찾아냈고, 이러한 방식으로 데이터의 보안이 뚫리기 시작하자 새로운 암호화 방식인 단방향해시 SHA-256을 통해 정보를 해시화하고 해당 해시를 저장하도록 권장되었다.

이러한 방식은 사용자가 인증을 시도하면 해시된 비밀번호가 입력한 비밀번호의 해시와 비교해서 일치한다면 로그인이 되는데, 이러한 암호화를 뚫기 위해 Rainbow Tables라는 조회 테이블이 생겼고 해당 테이블은 비밀번호의 입력값을 미리 해시화해서 테이블에 넣어두고 역인코딩으로 값을 받아온다

때문에 해당 방식으로도 값을 뚫리게 되고 이를 막기위해 개발자는 소금을 치기 시작한다(특정하지 않은 랜덤한 문자열을 해시문에 포함시킴) 이러한 형식으로 Rainbow Tables 조회테이블에 당하지 않는게 현재 상황이다.

이러한 보안상의 이슈를 스프링 시큐리티에서 자체적으로 지원을 해주는데, 추가로 생각해야 하는 것이 비밀번호 검증을 할 때 의도적으로 리소스(CPU, 메모리, 등등)를 많이 사용하는것이다.

비밀번호를 확인하는데 1초의 지연시간을 주는 이유는 공격자가 암호를 무차별확인을 하면서 해독할 수 있는 시간을 주지 않는것이 주요 목표이다.

때문에 스프링 시큐리티 인증 설정을 할 때 해당 부분을 잘 신경써서 같이 구현해야 할 것이다.

또 위와같이 데이터를 찾는데 시간을 주지않기 위해 장기자격증명이 아닌 단기자격증명으로 지속체크를 하는 것이 좋을 것이다.

 

장기자격증명 → 사용자 이름 및 비밀번호

단기자격증명 → 세션, Oauth토큰

User.UserBuilder users = User.withDefaultPasswordEncoder();

or 

UserBuilder users = User.withDefaultPasswordEncoder();
UserDetails user = users
  .username("user")
  .password("password")
  .roles("USER")
  .build();
UserDetails admin = users
  .username("admin")
  .password("password")
  .roles("USER","ADMIN")
  .build();

기본 Deprecated되어있는데 이유는 메모리와 컴파일된 소스코드에 비밀번호가 노출되어있기 때문에 해당 비밀번호를 외부에서 해시를 해야 안전해짐

때문에 사용x

 

위의 사진은 스프링 시큐리티 클래스의 User클래스 내부 코드중 일부인데 이런식으로 생성자를 통해서도 가입이 가능한듯?

패스워드 인코더는 자바 자체에서 지원하는데,

  • bcrypt
  • ldap
  • MD4
  • MD5
  • noop
  • pbkdf2
  • scrypt
  • SHA-1
  • SHA-256
  • sha256

그냥 이런게 있구나하고 인지하면될듯

주요한 것은 스프링시큐리티에 있는

  • BCryptPasswordEncoder
  • Argon2PasswordEncoder
  • Pbkdf2PasswordEncoder
  • SCryptPasswordEncoder

이 네가지 인코더인데 상황이나 여건에 따라서 네가지중 하나를 사용하면서 비밀번호를 확인하는 시간을 약 1초가 걸리도록 지연하게 하는것이 안전하다

이유는 레인보우테이블 공격을 통해 무차별 인코딩확인을 막기 위해.

PasswordEncoder 또한 지원을 하긴 하는데 현재는 안전하지 않다는 것이 검증되었다. 지원하는 이유는 이전 버전에서 만들어진 코드는 해당 인코더를 사용했었기 때문에

만들어진 비밀번호를 변경하는 방법은

http
    .passwordManagement(Customizer.withDefaults())
http
    .passwordManagement((management) -> management
        .changePasswordPage("/update-password")
    )

위의 방식으로 사용하면 될 것 같음

 

추가로

약관에 동의하더라도 개인정보는 평문으로 저장하면 안된다

이 때문에 스프링 시큐리티의 도움을 반드시 받는것이 중요할 것 같다.

혹시나 서비스할 때 항상 잊지않고 생각해야하는 부분

 

비밀번호 해시 관련 지식참조 사이트

https://starplatina.tistory.com/entry/비밀번호-해시에-소금치기-바르게-쓰기

반응형
반응형

이번 프로젝트에서 리더보드를 구현하는 부분을 맡게 되었다.

리더보드에서는 해당 유저의 엔티티를 통해 하나의 값을 기준으로 내림차순으로 나열하는 방식이였는데, 때문에 JPA를 통해서 findAll로 받고 java에서 Arrays.sort형식으로 구현하려 했었는데 막상 구현하려고 하니 DB에서 데이터를 findAll로 받아서 Java로 구현한다면

"자바를 통해 다시한번 해당 데이터를 처리해야하기에 메모리에 손해가 있지 않을까?"

라는 의문을 시작으로 그렇다면 DB를 사용해서 정렬을 활용하면 더 빠를까?라는 생각을 통해 기본적으로 pk값인 id는 인덱스가 걸려있어서 속도걱정을 안해도 되겠지만 다른 특정한 값에 인덱스를 넣지 않고 orderByDesc를 해야하는 상황이였기에 이게 빠른지 저게 빠른지 모르는 상태였다.

현재는 프로젝트가 대규모 데이터를 갖고있지는 않기에 얼마나 성능적인 면에서 차이가 있을지는 모르겠어서 내가 할 수 있는 최대한의 방법인 검색을 통해 데이터를 수집해 보았고, 정리를 해 보았다.

  • 해당 데이터가 DB측에서 정리가 가능게 되어있는 가공된 데이터들 orderByDesc, Asc 등등 예외사항이 나올 수 없는 경우에 일반적으로 DB에서 처리하는 것이 빠르고 안정적
  • 위에서 언급했던 자바에서 해당 데이터를 한번 더 처리해야하기에 O(2n)의 시간복잡도를 가지게 될 것이라 생각한다.

 

GPT를 통한 답변도 비슷하게 나왔다.

JPA로 정렬하고 가져오는 것과 JPA의 findAll을 통해서 리스트를 가져온 다음 Java로 메서드를 작성해서 정렬하는것 어느게 더 빨라?
JPA
(Java Persistence API
)를 이용한 데이터베이스 쿼리와 Java의 메서드를 이용한 데이터 정렬은 각각 장단점이 있습니다
다음은 주요 차이점과 고려사항입니다

JPA로 정렬하고 가져오는 방법:
성능 : 데이터베이스에서 최적화된 정렬 알고리즘을 이용하기 때문에 대체로 빠릅니다
네트워크 부하 : 필요한 데이터만 가져올 수 있어서 네트워크 트래픽이 줄어듭니다
메모리 사용량 : 필요한 데이터만 메모리에 로드되므로 효율적입니다

Java로 메서드를 작성해서 정렬하는 방법
성능 : Java에서 처리하는 정렬은 데이터베이스의 최적화된 알고리즘보다 일반적으로 느립니다
네트워크 부하 : 모든 데이터를 가져와야 하므로 네트워크 트래픽이 증가할 수 있습니다
메모리 사용량 : 모든 데이터를 메모리에 로드해야 하므로 메모리 사용량이 늘어납니다

이러한 차이점을 고려하면 일반적으로는 JPA를 이용해서 데이터베이스에서 정렬하고 가져오는 것이 더 효율적입니다
그러나 상황에 따라 다를 수 있으므로
성능 테스트를 통해 가장 적합한 방법을 선택하는 것이 좋습니다

결론은 데이터를 추가로 가공할 것이 아니면 자바에서 findAll로 받아올 필요가 없을 것이라 정리가 되었다.

다음에 정렬할 일이 있다면 더 신중하고 정확하게 어느걸 고를지 판단할 수 있을듯 한 느낌😊

반응형

'호기심 천국 > Spring' 카테고리의 다른 글

스프링 시큐리티 1 (인증)  (0) 2023.09.26
스프링부트 final로 클래스 생성자주입  (0) 2023.07.27
반응형

 

스프링 공부를 하면서 final키워드를 만났을 때 평소와 다른 어색한 사용방법 때문에 의문점이 생겼다.

final인데 왜 주입이 되는거지?
@Controller
public class MemberController{
		private final MemberService memberService;

		@Autowired
		public MemberController(MemberService memberService){
				this.memberService = memberService;
		}
		
}

final 선언 후 첫 값 초기화는 가능해서 그런건가? 라는 의문이 들었다.

이유는, 평소 final키워드 선언할 때는 항상 선언과 동시에 값을 초기화하기 때문이다.

이러한 부분이 모호하다 생각해서 final과 함께 서비스를 공부해보았다.

 

final


 

final 키워드는 변경이 더이상 필요없을 때 선언해줄 수 있다.

이번에 스프링부트를 사용하며 의문이 들었기에 위의 코드를 기준으로 스프링부트에서 생성자주입을 할때,

우리는 해당 객체의 메서드의 변경이 더이상 필요없다고 인지를 하고 있을 것이다.

때문에 Service객체에서 사용되는 모든 메서드들은 어디에서 사용이 되던 동일한 역할과 책임이 있다.

그러한 이유로 다른 컨트롤러든 서비스에서든 해당 Service에서 나올 기댓값은 동일하기에 클래스 추가생성을 통해 자원을 낭비할 필요가 없으니 final 선언을 하는 것이고, 이것이 싱글톤형태로 사용되고있는 이유다.

여러 클래스에서 해당 Service클래스를 여러번 선언하고 생성자주입을 한다면 싱글톤으로 돌아갈 수 없다고 생각될 수 있다.

하지만 이 부분은 스프링 빈의 특성을 통해서 해결이 가능하다 스프링 빈은 기본적으로 싱글톤 스코프로 관리되기 때문에 한번 해당 클래스가 스프링 빈으로 등록이 된다면 이후 동일한 인스턴스가 계속 동작한다

반응형

'호기심 천국 > Spring' 카테고리의 다른 글

스프링 시큐리티 1 (인증)  (0) 2023.09.26
데이터베이스 정렬 vs 자바 정렬  (0) 2023.09.12

+ Recent posts