[E-commerce App] Users Microservice
개요
user-microservice
는 UI는 없이 REST API 형식으로 구현할 것이다.
그리고 위 사진에서 보이는 기능들을 개발할 것이다.
APIs
API Gateway 사용 시에는 uri에 /user-service
라는 prefix가 붙어야하며, 모든 마이크로 서비스에 대해 같은 포트(ex.8000
)로 호출할 수 있다.
미사용 시 prefix 없이 호출하고자하는 end point만 입력하면 되고, 해당하는 마이크로 서비스의 포트(ex.8081
)로 호출해야 한다.
Users Microservice - 기본적인 사용자 가입과 전체 회원 목록 조회
1. 프로젝트 생성
- Dependencies
- DevTools, Lombok, Spring Web, Eureka Discovery Client
실행
서비스 디스커버리(= 유레카 서버) 실행
인텔리제이에서 실행하는 것 자체가 부하가 크기 때문에 터미널로 실행하자.
먼저, 터미널 상에서 유레카 서버가 있는 곳으로 이동하자.
$ cd service-discovery
$ mvn spring-boot:run # 유레카 서버 실행
user-service
실행
터미널을 이용해 유레카 서버를 먼저 실행한 뒤,
인텔리제이에서 user-service
를 실행한다.
그 다음, http://localhost:8761
에 접속하면 아래와 같이 서비스 디스커버리에 user-service
가 등록된 것을 확인할 수 있다.
해당 url을 클릭하여 포트번호를 확인한 뒤, end point를 /health-check
로 바꾼다.
참고로, 마이크로 서비스를 재시동 할 때 마다 포트번호는 바뀐다.
2. welcome()
메서드 생성
이번에는 화면에 간단한 인사말을 띄워볼 것이다.
application.yml
에 인사말을 정의할건데, 이를 가져오는 방법에는 두 가지가 있다.
우선 application.yml
의 맨 아래에 아래 코드를 추가하자.
greeting:
message: Welcome to the Simple E-commerce.
방법 1. Environment 이용
controller/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
이용
vo/Greeting.java
@Component
@Data
public class Greeting {
@Value("${greeting.message}")
private String message;
}
controller/UserController.java
@RestController
@RequestMapping("/")
public class UserController {
@Autowired // 이처럼 직접 주입 받는 것은 권장하지 않는다.
private Greeting greeting;
@GetMapping("welcome")
public String welcome() {
return greeting.getMessage();
}
}
3. H2 Database 연동
pom.xml
dependency의 정확한 이름을 모른다면
mvnrepository.com
에 접속하여 검색하자.
<!-- https://mvnrepository.com/artifact/com.h2database/h2 -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.3.176</version>
<scope>runtime</scope>
</dependency>
scope
을 test로 하면 실행했을 때 결과를 알 수 없기때문에 runtime으로 변경했다.
application.yml
아래와 같이 spring
하위에 h2
관련 내용을 추가한다.
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
# password: 1234
h2 console 접속
서비스 디스커버리를 실행한 뒤, user-service
를 실행한다.
http://localhost:8761
에 접속하여 기동되고 user-service
의 주소에 접속하면 Whitelabel Error Page
가 뜰텐데,
이 때 접속된 url의 포트번호 이후 엔트포인트를 /h2-console
로 변경하면 접속 가능하다.
JDBC URL
에는 jdbc:h2:mem:testdb
를 입력한다.
Test Connection
을 눌렀을 때 아래와 같은 오류가 발생한다면?
h2의 버전을 1.3.x 으로 변경한다.
테스트에 성공했다. 이제 Connect를 클릭해 접속할 수 있다.
4. 회원 가입
RequestUser
: 사용자가 입력한 JSON 데이터를 받는다.UserDto
: 데이터가 다른쪽에 있는 서비스 혹은 메서드로 전달되기 위해 사용한다.UserEntity
: jpa에서 데이터베이스와 매핑될 수 있는 객체이다.
회원가입에서는 아래 사진에서처럼 RequestUser
-> UserDto
-> UserEntity
순서로 변환하여 사용할 것이다.
🗂 vo
vo/RequestUser.java
사용자가 회원가입 할 때 전달하는 정보가 저장되는 클래스이다.
@Data
public class RequestUser {
@NotNull(message = "Email cannot be null")
@Size(min = 2, message = "Email not be less than two characters")
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 be equal or greater than 8 characters")
private String pwd;
}
🗂 dto
dto/UserDto.java
넘겨받은 RequestUser 값을 데이터베이스에 저장하고 다른쪽으로 이동하기 위한 용도로 dto 클래스를 생성한다.
UserDto에는 사용자가 회원가입 할 때 입력한 정보 뿐만 아니라 랜덤으로 생성된 userId
와 생성 날짜인 createdAt
그리고 암호화 된 패스워드인 encryptedPwd
가 추가되었다.
@Data
public class UserDto {
private String email;
private String name;
private String pwd;
private String userId;
private Date createdAt;
private String encryptedPwd;
}
🗂 jpa
jpa/UserEntity.java
entity에는 validation을 추가할 수 있다.
@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/UserRepository.java
public interface UserRepository extends CrudRepository<UserEntity, Long> {
}
🗂 service
service/UserService.java
public interface UserService {
UserDto createUser(UserDto userDto);
}
service/UserServiceImpl.java
UserServiceImpl 클래스에서는 ModelMapper
를 이용해 하나의 객체를 다른 객체로 변환한다.
@Service
public class UserServiceImpl implements UserService {
@Autowired
UserRepository userRepository;
@Override
public UserDto createUser(UserDto userDto) {
userDto.setUserId(UUID.randomUUID().toString());
ModelMapper mapper = new ModelMapper();
mapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);
UserEntity userEntity = mapper.map(userDto, UserEntity.class); // ModelMapper를 이용한 객체 변환
userEntity.setEncryptedPwd("encrypted_password");
userRepository.save(userEntity);
UserDto returnUserDto = mapper.map(userEntity, UserDto.class); // ModelMapper를 이용한 객체 변환
return returnUserDto;
}
}
🗂 controller
controller/UserController.java
@RestController
@RequestMapping("/")
public class UserController {
private UserService userService;
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
@PostMapping("/users")
public ResponseEntity<ResponseUser> createUser(@RequestBody RequestUser requestUser) {
ModelMapper mapper = new ModelMapper();
mapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);
UserDto userDto = mapper.map(requestUser, UserDto.class);
userService.createUser(userDto);
ResponseUser responseUser = mapper.map(userDto, ResponseUser.class);
return ResponseEntity.status(HttpStatus.CREATED).body(responseUser);
}
}
테스트
포스트맨
유레카 서버에서 기동되고 있는 서비스의 포트 번호를 확인한 뒤 진행한다.
h2 console 접속
유레카 서버에서 기동되고 있는 서비스의 url을 클릭한 뒤 엔드포인트를 /h2-console
로 변경해 콘솔에 접속할 수 있다.
5. Spring Security 연동
- Authentication(인증) + Authorization(권한)
- 애플리케이션에
spring security jar
을 Dependency에 추가 WebSecurityConfigurerAdapter
를 상속받는Security Configuration
클래스 생성Security Configuration
클래스에@EnableWebSecurity
추가Authentication
->configure(AuthenticationManagerBuilder auth)
메서드를 재정의- Password encode를 위한
BCryptPasswordEncoder
빈 정의 Authorization
->configure(HttpSecurity http)
메서드를 재정의
dependency
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
🗂 security
security/WebSecurity.java
@Configuration
@EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests()
.antMatchers("/users/**")
.permitAll();
http.headers().frameOptions().disable();
}
}
이때, 맨 마지막에 http.headers().frameOptions().disable();
를 추가하지 않으면 db 접속시에 프레임이 깨지는 에러가 발생한다.
🗂 service
service/UserServiceImpl.java
UserRepository
는 빈으로 등록을 시킬건데, BCryptPasswordEncoder
는 어디에서도 빈으로 주입한 적이 없다.
따라서 “Could not autowire” 라는 에러메시지가 표시될 것이다.
이를 해결하기 위해서는 가장 먼저 실행되는 스프링 어플리케이션의 기동 클래스 안에 해당하는 빈을 넣어놓으면 된다.
@Service
public class UserServiceImpl implements UserService {
UserRepository userRepository;
BCryptPasswordEncoder passwordEncoder;
@Autowired
public UserServiceImpl(UserRepository userRepository, BCryptPasswordEncoder passwordEncoder) { // 🌟 이때, passwordEmcoder에 에러가 표시될 것이다.
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
}
@Override
public UserDto createUser(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;
}
}
🌟 UserServiceApplication
BCryptPasswordEncoder
를 빈으로 등록한다. (메서드 이름은 아무거나 상관없다.)
@SpringBootApplication
@EnableEurekaClient
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
}
@Bean // 🌟 빈 등록
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
테스트
이전과 같이 포스트맨으로 요청한 뒤, h2 console에서 확인해보면,
아래와 같이 비밀번호가 암호화되어 저장된 모습을 확인할 수 있다.
💛 개인 공부 기록용 블로그입니다. 👻