728x90
반응형
목차
1. 암호화 표준
2. 비밀번호 검증 순간의 코드
3. 해싱과 PasswordEncoder로 비밀번호가 인증되는 과정
4. PasswordEncoder의 메서드들
5. PasswordEncoder의 다양한 Encoder들
6. BCryptPasswordEncoder적용하기
1. 암호화 표준
암호화 표준 - 어느 것이 적합할까?
- Encoding
- 데이터를 한 형식에서 다른 형식으로 변환
- 어떠한 기밀성도 포함하지 않는다.
- 누구든지 디코딩 가능, 완전 가역적
- 비밀번호 관리에는 적합하지 않다.
- 예시 ) ASCII, BASE64, UNICODE
- Encryption
- 기밀성을 보장
- 데이터를 암호화하려고 할 때 특정 알고리즘을 따르고 비밀 키를 제공한다.
- 복호화하는데 필요한 것이 비밀 키다. 또한 동일한 알고리즘이 필요하다.
- Hashing(가장 많이 쓰이는 방식)
- 비가역적
- One-way (한 번 해싱처리를 하면 복호화가 불가능하다)
- 최초의 일반 텍스트 비밀번호를 풀어내는 것은 매우 힘든 일
- db에 비밀번호를 저장하기위한 업계 표준이 되었다.
- 로그인 작업에서 빔닐번호에 기반된 해시값과 db에서 저장해 둔 해쉬값이 있다. 이 두개 값을 비교해서 일치하면 성공
- 해쉬화 처리된 비밀번호는 일반 텍스트 비밀번호를 알 수 없기 때문에 아는 사람만 접속 가능
2. 비밀번호를 어떻게 검증하는 순간의 코드
해당 과정 중 3-4-5,6에 해당한다.
Authentication Manager는 Authentication Providers들중 인증이 성공되는 것들을 찾기 시작한다.
3. 비밀번호 검증의 순간
- DaoAuthenticationProvider의 Authentication에서 검증이 이루어진다.
- DaoAuthenticationProvider는 Security에서 기본으로 제공하는 Provider샘플이다.
- 아래의 코드를 살펴보자.
- 이때 preAuthenticationChecks에서 해당 유저의 정보중 만료 여부, 비활성화 여부를 체크한 후
- additionalAuthenticationChecks를 통해 인증을 시작한다.
//유저 정보를 불러온다.
if (user == null) {
cacheWasUsed = false;
try {
user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
}
}
//user가 존재하는 경우 검증을 시작
try {
this.preAuthenticationChecks.check(user); //만료, 비활성화 여부 확인
additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
}
additionalAuthenticationChecks의 메서드 내용
- 메서드의 내용은 다음과 같다.
- getCredentials를 통해 입력한 비밀번호를 가져온다. (널체크를 우선 수행함)
- authentication.getCredentials().toString()을 통해 문자열로 변환한다.
- passwordEncoder.matches메서드를 통해 userDetails에 있는 비밀번호와 현재 입력한 비밀번호의 일치여부를 확인한다.
- 이 과정에서 userDetails.getPassword는 DB에서 가져오는 비밀번호다.
protected void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
//먼저 검증이 이루어진다.
if (authentication.getCredentials() == null) {
this.logger.debug("Failed to authenticate since no credentials provided");
throw new BadCredentialsException(this.messages
.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
//검증이 성공하면(null이 아니면) 비밀번호는 인증 객체로 변환된다.
String presentedPassword = authentication.getCredentials().toString();
//UserDetails의 비밀번호와 요청한 비밀번호가 일치하는지 확인한다.
if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
this.logger.debug("Failed to authenticate since password does not match stored value");
throw new BadCredentialsException(this.messages
.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
}
3. 해싱과 PasswordEncoder로 비밀번호가 인증되는 과정
- 유저가 Username과 Password를 입력한다.
- 이는 애플리케이션에서 Hashing 알고리즘에의해 암호화된다.
- 해당 Username의 입력한 HashValue와 DB에 있는 HashValue를 비교해서 일치하는지 여부를 파악하여 성공/실패를 반환한다.
- 더 자세히 말하면, 엔드 유저가 제공한 비밀번호와 loadUserByUsername()메서드를 통해 DB에서 가져온 비밀번호를 비교한다. 일반 텍스트 비교로 equals메서드가 수행된다.
4. PasswordEncoder의 메서드들
- Encoder 메서드
- 엔드 유저가 등록 절차에서 입력한 비밀번호를 개발자가 지정한 암호화방식에 따라 해시 문자열 또는 암호화된 값으로 변한다.
- matches 메서드
- 유저가 입력한 비밀번호와 DB에 이미 저장된 비밀번호를 비교하기 위해 사용
- 동일한 해싱 알고리즘을 사용해 먼저 rawPassword를 해싱하는 로직을 가진다.
- 그 다음 두 개의 문자열을 비교하고 동일한 해쉬값인지 판단한다.
- 해시값이 일치하지 않는다면 false로 로그인 작업 실패 반환
- upgradeEncoding
- 언제나 false로 반환되는 기본 로직
- 해커가 우리의 비밀번호를 해킹하고 복호화하는 과정을 어렵게 한다.
- 암호화를 2회 이상 수행할 수 있다. 암호화를 복잡하게 수행하고 싶을때 사용하는 메서드
package org.springframework.security.crypto.password;
public interface PasswordEncoder {
String encode(CharSequence rawPassword);
boolean matches(CharSequence rawPassword, String encodedPassword);
default boolean upgradeEncoding(String encodedPassword) {
return false;
}
}
5. PasswordEncoder의 다양한 Encoder들
- NoOpPasswrodEncoder : 암호화가 이뤄지지 않으므로 사용할 수 없음
- StandardPasswordEncoder
- 운영 앱에 추천하지 않는다.
- This PasswordEncoder is provided for legacy purposes only and is not considered secure. A standard PasswordEncoder implementation that uses SHA-256 hashing with 1024 iterations and a random 8-byte random salt value. It uses an additional system-wide secret value to provide additional protection. The digest algorithm is invoked on the concatenated bytes of the salt, secret and password.
- Pbkdf2PasswordEncoder
- 5~6년 전 쯤 개발된 어플에서나 사용하는 ENcoder
- 최근에는 CPU,GPU의 발전으로 더이상 안전하지 않다.
- 손쉽게 해시값에 무차별 대입 공격을 하고 일반 텍스트 비밀번호를 알아 낼 수 있다.
❗무차별 대입 공격이란?
- 사람들이 자주 사용하는 문구, 비밀번호를 무차별적으로 대입하는 프로그램을 사용하여 알아내는 시도
- BCryptPasswordEncoder
- BCrypt해싱 알고리즘을 사용한다.
- 주기적으로 업데이트 되고 있음
- CPU연산을 요청 ( 작업량, 라운드 수에 따라 CPU연산이 많아진다.)
- 연산을 요구
- 가장 많이 사용하는 암호 알고리즘(성능 문제 때문)
- ScrpytPasswordEncoder
- B의 고급버전
- 우리가 적용한 설정에 따라 고의적으로 일부 메모리 할당을 요구한다.
- Argon2PasswrodEncoder
- BCrypt, Scrypt에서 사용되는 연산 능력, 메모리
- 추가로 다중 스레드를 요구한다.
- 총 세가지 자원을 요구한다.
6. BCryptPasswordEncoder적용하기
- Configuration 주석이 달려있는 Config.java를 생성하여 아래와 같이 설정할 수 있다.
- 해당 Security는 이제 BCryptPasswordEncoder방식을 사용한다.
@Configuration
public class ProjectSecurityConfig {
@Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception { //거부패턴 : /myAccount/** 을 통해 아래 모든 경로도 지정 가능
http.csrf(httpSecurityCsrfConfigurer -> httpSecurityCsrfConfigurer.disable()).
authorizeHttpRequests((requests) ->
requests.requestMatchers("/myAccount/**","myBalance","/myLoans","myCards")
.authenticated()
.requestMatchers("/notices", "contact","/register")
.permitAll()
);
http.formLogin(withDefaults());
http.httpBasic(withDefaults());
return http.build();
}
//변경점 : NoOpPasswordEncoder -> BCryptPasswordEncoder
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
- Controller에서 회원가입하는 요청을 받았을때 코드다.
- 비밀번호 암호화를 수행하고 해당 유저의 정보를 저장한다.
- 만약 해당 유저의 Id가 0보다 큰 경우(존재하는 경우) 반환값으로 성공메세지를 body에 담고 HTTP상태 메세지를 CREATED로 반환한다.
- 아닌 경우에는 서버에러를 터트리며 예외 메세지를 출력한다.
@PostMapping("/register")
public ResponseEntity<String> registerUser(@RequestBody Customer customer) {
Customer savedCustomer = null;
ResponseEntity response = null;
try {
//비밀번호를 인코딩 후
String hashPwd = passwordEncoder.encode(customer.getPwd());
//setter를 통해 암호화된 비밀번호로 변환
customer.setPwd(hashPwd);
savedCustomer = customerRepository.save(customer);
if (savedCustomer.getId() > 0) {
response = ResponseEntity
.status(HttpStatus.CREATED)
.body("Given user details are successfully registred");
}
} catch (Exception exception) {
response = ResponseEntity
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("An exception occured due to" + exception.getMessage());
}
return response;
}
BcryptPasswordEncoder의 내부 코드
- 기본적으로 strength(라운드 횟수)는 -1로 지정되어 10라운드로 설정되어있다.
- version을 조절하여 해당 알고리즘을 커스텀 가능하다
- SecureRandom 을 통해 암호화한 비밀번호에 추가연산이 가능하다.
public BCryptPasswordEncoder(BCryptVersion version, int strength, SecureRandom random) {
if (strength != -1 && (strength < BCrypt.MIN_LOG_ROUNDS || strength > BCrypt.MAX_LOG_ROUNDS)) {
throw new IllegalArgumentException("Bad strength");
}
this.version = version;
this.strength = (strength == -1) ? 10 : strength;
this.random = random;
}
728x90
반응형
'Spring > Security6' 카테고리의 다른 글
[Java - Spring Security6] Authentication(인증)과 관련된 Provider와 manager (0) | 2024.03.05 |
---|---|
[Java - Spring Security6] Config파일 해석 및 직접 FilterChain만들기 (0) | 2024.03.02 |
[Java - Spring Security6] Spring Security를 사용하는 이유 및 아키텍처 (0) | 2024.02.25 |
자바[Java] - Security 소개 (1) | 2023.12.11 |