import lombok.Data;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
@Data
public class RequestUser {
@NotNull(message = "Email은 Null 일수 없습니다.")
@Size(min = 2, message = "Email은 2글자 이상입니다.")
@Email
private String email;
@NotNull(message = "name은 Null 일수 없습니다.")
@Size(min = 2, message = "name은 2글자 이상입니다.")
private String name;
@NotNull(message = "pw은 Null 일수 없습니다.")
@Size(min = 8, message = "name은 8글자 이상입니다.")
private String pw;
}
데이터를 받아 객체로 만들기 위한 vo class를 정의해준다.
@Data 로 모두 생성하는 것 보다 @Getter로 생성 후 build 패턴이나 생성자 패턴으로 구현하는게 더 유지보수에 좋지만 강의 내용 자체가 msa 설계방식으로 api를 만드는게 우선이니 이렇게 간단하게 구현한다!
import lombok.Data;
import java.util.Date;
@Data
public class UserDto {
private String email;
private String name;
private String pw;
private String userId;
private Date createAt;
private String encryptedPw;
}
실제로 사용자의 데이터는 vo에 맞게 보내오지만 우리는 db에 맞게 변경해서 내용을 저장해야하기 때문에 dto를 생성하여 중간에서 데이터를 가공해줘야한다.
import com.example.userservice.dto.UserDto;
public interface UserService {
UserDto createUser(UserDto userDto);
}
interface를 먼저 생성하고
import com.example.userservice.dto.UserDto;
import org.springframework.stereotype.Service;
import java.util.UUID;
@Service
public class UserServiceImpl implements UserService{
@Override
public UserDto createUser(UserDto userDto) {
userDto.setUserId(UUID.randomUUID().toString());
return null;
}
}
service에서 구현한다. 실제 이렇게 구현해도 되고 자신의 프로젝트에서 약속한 패턴이 있다면 그대로 개발해도 된다.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
pom에 다음과 같이 jpa를 추가해준다.
그 후 도메인을 추가해주어야 하는데
import lombok.Data;
import javax.persistence.*;
@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, length = 50, unique = true)
private String userId;
@Column(nullable = false, length = 50, unique = true)
private String encryptedPw;
}
jpa에서 사용할 entity를 정의해준다.
그리고 다음과 같이 UserRepository interface를 생성해주는데 직접 Repository 부분을 정의해서 만들어도 되고 제공해주는 crud로 interface만 선언해서 가져다 써도 된다.
import com.example.userservice.domain.UserEntity;
import org.springframework.data.repository.CrudRepository;
public interface UserRepository extends CrudRepository<UserEntity, Long> {
}
강의 내용을 그대로 따라가기 위해 interface로 구성하겠다.
그리고 위에서 구현하다가 말았던 UserServiceImpl을 구현할건데
@Service
@RequiredArgsConstructor
public class UserServiceImpl implements UserService{
private final UserRepository userRepository;
@Override
public UserDto createUser(UserDto userDto) {
userDto.setUserId(UUID.randomUUID().toString());
UserEntity userEntity = new UserEntity();
userEntity.setUserId(userDto.getUserId());
userEntity.setEmail(userDto.getEmail());
userEntity.setName(userDto.getName());
userEntity.setEncryptedPw("encrypted_password");
userRepository.save(userEntity);
return null;
}
}
다음과 같이 작성해주면 된다. 강의에서는 ModelMapper를 pom에 추가해서 사용하는데 지금 위에서 내가 한 로직처럼 Dto를 Entity로 변경해주는 작업을 실행해준다. 하지만 난 라이브러리를 사용하기보다 로직을 이해하는게 더 좋은 공부라고 생각해서 직접 set으로 세팅해줬다. 또한 이 코드도 좋은 코드는 아니기 때문에 domain(=entity) 자체에 method를 추가하여 dto 객체만 받아서 method 내에서 세팅해주거나 생성자 패턴으로 dto를 받아서 반환해준다거나 아니면 builder 패턴으로 생성해주는게 더 좋을거 같다!
마지막으로 controller에 /users를 post로 추가하여 실제로 서버를 실행해보자.
import com.example.userservice.dto.UserDto;
import com.example.userservice.service.UserService;
import com.example.userservice.vo.RequestUser;
import org.springframework.core.env.Environment;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/")
public class UserController {
private Environment env;
private UserService userService;
public UserController(Environment env, UserService userService) {
this.env = env;
this.userService = userService;
}
@GetMapping("health_check")
public String status(){
return "Ok";
}
@GetMapping("welcome")
public String welcome(){
return env.getProperty("greeting.msg");
}
@PostMapping("/users")
public String createUser(@RequestBody RequestUser user){
UserDto userDto = new UserDto();
userDto.setEmail(user.getEmail());
userDto.setName(user.getName());
userDto.setPw(user.getPw());
userService.createUser(userDto);
return "success create user";
}
}
실제로 유레카에서 확인이 된다면 포스트맨으로 확인해보자!
Post 잊지말고 Post로 유레카에서 확인할 수 있는 port번호로 users로 해당 내용을 전송하면 다음과 같이 결과값을 얻을 수 있다!
하지만 실제 내장 h2 db로 가면 저장된 내용은 확인할 수 없는데 yml에 우리가 driver에 대해 설정을 해주지 않았기 때문이다...ㅎ
spring:
application:
name: user-service
h2:
console:
enabled: true
settings:
web-allow-others: true #외부 접속 허용
path: /h2-console
datasource:
driver-class-name: org.h2.Driver
url: jdbc:h2:mem:testdb
username: sa
jpa:
database-platform: org.hibernate.dialect.H2Dialect
hibernate:
ddl-auto: update
show-sql: true
spring h2 아래에 datasource 부분을 수정해주면 된다. 나는 jpa의 sql값을 보고 싶어서 다음과 같이 jpa도 추가해줬다. ddl-auto의 경우 create이나 update로 해서 테스트 동안은 구조가 변경되어도 변경될 수 있도록 해주자.
우리가 지금처럼 String으로 Ok를 return할수도 있지만 우리는 Http를 통해서 통신하는 웹 개발자들이니까 Http 상태코드로 반환해주는 방법이 더 좋다. 또한 Post로 요청하여 성공적으로 생성을 했을 경우 통신 ok의 200보다는 create ok의 201로 반환해주는 것이 더 좋기도 하다. 그래서 우리는 아까 controller의 코드를 수정하려고 한다.
@PostMapping("/users")
public ResponseEntity createUser(@RequestBody RequestUser user){
UserDto userDto = new UserDto();
userDto.setEmail(user.getEmail());
userDto.setName(user.getName());
userDto.setPw(user.getPw());
userService.createUser(userDto);
return new ResponseEntity(HttpStatus.CREATED);
}
다음과 같이 수정해주고 실제 api를 만들때는 반환되는 스펙이 항상 동일해야하기 때문에 상태코드 뿐만 아니라 공통의 api 객체를 반환한다. ResponEntity를 추가하면서 봤겠지만 제네릭을 지원하고 있기 때문에 상태코드와 공통 api 객체로 반환이 가능하다.
결과도 다음과 같이 확인이 가능하다.
라고 위에 적었는데 강사님께서 바로 보여주셨다.
import lombok.Data;
@Data
public class ResponseUser {
private String email;
private String name;
private String userId;
}
vo에 ResponseUser라는 class를 만들고
@PostMapping("/users")
public ResponseEntity<ResponseUser> createUser(@RequestBody RequestUser user){
UserDto userDto = new UserDto();
userDto.setEmail(user.getEmail());
userDto.setName(user.getName());
userDto.setPw(user.getPw());
userService.createUser(userDto);
ResponseUser responseUser = new ResponseUser();
responseUser.setUserId(userDto.getUserId());
responseUser.setEmail(userDto.getEmail());
responseUser.setName(userDto.getName());
//return ResponseEntity.status(HttpStatus.CREATED).body(responseUser);
return new ResponseEntity(responseUser, HttpStatus.CREATED);
}
다음과 같이 수정해주면 된다. return하는 방법은 위의 방법도 되고 아래 방법도 되어서 다음과 같이 처리해놨다.
그리고 결과를 확인하면 다음과 같이 return되어지는 json 데이터와 status를 확인할 수 있다.
비밀번호를 암호화하기 위해서 Security를 추가하여 Security의 비밀번호 암호화를 사용하려고 한다. security는 인증과 권한을 처리해주는 library이다.
pom 추가
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
security를 추가해준다.
SecurityConfiguration 클래스 생성
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 WebSecurity extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable(); //csrf는 사용하지 않음
http.authorizeRequests().antMatchers("/users/**").permitAll(); //user url로 들어오는 작업만 통과시켜줌
http.headers().frameOptions().disable(); //추가하지 않으면 h2 내장 db를 사용할 수 없음.
}
}
security의 권한 설정 작업을 위해 다음과 같이 설정해준다. class는 annotation으로 spring에서 관리하기 때문에 자신이 원하는 패키지에 넣거나 혹은 그냥 프로젝트 자체에 생성해도 된다.
3.암호화를 위한 BCryptPasswordEncoder 빈 정의
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder(){
return new BCryptPasswordEncoder();
}
기존의 service에서 암호화 하지 않고 넘기던 부분을 암호화하여 사용하면 된다.
userEntity.setEncryptedPw(passwordEncoder.encode(userDto.getPw()));