티스토리 뷰

인프런 이도원님의 'Spring Cloud로 개발하는 마이크로서비스 애플리케이션(MSA)' 듣고 정리한 내용입니다.

 

Spring Cloud로 개발하는 마이크로서비스 애플리케이션(MSA) - 인프런 | 강의

Spring framework의 Spring Cloud 제품군을 이용하여 마이크로서비스 애플리케이션을 개발해 보는 과정입니다. Cloud Native Application으로써의 Spring Cloud를 어떻게 사용하는지, 구성을 어떻게 하는지에 대해

www.inflearn.com

 

1. Users Microservice 개요

Users Microservice 구성

API Gateway는 클라이언트로부터 사용자 요청 처리를 해주고 User Service는 Eureka 서비스에 등록이 되어 사용되는 마이크로서비스이다. 

 

Users Microservice의 비지니스 로직은 다음과 같다.

- 신규 회원 등록

- 회원 로그인

- 상세 정보 확인

- 회원 정보 수정/삭제

- 상품 주문

- 주문 내역 확인

 

Users MicroService의 API는 다음 표와 같고, 전달되는 데이터의 형식은 JSON 타입이다.

기능 URI (API Gateway 사용 ⭕️) URI (API Gateway 사용 ❌) HTTP Method
사용자 정보 등록 /user-service/users /users POST
전체 사용자 조회 /user-service/users /users GET
사용자 정보, 주문 내역 조회 /user-service/users/{user_id} /users/{user_id} GET
작동 상태 확인 /user-service/users/health_check /users/health_check GET
환영 메시지 /user-service/users/welcome /users/welcome GET

 

2. Users Microservice - 프로젝트 생성

 

UserServiceApplication.java

@SpringBootApplication
@EnableDiscoveryClient
public class UserServiceApplication {

    public static void main(String[] args) {
        SpringApplication.run(UserServiceApplication.class, args);
    }

}

@EnableDiscoveryClient 어노테이션은 Eureka 서버에 등록해주는 것이다.

 

application.yml

server:
  port: 0

spring:
  application:
    name: user-service

eureka:
  instance:
    instance-id: ${spring.application.name}:${spring.application.instance_id:${random.value}}
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://localhost:8761/eureka

포트번호를 0으로 설정하고 하나의 마이크로서비스로 여러개의 서버를 작동했을 때 구분하기 위해 instance의 id를 지정하였다.

 

UserController.java - 상태 체크할 수 있는 Controller

@RestController
@RequestMapping("/")
public class UserController {

    @GetMapping("/health-check")
    public String status() {
        return "It's Working in User Service";
    }
}

 

Eureka 서버에 올라간 것 확인!

 

health-check 확인

 

application.yml에 등록되어 있는 정보를 가져오는 방법에는 2가지가 있다.

 

application.yml

greeting:
  message: Welcome to the Simple E-commerce.

 

1. Environment 객체 사용

 

UserController.java

@RestController
@RequestMapping("/")
public class UserController {
   
    private Environment env;

    @Autowired
    public UserController(Environment env) {
        this.env = env;
    }

    @GetMapping("/welcome")
    public String welcome() {
        return env.getProperty("greeting.message");
    }
}

 

2. @Value 어노테이션 사용

 

Greeting.java

@Component
@Data
public class Greeting {

    @Value("${greeting.message}")
    private String message;

}

application.yml에서 데이터만 가져올 수 있는 별도의 Greeting이라는 객체를 만들어서 @Value 어노테이션으로 값을 가져오도록 설정하였다.

 

UserController.java

@RestController
@RequestMapping("/")
public class UserController {

    @Autowired
    private Greeting greeting;

    @GetMapping("/welcome")
    public String welcome() {
        return greeting.getMessage();
    }
}

 

welcome 확인

 

3. Users Microservice - DB

h2 데이터베이스를 연동하기 위해 pom.xml에 dependency를 등록해주고 application.yml에는 h2 사용을 위한 설정을 추가해준다.

 

Test Connection 시 나타나는 오류

먼저 데이터베이스를 만들어두지 않고 시작했기 때문이다. 버전을 바꿔주면 해결된다.

 

여기서 버전을 낮춰주면 바로 해결될 줄 알았는데... 버전을 낮추면 404에러가 나고 최신 버전을 사용하면 DB가 자동 생성이 되지 않고....

 

H2를 최신버전(현재는 2.1.214)으로 사용 시 오류 임시 해결 방법 - 인프런 | 고민있어요

안녕하세요.다름 아니라 Spring Boot가 3.XX 버전으로 업데이트가 되면서, 해당 버전을 사용하시는 분들은 1.3.176버전으로 H2 이용 시 404에러가 발생합니다.해당 에러를 해결하려면 최신 버전으로 H2

www.inflearn.com

이 방법을 사용했더니 해결되었다...!!!

 

4. Users Microservice - 회원가입

이제 회원가입 기능을 구현해보자. 

 

클라이언트가 회원가입을 하기 위해 이메일, 이름, 비밀번호를 입력할 것이다. 입력 받은 정보들을 requestUser라는 Dto 클래스를 만들어어서 Validation Check를 해주는데, null 값을 입력할 수 없다는 메세지와 최소 몇 글자 이상을 입력해야하는지 조건이 있다. 클라이언트가 회원가입 정보를 JSON 타입으로 전달하면 비지니스 로직에 의해 처리가 된다.

 

사용자로부터 데이터를 입력받을 때는 RequestDto

데이터가 내부적으로 처리되기 위해 전달될 때는 UserDto

데이터베이스와 매핑될 때는 UserEntity

 

JPA

JPA를 사용하기 위해 pom.xml에 dependency를 추가해준다. JPA를 사용하면 API에서 쿼리 없이 데이터를 다룰 수 있도록 도와준다. 객체를 생성하여 JPA에 전달하면 JPA가 자동으로 sql코드로 변환시켜서 데이터베이스에 저장하고 쿼리를 조회한다.

 

RequestUser.java - 제약조건 추가

@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 pwd;

    @NotNull(message = "Password cannot be null")
    @Size(min = 8, message = "Password must be equal or greater than 8 characters")
    private String name;
}

 

Validation 속성을 넣고 싶을 땐 @NotNull을 사용하면 된다. 추가적으로 message 값도 넣을 수 있다.

 

UserDto.java

@Data
public class UserDto {
    private String email;
    private String name;
    private String pwd;
    private String userId;
    private Date createdAt; // 회원가입 날짜

    private String encryptedPwd;
}

입력받은 RequestUser 정보를 저장하기 위한 용도로 Dto를 만들었다.

 

UserService.java

public interface UserService {

    UserDto createdUser(UserDto userDto);
}

 

UserServiceImpl.java

@Service
public class UserServiceImpl implements UserService {

    @Override
    public UserDto createdUser(UserDto userDto) {
        userDto.setUserId(UUID.randomUUID().toString());

        return null;
    }
}

userId는 자동으로 랜덤하게 생성된다.

 

UserEntity.java - 데이터베이스로 만들어지는 요소

@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;
}

 

UserRepository.java

public interface UserRepository extends CrudRepository<UserEntity, Long> {
}

기본적으로 저장하거나 삭제, 수정, 간단한 키 값을 이용하여 검색하는 메소드는 CrudRepostiory를 상속받아서 사용한다.

 

RequestUser → UserDto → UserEntity 데이터를 옮겨주는 작업이 필요하다. 객체에 있는 필드값들을 원하는 객체로 자동으로 매핑시켜주는 라이브러리인 ModelMapper를 사용할 것이다.

 

UserServiceImpl.java

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    UserRepository userRepository;

    @Override
    public UserDto createdUser(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"); // pwd를 암호화된 pwd로 바꿔주는 작업 (나중에)

        userRepository.save(userEntity);

        return null;
    }
}

먼저 UserService에서는 UserDto에 있는 데이터를 Entity에 매핑시킨 후 저장하였다.

 

UserController.java

@PostMapping("/users")
public ResponseEntity createdUser(@RequestBody RequestUser requestUser) {
	ModelMapper mapper = new ModelMapper();
	mapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);

	UserDto userDto = mapper.map(requestUser, UserDto.class);
	userService.createdUser(userDto);

	return "Create user method is called";
}

UserController에서는 RequestUser에 있는 입력 받은 데이터를 UserDto에 매핑시켰다.

포스트맨 실행 시 성공!
h2 DB 조회 시 저장된거 확인!

 

하지만, POST 방식으로 했을 때 Status 코드가 200번이 아닌 201번이 더 정확한 방법이다.

Controller에서 ResponseEntity로 반환하는 메서드를 수정하였다.

    @PostMapping("/users")
    public ResponseEntity createdUser(@RequestBody RequestUser requestUser) {
        ModelMapper mapper = new ModelMapper();
        mapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);

        UserDto userDto = mapper.map(requestUser, UserDto.class);
        userService.createdUser(userDto);

        return new ResponseEntity(HttpStatus.CREATED);
    }

Status 201 확인!

 

그렇다면 상태코드 값 201번과 가입한 회원정보를 같이 보여주려면 어떻게 해야 할까!

 

ResponseUser.java - 사용자에게 반환할 객체를 추가

@Data
public class ResponseUser {

    private String email;
    private String name;
    private String userId;
}

 

UserController.java

@PostMapping("/users")
public ResponseEntity<ResponseUser> createdUser(@RequestBody RequestUser requestUser) { // <>안에 전환시킬 것을 명시 = 정확하게 전달하고자 하는 데이터 타입 넣기!
    ModelMapper mapper = new ModelMapper();
    mapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);

    UserDto userDto = mapper.map(requestUser, UserDto.class);
    userService.createdUser(userDto);

    ResponseUser responseUser = mapper.map(userDto, ResponseUser.class);

    return ResponseEntity.status(HttpStatus.CREATED).body(responseUser);
}

UserController에서 Status 값과 body에 ResponseUser를 같이 반환한다. ResponseUser에 데이터를 받아서 보여주기 위해 ModelMapper를 이용하여 UserDto에 있는 데이터를 ResponseUser로 매핑시켜준다.

 

UserServiceImpl.java

@Override
public UserDto createdUser(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");

	userRepository.save(userEntity);

	UserDto returnUserDto = mapper.map(userEntity, UserDto.class);

	return returnUserDto;
}

UserService에서는 DB에 저장된 데이터를 가져와서 UserDto로 매핑 시킨 후 이메일, 이름, 아이디 값이 담긴 객체를 반환하도록 수정하였다.

포스트맨 실행 시 성공!

 

5. Users Microservice - Security

 

Spring Security란?

Authentication (인증) + Authorization (인가)

 

처리 절차

(1) spring security jar을 dependency에 추가

(2) WebSecrutityConfigurerAdapter를 상속받는 Security Configuration 클래스 생성

(3) Security Configuration 클래스에 @EnableWebSecurity 추가

(4) configure(AuthenticationManagerBuilder auth) 메서드 재정의 → 인증을 위한 메서드

(5) BCryptPasswordEncoder 빈 정의 → 비밀번호 암호화

(6) configure(HttpSecurity http) 메서드 재정의 → 인가를 위한 메서드

 

pom.xml - dependency 추가

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-security</artifactId>
</dependency>

 

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());
        // 인증 작업 없이 사용할 수 있는 범위를 지정
        return http.build();
    }
}

 

WebSecurityConfigurerAdapter는 현재 deprecated 되어서 SecurityFilterChain을 Bean으로 등록하는 방법을 사용하였다.

 

UserServiceImpl.java

@Service
public class UserServiceImpl implements UserService {

    UserRepository userRepository;
    BCryptPasswordEncoder passwordEncoder; // 비밀번호를 암호화를 위한 변수

    @Autowired
    public UserServiceImpl(UserRepository userRepository, BCryptPasswordEncoder passwordEncoder) {
        // 빨간 밑줄 생기는 이유? 빈을 주입한 적이 없어서 -> 스프링 어플리케이션이 기동하는 클래스에 빈을 넣어두면 됨!
        this.userRepository = userRepository;
        this.passwordEncoder = passwordEncoder;
    }
    
    @Override
    public UserDto createdUser(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(passwordEncoder.encode(userDto.getPwd()));

        userRepository.save(userEntity);

        UserDto returnUserDto = mapper.map(userEntity, UserDto.class);

        return returnUserDto;
    }
 }

비밀번호를 암호화하기 위해 BCryptPasswordEncoder를 사용하였다.

 

BCryptPasswordEncoder란?

스프링 시큐리티에서 제공하는 클래스 중 하나로 비밀번호를 암호화하는 데 사용할 수 있는 메서드를 가진 클래스이다.

비밀번호를 암호화해주는 메서드와 사용자에 의해 제출된 비밀번호와 저장되어 있는 비밀번호의 일치 여부를 확인해주는 메서드를 제공한다.

포스트맨으로 데이터 보내주고
h2 DB에서 암호화된 비밀번호 확인!

 

비밀번호"만" 같은 데이터를 하나 더 추가해보자.

분명 pwd에 같은 값을 넣었는데 암호화된 비밀번호는 다르다는 것을 볼 수 있다.

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/04   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30
글 보관함