웹서비스 중 회원가입에서 비밀번호에 대한 값은 상당히 보안적으로 중요합니다.
사용자가 입력한 정보들 중 password를 그대로 데이터베이스에 입력하게 되면 보안적으로 상당히 위험합니다.
따라서 우리는 입력한 비밀번호를 hashing 해서 사용하게 되는데 저는 프로젝트를 진행하면서 BCrypt방식을 사용할 것입니다.
bcrypt는 비밀번호 해시함수로 Niels Provos와 David Mazieres에 의해 만들어졌으며 Blowfish라는 암호에 기반하였다. Bcrypt는 조정할 수 있는 해시 알고리즘을 써서 패스워드를 저장한다. Bcrypt는 패스워드를 해싱할 때 내부적으로 랜덤 한 솔트를 생성하기 때문에 같은 문자열에 대해서 다른 인코드 된 결과를 반환한다. 하지만 공통된 점은 매번 길이가 60인 String을 만든다.
BCrypt는 단방향성을 가지고 있는데 이말은 인코딩은 되었지만 디코딩을 할 수 없기에 복원이 불가능하다는 것이다.
여기서의 솔트라는 것은 해당 문자열을 hashing할때 랜덤 한 문자열을 붙여서 해싱한다는 것이다. 다만 이럴 경우 랜덤 한 솔트의 해싱 값과 비밀번호의 해싱 값 둘 다를 저장하여야 인증에 확인할 수 있다.
보통 비밀번호 해싱값과 같이 붙어있거나 따로 저장할 수 있다.
정리 사용자가 입력한 비밀번호를 디코딩할 수없게 해싱하고 거기에 랜덤 한 솔트를 쳐서 고유한 비밀번호를 인코딩한다. 따라서 DB 관리자 또한 비밀번호를 알 수없으며 해킹에 대한 어느 정도 방어를 할 수 있다.
Spring을 이용할시 spring security라는 라이브러리를 제공해주는데 인증과 인가 보안까지 spirng에서 간편하게 제공해 준다. 하지만 기본값을 사용할 경우 생기는 문제점도 존재하기 때문에 httpSecurity를 적용할 것이다.
회원 가입시 비밀번호를 입력받는 input창은 처음 입력한 비밀번호를 확인하는 input이 존재하는데 이러한 검사는 front에서 확인하는 방법도 있다.
먼저 Spring security를 적용하기 위해선 의존성을 추가해야 합니다.
빌드 툴로서 gradle을 이용하므로 org.springframework.boot:spring-boot-starter-security를 추가해줍니다.
의존성을 추가한 다음 서버를 실행시켜 확인을 해보면 우리가 생성하지도 않았던 로그인 페이지가 존재하는 것을 확인할 수 있습니다.
하지만 현재 API 서버를 만들기 때문에 로그인 페이지가 불필요합니다. 따라서 로그인 창을 제거할 것입니다.
config를 위한 클래스를 설정한 다음 기본적으로 구현되어있는 WebSecurityConfigurerAdapter 추상 클래스를 상속받을 것입니다.
이제 form을 disable()하게 되면 기본적인 form화면이 나오지 않습니다.
package kr.co.fastcampus.eatgo;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
@EnableWebSecurity
public class SecurityJavaConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin().disable();
}
}
보안적으로 중요한 csrf는 사용자가 자신의 의지와는 무관하게 공격자가 의도한 행위(수정, 삭제, 등록 등)를 특정 웹사이트에 요청하게 하는 공격을 말한다.
Spring security는 Synchronizer Token Pattern을 이용하여 이러한 공격을 방어하는데 간단하게 설명하자면 요청 변수인 csrf파라미터의 랜덤 한 토큰 값을 설정하여 요청 시 그 값을 이용해 동일한지 여부를 따져 처리는 하는 것입니다.
ex) localhost:8080/index? post=input&_csrf=<secure-random>
security 공식문서 중 일부분입니다.
When to use CSRF protection
When you use CSRF protection? Our recommendation is to use CSRF protection for any request that could be processed by a browser by normal users. If you are only creating a service that is used by non-browser clients, you will likely want to disable CSRF protection.CSRF with JSON using the following form
일반적으로 사용자가 요청하는 모든 것에 CSRF보호를 사용하는 것이 좋지만 현재 클라이언트 서비스만을 만들기 때문에 CSRF를 비활성화해야 합니다. GET 요청만 하는 경우 해당 페이지를 요청할 때마다 CSRF의 파라미터 값을 검증하기에
HTTP Status 403 - Expected CSRF token not found. Has your session expired?
에러가 발생할 수 있습니다.
h2-database를 이용하는 경우 iframe이 전부 거부되는 것을 확인할 수 있는데 i-frame도 보안적으로 중요한 이슈이기 때문에 spring security에서 기본적으로 거부를 하기 때문에 설정을 해주어야 합니다.
package kr.co.fastcampus.eatgo;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
@EnableWebSecurity
public class SecurityJavaConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors().disable()
.csrf().disable()
.formLogin().disable()
.headers().frameOptions().disable();
}
}
일단 기본적인 세팅을 구성했습니다.
이제 userService의 구성만 보면 아까 말했던 것처럼 BCryptPasswordEncoder()를 이용할 것입니다. 스프링 security가 구현해 놓았기에 간단히 이용할 수 있습니다.
package kr.co.fastcampus.eatgo.application;
import kr.co.fastcampus.eatgo.domain.User;
import kr.co.fastcampus.eatgo.domain.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import javax.transaction.Transactional;
@Service
@Transactional
public class UserService {
private UserRepository userRepository;
@Autowired
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User registerUser(String email, String name, String password) {
PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
String encodedPassword = passwordEncoder.encode(password);
User user = User.builder()
.email(email)
.name(name)
.password(encodedPassword)
.level(1L)
.build();
return userRepository.save(user);
}
}
이제 유저의 회원가입을 진행하면 h2-database에 다음과 같이 해싱된 값을 볼 수 있습니다.
'Spring|Spring-boot' 카테고리의 다른 글
JWT(2) (0) | 2020.08.18 |
---|---|
Json Web Tokens(1) (0) | 2020.08.14 |
Gradle 멀티 모듈 (0) | 2020.08.06 |
Lombok (0) | 2020.08.03 |
[Spring] Interceptor , ArgumentResolver (0) | 2020.07.20 |
댓글