👽 명세서 작성
Features
- 신규 회원 등록
- 회원 로그인
- 상세 정보 확인
- 회원 정보 수정 / 삭제
- 상품 주문
- 주문 내역 확인
API 명세서
라이브러리 등록
- Lombok
- H2
- Spring Boot DevTool - 굳이 웹을 종료하지 않아도 reload 해주는 기능이 포함되어 있다.
- Spring Web
- Eureka Discovery Client
- JPA
- Model Mapper
👽 기본 설정 코드 작성
1. UserServiceApplication
@SpringBootApplication
@EnableDiscoveryClient
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
}
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
- 어노테이션을 통해 클라이언트로 등록합니다
2. Application.yml
server:
port: 0
spring:
application:
name: user-service
h2:
console:
enabled: true
settings:
web-allow-others: true
path: /h2-console
jpa:
hibernate:
ddl-auto: create-drop
show-sql: true
datasource:
url: jdbc:h2:mem:testdb
username: sa
eureka:
instance:
instance-id: ${spring.cloud.client.hostname}:${spring.application.instance_id:${random.value}}
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://127.0.0.1:8761/eureka
greeting:
message: Welcome to the Simple E-commerce.
- 랜덤 포트로 생성하기 위해 포트 번호를 0으로 해주고 랜덤한 포트 번호를 표시해주기 위해 instance를 활용했습니다.
※ H2 환경 설정에 버전으로 인해 테이블이 자동생성되는 것이 막혔습니다. 때문에 임의로 아무런 기능이 없는 테이블로 만들어진 객체를 하나 생성하면 제대로 설정이 되는 것을 확인 할 수 있습니다.
3. UserContoller
@RestController
@RequestMapping("/user-service")
public class UserController {
private Environment env;
private UserService userService;
@Autowired
private Greeting greeting;
@Autowired
public UserController(Environment env, UserService userService) {
this.env = env;
this.userService = userService;
}
@GetMapping("/health_check")
public String status() {
return "It's Working in User Service on POST %s"
.formatted(env.getProperty("local.server.port"));
}
@GetMapping("/welcome")
public String welcome() {
// return env.getProperty("greeting.welcome");
return greeting.getMessage();
}
- 설정된 정보를 사용하는 것의 방법은 2가지가 존재합니다
- Enviroment 를 등록
- @Value(${greeting.message}) 방식으로 설정 정보를 가져올 수 있습니다.
👽 회원 가입 기능 제작 ( 신규 회원 등록 )
● 전달 객체 (Request → Dto → Entity → Dto → Response)
- 전달하고자 하는 객체는 Request 객체로 데이터를 받고 Dto로 Service 의 계층으로 전달한다. 그리고 Service 계층 내부에서 Dto를 Entity로 전환하고 데이터를 제작하고 Dto 로 반환한다. 그리고 Controller 단에서 Response 객체로 전환해서 클라이언트에게 전달하는 것을 볼 수 있다.
1. RequestUser
@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 = "pwd cannot be null")
@Size(min = 8, message = "password must be equal or grater than 8 characters")
private String pwd;
}
- 어노테이션을 통해서 들어오는 객체의 조건을 넣어서 한번 검사한다.
2. UserDto
@Data
public class UserDto {
private String email;
private String name;
private String pwd;
private String userId;
private Date createdAt;
private String encryptedPwd;
}
- 전달하고자하는 변수들을 담고 있다. createdAt은 생성 날짜이고 필요시 사용할 수 있다.
3. UserEntity
@Entity
@Data
@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;
}
- Entity 객체이므로 데이터 베이스와 가장 밀접한 관련이 있다. 때문에 JPA의 설정 어노테이션이 적히는 곳이다.
4. ResponseUser
@Data
public class ResponseUser {
private String email;
private String name;
private String userId;
}
- 클라이언트에게 전달할 내용을 담은 객체이다. 데이터를 전달할 내용만 담아 전달할 수 있다
● 서비스 객체 (Service → ServiceImpl)
1. UserService ( interface )
public interface UserService {
UserDto create(UserDto userDto);
}
- 제작해야 하는 서비스를 메소드만 제작하여 interface 형식으로 두었다. OCP 원칙을 지키기 위해서이다.
2. UserServiceImpl ( 구현체 )
@Service
@RequiredArgsConstructor
public class UserServiceImpl implements UserService {
private final UserRepository userRepository;
@Override
public UserDto create(UserDto userDto) {
userDto.setUserId(UUID.randomUUID().toString());
ModelMapper mapper = new ModelMapper();
mapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);
UserEntity userEntity = mapper.map(userDto, UserEntity.class);
userEntity.setEncryptedPwd("encrypted_password");
UserDto returnUserDto = mapper.map(userEntity, UserDto.class);
return returnUserDto;
}
}
- 의존성은 어노테이션을 통해 생성자를 통한 의존성 주입을 완료
- 각 객체의 변환시 사용한 ModelMapper 는 getConfiguration을 통해 서로 변환하려는 변수를 엄격하게 설정하겠다는 뜻이 됩니다.
● 컨트롤 객체
@RestController
@RequestMapping("/")
@RequiredArgsConstructor
public class UserController {
private final Environment env;
private final UserService userService;
...
@PostMapping("/users")
public ResponseEntity<ResponseUser> createUser(@RequestBody RequestUser user) {
ModelMapper mapper = new ModelMapper();
mapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);
UserDto userDto = mapper.map(user, UserDto.class);
userService.create(userDto);
ResponseUser responseUser = mapper.map(userDto, ResponseUser.class);
return ResponseEntity.status(HttpStatus.CREATED).body(responseUser);
}
}
- 기존의 check 하는 메소드를 제외하고 요청에 의한 직접적인 회원 등록 서비스를 보았다.
- Controller 에서 Requset 객체와 Response 객체를 다루고 있는 것을 볼 수 있다.
- 또한 반환타입을 ResponseEntity로 만들어서 status 로 반환 상태를 다루고 그안에 담을 body 내용을 꾸밀 수 있다.
● 결과
- 보안 정책 상으로 아무 걸리는 것 없이 사용을 했기 때문에 회원 등록의 결과를 바로 볼 수 있다. 정상적으로 201 Created (등록 완료) 상태를 전달받으며 body에는 전달하고자 하는 내용을 받을 수 있다.
👽 Security 등록
기존의 내용은 Security가 존재하지 않았기 때문에 그냥 들어올 수 있었지만 Security를 등록하게 되면서 보안이 생기고 해당 서비스를 사용하기 위한 요청에 대한 설정을 다시 해주어야 한다.
- Dependency 에 Security를 등록한다.
- @Bean을 통해 설정하고 반환타입을 SecurityFilterChain으로 정하고 등록한다
- Bcypt를 통해 암호화를 진행하기 위해 빈으로 등록
● Security 정의
@Configuration // 다른 빈보다 등록 우선순위가 높아진다.
@EnableWebSecurity
public class WebSecurity {
private static final String[] WHITE_LIST = {
"/users/**",
"/**"
};
@Bean
protected SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.csrf(AbstractHttpConfigurer::disable)
.headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin))
.authorizeHttpRequests(
authorize -> authorize
.requestMatchers(WHITE_LIST).permitAll()
.requestMatchers(PathRequest.toH2Console()).permitAll()
)
.build();
}
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
- csrf 를 사용하지 않기때문에 disable을 통해 꺼준다
- H2는 각 iframe 으로 묶여있기 때문에 각 프레임마다 인증을 요구한다. 때문에 한개의 프레임으로 보기 위한 옵션을 달았다.
- 또한 요청시 permitAll을 통해 인증을 하지 않고 지나가게 하기 위한 URI List를 만들어 등록한다.
- H2 관련 데이터 베이스 화면을 그냥 통과하는 requestMatchers 를 사용했다.
● Security 등록으로 인한 UserServiceImpl 의 변동
@Override
public UserDto createUser(UserDto userDto) {
userDto.setUserId(UUID.randomUUID().toString());
ModelMapper mapper = new ModelMapper();
// mapper 의 환경 설정정보 설정 ( 정확히 맞아야 한다 라는 뜻 )
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;
}
- 차이는 passwordEncoder 즉, Bycrypt 를 통해 사용자의 비밀번호를 암호화 해주는 과정이 추가로 생겼다.
👽 느낀점
- 추후에 ModelMapper 대신 mapStruct를 사용해서 제작해봐야겠다
- JWT 가 들어오면 JWT 토큰을 인증하는 필터를 어디에 등록하는지 고민해볼 수 있다.
- 그리고 Security 설정을 모두 통과로 만들면서 추후 인증의 역할을 하게 될 때 로직을 생각해본다.
'MSA > MSA 강좌 - 이도원 강사님' 카테고리의 다른 글
👨👧👦6. Spring Cloud Config (0) | 2024.04.01 |
---|---|
👨👧👦5. User Microservice 와 API Gateway - Security와 Filter 적용 (0) | 2024.03.30 |
👨👧👦 4. User & Catalogs & Orders Microservice (0) | 2024.03.29 |
👨👧👦 2. API Gateway Service (feat. Spring Cloud gateway) (0) | 2024.03.27 |
👨👧👦 1. ServiceDiscovery 등록 ( feat. Spring Cloud Netflix Eureka ) (1) | 2024.03.26 |