Spring Cloud로 개발하는 마이크로서비스 애플리케이션(MSA)
Users Microservice - JPA①
build.gradle_validation dependency 추가
implementation 'org.springframework.boot:spring-boot-starter-validation'
VO 생성
@Data
public class RequestUser {
@NotNull(message = "Email cannot be null")
@Size(min = 2, message = "Email not be less than two characters")
@Email
private String email;
@NotNull(message = "Name cannot be null")
@Size(min = 2, message = "Name not be less than two characters")
private String name;
@NotNull(message = "Password cannot be null")
@Size(min = 8, message = "Password must not be equal or grater than 8 characters")
private String pwd;
}
RequestUser vo를 DB에 저장하거나 다른 쪽으로 이동하려면 변환 작업이 추가로 필요하다.
DTO 생성
@Data
public class UserDto {
private String email;
private String name;
private String pwd;
private String userId;
private Date createAt;
private String encryptedPwd;
}
Service 생성
interface와 그 interface를 구현하는 java 클래스 생성
public interface UserService {
UserDto createUser(UserDto userDto);
}
@Service
public class UserServiceImpl implements UserService{
@Override
public UserDto createUser(UserDto userDto) {
userDto.setUserId(UUID.randomUUID().toString());
return null;
}
}
Entity 생성
@Data
@Entity
@Table(name = "users") // 테이블 이름 지정. 이 어노테이션이 없으면 클래스 이름으로 테이블이 생성됨
public class UserEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 50, unique = true)
private String email;
@Column(nullable = false, length = 50)
private String name;
@Column(nullable = false, unique = true)
private String userId;
@Column(nullable = false, unique = true)
private String encryptedPwd;
}
JPA repository 생성
// CrudRepository<연동 될 entity, entity의 기본키 타입>
public interface UserRepository extends CrudRepository<UserEntity, Long> {
}
Service 비즈니스 로직 추가
modelmapper dependency 추가
implementation 'org.modelmapper:modelmapper:2.4.2'
service createUser()
@Autowired
UserRepository userRepository;
@Override
public UserDto createUser(UserDto userDto) {
userDto.setUserId(UUID.randomUUID().toString());
//UserDto -> UserEntity 변환 작업
ModelMapper mapper = new ModelMapper();
mapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);
UserEntity userEntity = mapper.map(userDto, UserEntity.class);
userEntity.setEncryptedPwd("encrypted_password");
userRepository.save(userEntity);
return null;
}
ModelMapper 라이브러리를 이용해서 간단하게 UserDto를 UserEntity로 변환한다.
mapper를 사용하기 전에 mapper에 대한 설정을 해야하는데 그 부분은 getConfiguration() 코드로 설정한다. 위 코드는 매칭 전략과 관련한 설정인데, 완벽한 매칭이 되지 않으면(STRICT) 매핑하지 않는다는 설정이다.
직접적으로 UserDto가 UserEntity로 변환되는 코드는 mapper.map(userDto, UserEntity.class)이다.
암호화된 비밀번호는 아직 관련 기능이 추가되지 않았으므로 기본 값을 저장해놓고 save를 한다.
Controller createUser() 추가
// ... 생략
private UserService userService;
@Autowired
public UserController(Environment env, Greeting greeting, UserService userService){
this.env = env;
this.greeting = greeting;
this.userService = userService;
}
// ... 생략
@PostMapping("/users")
public String createUser(@RequestBody RequestUser user) {
// RequestUser -> UserDto 변환
ModelMapper mapper = new ModelMapper();
mapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);
UserDto userDto = mapper.map(user, UserDto.class);
userService.createUser(userDto);
return "Create user method is called";
}
컨트롤러에서는 RequestUser 객체를 받고, 서비스 단에는 RequestUser 객체를 UserDto로 전달해줘야한다. 그렇기 때문에 컨트롤러에서도 매핑 작업을 한 번 해줘야한다.
서버 실행(혹은 재실행)
유레카 대시보드에서 링크를 클릭 후 포트 번호를 확인한다. POST의 경우 웹 브라우저에서는 프론트 단이 없으면 테스트할 수 없기 때문에 포스트맨에서 확인한다.
200 OK가 떨어졌으면 h2 데이터베이스도 확인해보자.
포스트맨을 보면 요청의 결과 상태 코드를 '200 OK'를 받았는데 POST Method의 경우 응답 상태 코드를 200이 아닌 '201 Created'로 받아야 조금 더 정확하고 명확하다.
Controller createUser() 수정
@PostMapping("/users")
public ResponseEntity createUser(@RequestBody RequestUser user) {
// RequestUser -> UserDto 변환
ModelMapper mapper = new ModelMapper();
mapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);
UserDto userDto = mapper.map(user, UserDto.class);
userService.createUser(userDto);
return new ResponseEntity(HttpStatus.CREATED);
}
서버 재실행 후 포스트맨에서 상태 코드를 확인한다.
하나만 더 추가하자. 사용자 추가가 성공적으로 끝났을 때, 사용자에게 이메일과 이름, 그리고 새롭게 자동으로 부여되는 userId를 보여주자.
ResponseUser VO 생성
@Data
public class ResponseUser {
private String email;
private String name;
private String userId;
}
Controller createUser() 수정
@PostMapping("/users")
public ResponseEntity createUser(@RequestBody RequestUser user) {
// RequestUser -> UserDto 변환
ModelMapper mapper = new ModelMapper();
mapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);
UserDto userDto = mapper.map(user, UserDto.class);
userService.createUser(userDto);
ResponseUser responseUser = mapper.map(userDto, ResponseUser.class);
return ResponseEntity.status(HttpStatus.CREATED).body(responseUser);
}
service createUser() 수정
@Override
public UserDto createUser(UserDto userDto) {
userDto.setUserId(UUID.randomUUID().toString());
//UserDto -> UserEntity 변환 작업
ModelMapper mapper = new ModelMapper();
mapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);
UserEntity userEntity = mapper.map(userDto, UserEntity.class);
userEntity.setEncryptedPwd("encrypted_password");
userRepository.save(userEntity);
UserDto returnUserDto = mapper.map(userEntity, UserDto.class);
return returnUserDto;
}
UserDto가 반환 타입이므로 UserEntity를 UserDto로 다시 변환해 컨트롤러에 그 객체를 return한다.
서버 재실행 후 확인한다.
Users Microservice - Spring Security 연동
Spring Security는 Authentication(인증)과 Authorization(권한)과 관련된 작업을 처리할 수 있도록 해준다.
- build.gradle dependency 추가
implementation 'org.springframework.boot:spring-boot-starter-security'
- WebSecurity.java 생성
@Configuration
@EnableWebSecurity
public class WebSecurity {
private static final String[] WHITE_LIST = {
"/users/**",
"/**"
};
@Bean
protected SecurityFilterChain config(HttpSecurity http) throws Exception {
http.csrf().disable();
http.headers().frameOptions().disable();
http.authorizeHttpRequests(authorize -> authorize
.requestMatchers(WHITE_LIST).permitAll()
.requestMatchers(PathRequest.toH2Console()).permitAll());
return http.build();
}
}
강의와 스프링부트 버전이 달라 코드가 많이 다르다. spring boot 3.0부터는 WebSecurityConfigurerAdapter 클래스가 deprecated 되어 3.0에서는 사용하지 못한다. 대신 SecurityFilterChain을 반환하고 직접 Bean으로 등록해서 사용한다.
서버 재실행 후 포스트맨 및 DB 확인
Password 암호화
- BCryptPasswordEncoder 사용
- 패스워드를 해싱하기 위해 Bcrypt 알고리즘 사용
- 랜덤 Salt를 부여하여 여러 번 hash를 적용한 암호화 방식
service createUser() 수정
기존 UserRepository를 필드 주입 방식으로 주입했는데 생성자 주입 방식으로 변경하고, BCryptPasswordEncoder 객체도 마찬가지로 생성자 주입 방식으로 의존성을 주입한다.
UserRepository userRepository;
BCryptPasswordEncoder passwordEncoder;
@Autowired
public UserServiceImpl(UserRepository userRepository, BCryptPasswordEncoder passwordEncoder) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
}
그런데 생성자의 passwordEncoder의 매개변수에서 계속 빨간줄(에러)이 뜬다. 에러 메시지는 'Could not autowire. No beans of 'BCryptPasswordEncoder' type found. '인데 autowire를 사용할 수 없고, BCryptPasswordEncoder라는 bean을 찾을 수 없다는 뜻이다. Bean을 추가해주기 위해 애플리케이션 클래스에 코드를 추가한다.
application class
@SpringBootApplication
@EnableDiscoveryClient
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
}
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
service createUser() 암호화 적용
@Override
public UserDto createUser(UserDto userDto) {
userDto.setUserId(UUID.randomUUID().toString());
//UserDto -> UserEntity 변환 작업
ModelMapper mapper = new ModelMapper();
mapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);
UserEntity userEntity = mapper.map(userDto, UserEntity.class);
userEntity.setEncryptedPwd(passwordEncoder.encode(userDto.getPwd())); // **
userRepository.save(userEntity);
UserDto returnUserDto = mapper.map(userEntity, UserDto.class);
return returnUserDto;
}
서버 재실행 후 포스트맨, DB 확인
- 출처 : 인프런 Spring Cloud로 개발하는 마이크로서비스 애플리케이션(MSA) 강의