Login Service[1] security ํšŒ์›๊ฐ€์ž…

์ตœ์ค€ํ˜ธยท2022๋…„ 3์›” 17์ผ
0

jayeon

๋ชฉ๋ก ๋ณด๊ธฐ
7/10
post-thumbnail

๐Ÿ”จProject ์ƒ์„ฑ

์ถ”ํ›„์— Kafka์™€ RabbitMQ์™€ ๊ฐ™์€ ์„ค์ •๋„ ์ถ”๊ฐ€ํ• ๊ฑฐ์ง€๋งŒ ๊ทธ๊ฑด ๊ทธ๋•Œ ์ƒ๊ฐํ•˜๊ณ  ์šฐ์„  ๋‹น์žฅ์— ์‚ฌ์šฉํ• ๊ฑฐ ๊ฐ™์€ ์˜์กด์„ฑ๋งŒ ์ถ”๊ฐ€ํ•ด๋ดค๋‹ค. ์ƒ๊ฐ๋ณด๋‹ค ๋งŽ๋‹ค...ใ…Ž

@SpringBootApplication
@EnableDiscoveryClient
public class LoginServiceApplication {

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

}

Eureka์— ๋“ฑ๋ก๋˜๋„๋ก @EnableDiscoveryClient๋ฅผ ์ถ”๊ฐ€ํ•ด์ฃผ๊ณ 

server:
  port: 0 #๋žœ๋ค์œผ๋กœ ํฌํŠธ ์„ค์ •

spring:
  application:
    name: login-service  #Eureka์— ๋“ฑ๋ก๋˜๋Š” ์„œ๋น„์Šค ์ด๋ฆ„
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://127.0.0.1:8761/eureka

๋‹ค์Œ ์„ค์ • ๋‚ด์šฉ์„ config server git์— ์˜ฌ๋ ค๋†“์ž or application.yml์— ๊ทธ๋Œ€๋กœ ๋“ฑ๋กํ•ด๋„ ๋œ๋‹ค.

application.yml

server:
  port: 0 #๋žœ๋ค์œผ๋กœ ํฌํŠธ ์„ค์ •

spring:
  application:
    name: login-service  #Eureka์— ๋“ฑ๋ก๋˜๋Š” ์„œ๋น„์Šค ์ด๋ฆ„

eureka:
  instance:
    instance-id: ${spring.application.name}:${spring.application.instance_id:${random.value}}  #ํฌํŠธ๊ฐ€ ์ค‘๋ณต์œผ๋กœ ์„ค์ •๋˜์–ด ๊ตฌ๋ถ„ํ•˜๊ธฐ ์œ„ํ•œ ์ธ์Šคํ„ด์Šค ์•„์ด๋”” ๊ฐ’ ์„ค์ •

bootstrap.yml

eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://127.0.0.1:8761/eureka

bootstrap์„ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์„ค์ •ํ•ด์ค€๋‹ค.

implementation 'org.springframework.cloud:spring-cloud-starter-bootstrap'
implementation 'org.springframework.boot:spring-boot-starter-actuator'

spring initialization์œผ๋กœ ๋งŒ๋“ค์—ˆ๋˜ ํ”„๋กœ์ ํŠธ์—์„œ ์ •์ƒ ์ž‘๋™์„ ํ•˜์ง€ ์•Š์•„์„œ ๋‹ค์Œ ์˜์กด์„ฑ์„ ๋” ์ถ”๊ฐ€ํ•ด์ฃผ์—ˆ๋‹ค.

config์˜ ๊ฒฝ์šฐ ${}๊ณผ ๊ฐ™์ด ๋ณ€์ˆ˜?๋ฅผ ๋ฐ›์•„์˜ค๋Š” ์ž‘์—…์€ ์ •์ƒ์ ์œผ๋กœ ์ง„ํ–‰๋˜์ง€ ์•Š๋”๋ผ. config์ชฝ์—์„œ ๋ฐ›์•„์˜ค๋Š” ์ •๋ณด๋Š” ์ตœ๋Œ€ํ•œ ๊ณ ์ •๊ฐ’์ด ์ •ํ•ด์ง„ ๊ฐ’์œผ๋กœ๋งŒ ๋ฐ›๊ณ  ๋‚˜๋จธ์ง€๋Š” application config๋กœ ์‹คํ–‰ํ•˜์ž.

์„ค์ •๊ณผ ํ•จ๊ป˜ ์ž˜ ๋“ฑ๋ก๋œ ๊ฒƒ์„ ๋ณผ์ˆ˜ ์žˆ๋‹ค.

์—ด๋ ค์ง„ ํฌํŠธ๋กœ ์ ‘์†ํ•˜๋ฉด security ์„ค์ •์œผ๋กœ ์ธํ•ด ๋ชจ๋“  ์ ‘๊ทผ์ด ๋ง‰ํ˜€์žˆ๋‹ค. ์ด์ œ ์ด๊ฑธ ์šฐ๋ฆฌ๊ฐ€ ์„ค์ •ํ•ด๋ณด์ž.

๐Ÿ”จSecurity ์„ค์ •

runtimeOnly 'com.h2database:h2:1.3.176'

์šฐ์„ ์€ h2๋กœ ํ…Œ์ŠคํŠธ ์ง„ํ–‰ํ•  ๊ฒƒ์ด๋ฏ€๋กœ 1.3.176์œผ๋กœ ์‚ฌ์šฉํ•˜์ž ์™œ๋ƒํ•˜๋ฉด ์ด ์ƒ์œ„๋ฒ„์ „์€ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋Š”๋ฐ ๊ทธ๊ฑธ ํ•ด๊ฒฐํ•˜๋ฉด์„œ ์ง„ํ–‰ํ•˜๋ ค๋ฉด ์‹œ๊ฐ„ ๋” ๊ฑธ๋ฆฐ๋‹ค ๊ทธ๋ƒฅ 1.3.176์œผ๋กœ ์“ฐ์ž

spring:
  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: 

๋‹ค์Œ ์„ค์ •๊ฐ’์„ application.yml์— ์„ค์ •ํ•ด์ค€๋‹ค. bootstrap์— ์„ค์ •ํ•ด์ฃผ๋ ค๊ณ  ํ•˜๋‹ˆ web-allow-others ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

@Configuration
@EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();
        http.authorizeRequests().antMatchers("/**").permitAll();

        http.headers().frameOptions().disable();    //h2 error
    }
}

๊ทธ ํ›„ ๋‹ค์Œ๊ณผ ๊ฐ™์ด security ์„ค์ •์„ ํ†ตํ•ด ๋ชจ๋“  url์— ์šฐ์„  ์ ‘๊ทผ์ด ๊ฐ€๋Šฅํ•˜๋„๋ก ํ’€์–ด๋†“์ž. ๊ทธ ํ›„์— ์ฐจ์ฐจ ์ ‘๊ทผ ๊ถŒํ•œ์„ ์„ค์ •ํ•ด๋‘˜๊ฑฐ๋‹ค.

์ž˜ ์ ‘๊ทผ์ด ๋œ๋‹ค.

๐Ÿ”จController Test

Controller์—์„œ ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์œ„ํ•ด port๊ฐ€ ์ฐํ˜€ ๋‚˜์˜ค๋Š” url์„ ํ•˜๋‚˜ ๋งŒ๋“ค์–ด๋ณด์ž

@RestController
@RequestMapping(value = "/")
@RequiredArgsConstructor
public class HelloController {

    private final Environment env;

    @GetMapping("check")
    public ResponseEntity<String> check(){

        return ResponseEntity.ok().body(String.format("hello Login Service Port = %s", env.getProperty("local.server.port")));
    }
}

ํฌํŠธ์— ์ง์ ‘ ๋“ค์–ด๊ฐ€์„œ check๋กœ ์š”์ฒญํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ •์ƒ ์š”์ฒญ์ด ๋œ๋‹ค.

์ด์ œ gateway๋ฅผ ์—ฐ๊ฒฐํ•ด์„œ ์‚ฌ์šฉํ•ด๋ณด์ž.

server:
  port: 8000

eureka:
  client:
    fetch-registry: true
    register-with-eureka: true
    service-url:
      defaultZone: http://127.0.0.1:8761/eureka

spring:
  application:  #gateway service ์ด๋ฆ„๋ฆ„
    name: gateway-service
  cloud:
    gateway:  #gateway ์„ค์ •
      routes:
        - id: login-service	#Service id
          uri: lb://LOGIN-SERVICE	#Eureka์— ๋“ฑ๋ก๋œ Service ์ด๋ฆ„
          predicates:
            - Path=/login-service/**	#๊ตฌ๋ถ„๋  url
            - Method=GET, POST			#์š”์ฒญ ๊ฐ€๋Šฅํ•œ Method
          filters:
            - RemoveRequestHeader=Cookie
            - RewritePath=/login-service(?<segment>.*), /$\{segment}	#๋“ค์–ด์˜จ ์š”์ฒญ์— ๋Œ€ํ•ด ์ž๋™์œผ๋กœ /login-service/๋ฅผ ๋ถ™์—ฌ์„œ ์‹คํ–‰

config์— ์„ค์ •ํ•ด๋†จ๋˜ gateway์˜ ymlํŒŒ์ผ์„ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ˆ˜์ •ํ•ด์ฃผ์—ˆ๋‹ค.

์žฌ์‹คํ–‰ ํ•˜๋Š๋ผ ํฌํŠธ๊ฐ€ ๋ณ€๊ฒฝ๋˜์—ˆ์ง€๋งŒ ์ด์ œ gateway๋กœ ์š”์ฒญํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜์—ˆ๋‹ค.

๐Ÿ”จUser ๋“ฑ๋ก ์„œ๋น„์Šค ๋งŒ๋“ค๊ธฐ

๋กœ๊ทธ์ธ ๊ธฐ๋Šฅ์„ ๋งŒ๋“ค๊ธฐ ์ „์— ์šฐ์„  ํšŒ์›๊ฐ€์ž…์„ ํ†ตํ•ด User์˜ ์ •๋ณด๋ฅผ ๋“ฑ๋กํ•  User Entity๋ฅผ ๋งŒ๋“ค์–ด๋‘๋ ค๊ณ  ํ•œ๋‹ค.

@Table(name = "USERS")
@Entity
@Getter
@AllArgsConstructor
@NoArgsConstructor
public class UserEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "user_id", nullable = false, unique = true, length = 10)
    private String userId;
    @Column(nullable = false, length = 20)
    private String name;
    @Column(nullable = false, length = 150)
    private String pw;

    @Column(nullable = false, length = 10)
    private String address1;
    @Column(nullable = false, length = 50)
    private String address2;
    @Column(nullable = false, length = 50)
    private String address3;

    @Column(nullable = false, length = 20)
    private String tel;

    @CreationTimestamp
    private LocalDateTime createdAt = LocalDateTime.now();

    @Builder
    public UserEntity(@NonNull Long id, @NonNull String userId, @NonNull String name, @NonNull String pw, @NonNull String address1, @NonNull String address2, @NonNull String address3, @NonNull String tel) {
        this.id = id;
        this.userId = userId;
        this.name = name;
        this.pw = pw;
        this.address1 = address1;
        this.address2 = address2;
        this.address3 = address3;
        this.tel = tel;
    }
}

UserEntity๋ฅผ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ •์˜ํ–ˆ๋‹ค.

@AllArgsConstructor
@Getter
public class RequestUser {
    private String userId;
    private String pw;
    private String name;
    private String address1;    //์šฐํŽธ๋ฒˆํ˜ธ
    private String address2;    //๋„๋กœ๋ช… ์ฃผ์†Œ
    private String address3;    //์ƒ์„ธ ์ฃผ์†Œ
    private String tel;         //์ „ํ™”๋ฒˆํ˜ธ
}

RequestUser๋ผ๋Š” Vo๋ฅผ ์ •์˜ํ•˜์—ฌ Controller์—์„œ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ๋ฐ›์„๊ฑฐ๊ณ 


@SpringBootApplication
@EnableDiscoveryClient
public class LoginServiceApplication {

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

	//๋น„๋ฐ€๋ฒˆํ˜ธ ์•”ํ˜ธํ™”์šฉ encoder
	@Bean
	public BCryptPasswordEncoder passwordEncoder(){
		return new BCryptPasswordEncoder();
	}
}

๋น„๋ฐ€๋ฒˆํ˜ธ ์•”ํ˜ธํ™”๋ฅผ ์œ„ํ•ด Security์—์„œ ์ œ๊ณตํ•˜๋Š” Encoder๋ฅผ Bean์œผ๋กœ ๋“ฑ๋กํ•ด์ค€๋‹ค.

@Getter
@AllArgsConstructor
@Builder
public class ResponseUser {
    String userId;
}

Vo ResponseUser๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ 

public interface UserService {
    ResponseUser join(RequestUser user);
}

service interface๋ฅผ ์ถ”๊ฐ€ํ•ด์ค€๋‹ค.

ModelMapper์™€ ๊ฐ™์€ ์ž๋™ ๋งคํผ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋“ค์ด ์žˆ์ง€๋งŒ ์„ฑ๋Šฅ์— ์ €ํ•˜๋‚˜ ์„ค์ •์— ์žˆ์–ด์„œ ์‹œ๊ฐ„์ด ์˜ค๋ž˜ ๊ฑธ๋ฆฌ๊ธฐ ๋•Œ๋ฌธ์— ์›๋ž˜ ์‚ฌ์šฉํ•˜๋˜ builder ํŒจํ„ด์œผ๋กœ ์ƒ์„ฑํ•œ๋‹ค.

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

repository๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ƒ์„ฑํ•˜๊ณ 

@Service
@RequiredArgsConstructor
public class UserServiceImpl implements UserService{
    private final BCryptPasswordEncoder passwordEncoder;
    private final UserRepository userRepository;

    @Override
    public ResponseUser join(RequestUser user) {
        UserEntity userEntity = UserEntity.builder()
                .userId(user.getUserId())
                .pw(passwordEncoder.encode(user.getPw()))
                .name(user.getName())
                .address1(user.getAddress1())
                .address2(user.getAddress2())
                .address3(user.getAddress3())
                .tel(user.getTel())
                .build();

        UserEntity save = userRepository.save(userEntity);

        return ResponseUser.builder().userId(save.getUserId()).build();
    }
}

UserServiceImpl์˜ ํšŒ์›๊ฐ€์ž… ๋กœ์ง์„ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ž‘์„ฑํ–ˆ๋‹ค.

์ด์ œ ํฌ์ŠคํŠธ๋งจ์„ ํ†ตํ•ด ์š”์ฒญํ•ด๋ณด์ž.

๋‹ค์Œ๊ณผ ๊ฐ™์ด ์š”์ฒญํ–ˆ๊ณ 

๋ฐ์ดํ„ฐ๋„ ์ž˜ ๋“ค์–ด์™”๋‹ค.

ํ•˜์ง€๋งŒ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋Š”๋ฐ... ์ด์œ ๋Š” pw์˜ ๊ธธ์ด๋ฅผ ๋„ˆ๋ฌด ์งง๊ฒŒ ์žก์•„๋†จ๋‹ค. ์•”ํ˜ธํ™” ์ดํ›„์— ๊ธธ์ด๊ฐ€ 60๊นŒ์ง€ ๊ธธ์–ด์ ธ๋ฒ„๋ ค์„œ ์˜ค๋ฅ˜๊ฐ€ ๋‚ฌ๋‹ค. ๊ธธ์ด๋ฅผ 150๊นŒ์ง€ ๋Š˜๋ ค๋ฒ„๋ฆฌ์ž.

์ •์ƒ์ ์œผ๋กœ ์ž˜ ๋“ฑ๋ก๋˜์—ˆ๋‹ค!

DB์—๋„ ์ •์ƒ ๋“ฑ๋ก๋œ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

๐Ÿ‘‰api์ฒ˜๋Ÿผ ๋ฐ˜ํ™˜ํ•ด๋ณด๊ธฐ

์ •์ƒ ์‹คํ–‰์€ ๋˜์ง€๋งŒ api์ฒ˜๋Ÿผ ๋ญ”๊ฐ€ ํ˜•์‹์ด ์—†์ด ๊ทธ๋ƒฅ ๋‚ด๊ฐ€ ๋ฐ˜ํ™˜ํ•˜๊ณ  ์‹ถ์€๋Œ€๋กœ ๋ฐ˜ํ™˜์ด ๋œ๋‹ค. api๋ผ๋ฉด ์ผ์ •ํ•œ ๊ทœ์น™์ด ์žˆ๋Š” ์ŠคํŽ™์„ ์ •์˜ํ•ด๋‘๊ณ  ํ•ด๋‹น ์ŠคํŽ™์œผ๋กœ ๋ฐ˜ํ™˜ํ•ด์ฃผ๋Š” ๊ฒƒ์ด ์‚ฌ์šฉ์ž์—๊ฒŒ๋„ ์ข‹๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

@Getter
public class CommonResponse<T>{
    private String code;
    private String msg;
    private T data;

    @Builder
    public CommonResponse(String code, String msg, T data) {
        this.code = code;
        this.msg = msg;
        this.data = data;
    }
}

CommonResponse class๋ฅผ ์ •์˜ํ•ด์„œ ์‚ฌ์šฉํ•ด๋ณด์ž.

@RestController
@RequiredArgsConstructor
@RequestMapping(value = "/")
@Slf4j
public class UserController {

    private final UserService userService;

    @PostMapping("/join")
    public ResponseEntity<CommonResponse<Object>> join(@RequestBody RequestUser user){
        log.debug("user = {}", user.toString());
        ResponseUser responseUser = userService.join(user);
        CommonResponse<Object> response = CommonResponse.builder()
                .code("200")    //api ์ •์˜๋œ ์ฝ”๋“œ
                .msg("ํšŒ์›๊ฐ€์ž… ์ •์ƒ์‹คํ–‰")    //api ์ •์˜๋œ ๋ฉ”์„ธ์ง€
                .data(responseUser) //๋ฐ์ดํ„ฐ
                .build();
        return ResponseEntity.status(HttpStatus.CREATED).body(response);
    }
}

UserController๋ฅผ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ˆ˜์ •ํ•ด์คฌ๋‹ค.

์ด์ œ ๋‹ค์‹œ ์‹คํ–‰ํ•ด๋ณด๊ณ  ๊ฒฐ๊ณผ๊ฐ’์„ ํ™•์ธํ•ด๋ณด์ž!

๊ฒฐ๊ณผ ๊ฐ’์€ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋‚˜์˜ค๋Š”๋ฐ ์ด์ œ ์€๊ทผ api ๊ฐ™์ด ๋ฐ˜ํ™˜ํ•œ๋‹ค. Exception ์ฒ˜๋ฆฌ๋„ ํ•ด์ค˜์•ผํ•˜๋Š”๋ฐ. ์šฐ์„  jwt๋„ ์ ์šฉํ•ด์•ผํ•˜๋‹ˆ ๋‹ค ํ•ด๋†“๊ณ  ํ•ด๋ณด์ž!

profile
์ฝ”๋”ฉ์„ ๊น”๋”ํ•˜๊ฒŒ ํ•˜๊ณ  ์‹ถ์–ดํ•˜๋Š” ์ดˆ๋ณด ๊ฐœ๋ฐœ์ž (ํŽธํ•˜๊ฒŒ ๊ธ€์„ ์“ฐ๊ธฐ์œ„ํ•ด ๋ฐ˜๋ง์ฒด๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค! ์–‘ํ•ด ๋ถ€ํƒ๋“œ๋ ค์š”!) ํ˜„์žฌ KakaoVX ๊ทผ๋ฌด์ค‘์ž…๋‹ˆ๋‹ค!

0๊ฐœ์˜ ๋Œ“๊ธ€