kafka docker에 간단 설치 후 Spring boot 연동 테스트

web/Spring|2019. 1. 25. 00:14

간단하게 Kafka 설치

docker-compose.yml 생성 후 docker-compose up -d를 통해 설치

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
version: '2'
services:
  zookeeper:
    image: wurstmeister/zookeeper
    ports:
      - "2181:2181"
  kafka:
    image: wurstmeister/kafka
    ports:
      - "9092:9092"
    environment:
      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
      KAFKA_ADVERTISED_HOST_NAME: wedul.pos
      KAFKA_CREATE_TOPICS: "test:1:1"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
cs


설치된 프로세스 확인


생성된 토픽 확인

- test 토픽이 파티션 1와 replication 1로 생성되었는지 여부 확인

1
2
3
$ docker exec -it simple_kafka_1 bash
$ cd /opt/kafka/bin
$ kafka-topics.sh --describe --topic test --zookeeper simple_zookeeper_1
cs


Spring Boot 프로젝트 생성


카프카 설정

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package com.kafka.study.configuration;
 
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.common.serialization.StringSerializer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.kafka.annotation.EnableKafka;
import org.springframework.kafka.core.DefaultKafkaProducerFactory;
import org.springframework.kafka.core.KafkaTemplate;
 
import java.util.HashMap;
import java.util.Map;
 
/**
 * 카프카 설정
 *
 * @author wedul
 * @since 2019-01-24
 **/
@Configuration
@EnableKafka
@PropertySource("classpath:kafka.properties")
public class KafkaConfiguration {
 
  @Autowired
  private Environment env;
 
  private Map<String, Object> producerConfig() {
    Map<String, Object> config = new HashMap<>();
 
    config.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, env.getProperty("bootstrap.servers"));
    config.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
    config.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
 
    return config;
  }
 
  @Bean
  public KafkaTemplate<StringString > kafkaTemplate() {
    return new KafkaTemplate<>(new DefaultKafkaProducerFactory<>(producerConfig()));
  }
 
}
 
cs


카프카 컨트롤러

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
31
32
33
package com.kafka.study.ctrl;
 
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
 
/**
 * study
 *
 * @author wedul
 * @since 2019-01-24
 **/
@RestController
@Slf4j
public class KafkaCtrl {
 
  private final KafkaTemplate kafkaTemplate;
 
  public KafkaCtrl(KafkaTemplate kafkaTemplate) {
    this.kafkaTemplate = kafkaTemplate;
  }
 
  @PostMapping("/send")
  public ResponseEntity<String> sendMessage(String message) {
    if(!StringUtils.isEmpty(message)) kafkaTemplate.send("test""Message is " + message);
    log.info(message);
    return ResponseEntity.ok("");
  }
}
 
cs


Console-consumer 모니터링 모드

카프카에 메시지를 send 했을 때 모니터링하기 위한 모드 스크립트 실행

1
bash-4.4# kafka-console-consumer.sh --bootstrap-server wedul.pos:9092 --topic test
cs


실행해보면 콘솔에 메시지가 전송된것을 확인할 수 있다.


Kafka Licenser 설정 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.kafka.study.configuration;
 
import lombok.extern.slf4j.Slf4j;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Component;
 
/**
 * study
 *
 * @author wedul
 * @since 2019-01-25
 **/
@Slf4j
@Component
public class ReceiveConfiguration {
 
  @KafkaListener(topics = "test", groupId = "console-consumer-1970")
  public void receive(String payload) {
    log.info("received payload='{}'", payload);
  }
 
}
 
cs


보내면 바로 consumer에서 메시지를 받을 수 있도록 리스너를 설정해보자.

그리고 테스트!

1
2
3
4
5
2019-01-25 00:09:43.033  INFO 1760 --- [nio-8080-exec-1] o.a.kafka.common.utils.AppInfoParser     : Kafka version : 2.0.1
2019-01-25 00:09:43.034  INFO 1760 --- [nio-8080-exec-1] o.a.kafka.common.utils.AppInfoParser     : Kafka commitId : fa14705e51bd2ce5
2019-01-25 00:09:43.041  INFO 1760 --- [ad | producer-1] org.apache.kafka.clients.Metadata        : Cluster ID: 8gNzLx__SHq-4p0b_WsydA
2019-01-25 00:09:43.047  INFO 1760 --- [nio-8080-exec-1] com.kafka.study.ctrl.KafkaCtrl           : babo
2019-01-25 00:09:43.069  INFO 1760 --- [ntainer#0-0-C-1] c.k.s.c.ReceiveConfiguration             : received payload='Message is babo'
cs


git 저장소 : https://github.com/weduls/kafka_example

댓글()

피보나치 수열 재귀, DP, loop 방법으로 구현하고 차이 확인

JAVA/알고리즘|2018. 7. 9. 08:57

피보나치 수열을 이용한 재귀 프로그래밍은 대학교 1학년때 처음 재귀를 구하면서 접했었다.


당시에는 재귀의 예제로써 피보나치와 팩토리얼함수를 구현하는 것으로 소개되었다.

하지만 시간복잡도에 대해 다시 공부하던 중 우리가 배웠던 피보나치 수열의 재귀는 좋은 방식이 아니라는 것을 알게되었다.


피보나치 수열의 3가지 방식에 대해 구현해보고 차이를 느껴보자.


우선 피보나치 수열은 현재 값을 구하기위해서는 이전의 값(n-1)과 그 더 이전의 값(n-2)을 더하면서 구한다.

N = (n - 2) + (n -1)

0, 1, 1, 2, 3, 5, 8, 13, 21, 34........


1) 재귀방식

재귀로 구현하는 방식은 가장 익숙한 방법이지만 매번 구할 때 마다 처음까지 가야하는 가장 안좋은 BigO(2^n)의 시간 복잡도를 가지게 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
    * 재귀를 사용한 피보나치 수열
    * 
    * @param i
    * @return
*/
private static int recurSiveFibo(int i) {
    if (i <= 1) {
        return i;
    } else {
        return recurSiveFibo(i - 2) + recurSiveFibo(i - 1);
    }
}
cs



2) Dynamic Programming

이전에 구해놓은 값을 이용하여 값을 구하는 방식인 DP 알고리즘을 구하면 BigO(n)의 시간 복잡도를 가지게 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
     * DP 배열 생성
     * 
     * @param i
     * @return
*/
private static int getDpFibo(int fiboCnt) {
   fiboDpArray = new int[fiboCnt + 1];
        
   fiboDpArray[0= 0;
   fiboDpArray[1= 1;
        
   if (fiboCnt > 1) {
     for (int i = 2; i <= fiboCnt; i++) {
        fiboDpArray[i] = fiboDpArray[i - 2+ fiboDpArray[i - 1];
     }
   }
        
    return fiboDpArray[fiboCnt];
}
cs



3) 반복문

반복문을 통해서 값을 구할 때도 동일하게  BigO(n)의 시간 복잡도를 가지게 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
     * @param fiboCnt
     * @return
 */
private static int getLoopFibo(int fiboCnt) {
    if (fiboCnt <= 1) {
        return 1;
    } else {
        int a = 0;
        int b = 1;
        int sum = 0;
        
        for (int i = 2; i <= fiboCnt; i++) {
            sum = a + b;
            a = b;
            b = sum;
        }
            
        return sum;
    }
}
cs


무턱대고 재귀함수를 사용하면 안되지만, 재귀함수가 그렇다고 어떤경우에도 다 안 좋은것은 아니다.

상황에 따라 시간복잡도를 잘 고려해서 사용하면 좋은 재귀를 쓸 수있을 것 같다.


댓글()

Spring boot에서 Spring security를 사용하여 로그인 하기

web/Spring|2018. 5. 27. 19:44

스프링 부트를 공부하면서 스프링에서 제공하는 spring security를 통해 로그인 기능을 편리하게 구현할 수 있다는 것을 알았다.

기존에 회사에서는 interceptor 기능을 통해서 로그인 기능을 만들어서 사용했다. 둘다 좋은 방식이지만 spring security를 사용하면 csrf 공격도 막을 수 있어서 한번 사용해봐도 좋을 것 같다.

1. 라이브러리 pom.xml



1
2
3
4
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
cs




2. SpringSecurityAdaptor


-> 스프링 시큐리티에 필요한 내용을 정의하는 configuration을 생성해야한다.
-> WebSecurityConfigurerAdapter 클래스를 상속받아서 configure 메서드를 재정의 해야한다.
-> @EnableWebSecurity, @EnableGlobalAuthentication와 같은 애노테이션을 사용하여 스프링 시큐리티 사용을 정의 해야 한다.
-> public void configure(WebSecurity web) throws Exception 메서드를 재정의하여 로그인 상관 없이 허용을 해줘야할 리소스 위치를 정의한다.
-> protected void configure(HttpSecurity http) throws Exception  메소드를 재정의하여 로그인 URL, 권한분리, logout URL 등등을 설정할 수 있다. (자세한 설명은 메서드에 주석으로 확인)




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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
package com.wedul.common.config;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.configuration.EnableGlobalAuthentication;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
 
import com.wedul.wedulpos.user.serviceImpl.AuthProvider;
 
/**
 * Security Configuration
 * 
 * @author wedul
 *
 */
@Configuration
@EnableWebSecurity
@EnableGlobalAuthentication
@ComponentScan(basePackages = {"com.wedul.*"})
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
    
    @Autowired
    AuthProvider authProvider;
    
    @Autowired
    AuthFailureHandler authFailureHandler;
 
    @Autowired
    AuthSuccessHandler authSuccessHandler;
 
    @Override
    public void configure(WebSecurity web) throws Exception {
        // 허용되어야 할 경로들
        web.ignoring().antMatchers("/resources/**"
                                   "/dist/**"
                                   "/weather"
                                   "/user/password/find",
                                   "/user/join",
                                   "/user/email",
                                   "/user/send/temppw",
                                   "/findpw"
                                   "/user/findpw",
                                   "/user/cert/check",
                                   "/join"
                                   "/getLanguage/**",
                                   "/getMessage"); // #3
    }
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
 
        // 로그인 설정
        http.authorizeRequests()
            // ROLE_USER, ROLE_ADMIN으로 권한 분리 유알엘 정의
            .antMatchers("/""/user/login""/error**").permitAll()
            .antMatchers("/**").access("ROLE_USER")
            .antMatchers("/**").access("ROLE_ADMIN")
            .antMatchers("/admin/**").access("ROLE_ADMIN")
            .antMatchers("/**").authenticated()
        .and()
            // 로그인 페이지 및 성공 url, handler 그리고 로그인 시 사용되는 id, password 파라미터 정의
            .formLogin()
            .loginPage("/user/login")
            .defaultSuccessUrl("/")
            .failureHandler(authFailureHandler)
            .successHandler(authSuccessHandler)
            .usernameParameter("id")
            .passwordParameter("password")
        .and()    
            // 로그아웃 관련 설정
            .logout().logoutRequestMatcher(new AntPathRequestMatcher("/user/logout"))
            .logoutSuccessUrl("/")
            .invalidateHttpSession(true)
        .and()
            // csrf 사용유무 설정
            // csrf 설정을 사용하면 모든 request에 csrf 값을 함께 전달해야한다.
            .csrf()
        .and()
            // 로그인 프로세스가 진행될 provider
            .authenticationProvider(authProvider);
    }
 
}
cs



3. 로그인 검증을 진행하는 AuthProvider
-> AuthenticationProvider 인터페이스를 구현



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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package com.wedul.wedulpos.user.serviceImpl;
 
import java.util.ArrayList;
import java.util.List;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Component;
 
import com.wedul.common.util.Constant;
import com.wedul.common.util.HashUtil;
import com.wedul.wedulpos.user.dto.MyAuthenticaion;
import com.wedul.wedulpos.user.dto.UserDto;
import com.wedul.wedulpos.user.service.UserService;
 
/**
 * 인증 프로바이더
 * 로그인시 사용자가 입력한 아이디와 비밀번호를 확인하고 해당 권한을 주는 클래스
 * 
 * @author wedul
 *
 */
@Component("authProvider")
public class AuthProvider implements AuthenticationProvider  {
    
    @Autowired
    UserService userService;
 
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String id = authentication.getName();
        String password = HashUtil.sha256(authentication.getCredentials().toString());
        
        UserDto user = userService.selectUser(new UserDto(id));
        
        // email에 맞는 user가 없거나 비밀번호가 맞지 않는 경우.
        if (null == user || !user.getPassword().equals(password)) {
            return null;
        }
        
        List<GrantedAuthority> grantedAuthorityList = new ArrayList<>();
        
        // 로그인한 계정에게 권한 부여
        if (user.isIsadmin()) {
            grantedAuthorityList.add(new SimpleGrantedAuthority(Constant.ROLE_TYPE.ROLE_ADMIN.toString()));
        } else {
            grantedAuthorityList.add(new SimpleGrantedAuthority(Constant.ROLE_TYPE.ROLE_USER.toString()));
        }
 
        // 로그인 성공시 로그인 사용자 정보 반환
        return new MyAuthenticaion(id, password, grantedAuthorityList, user);
    }
    
    @Override
    public boolean supports(Class<?> authentication) {
        return authentication.equals(UsernamePasswordAuthenticationToken.class);
    }
 
}
cs




4. 로그인 성공, 실패 후 진행되는 handler 클래스
-> 로그인 성공 그리고 실패 후 진행될 클래스 정의



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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package com.wedul.common.config;
 
import java.io.IOException;
 
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.stereotype.Component;
 
/**
 * 로그인 실패 핸들러
 * 
 * @author wedul
 *
 */
@Component
public class AuthFailureHandler extends SimpleUrlAuthenticationFailureHandler {
    
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
            AuthenticationException exception) throws IOException, ServletException {
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        // 실패 시 response를 json 형태로 결과값 전달
        response.getWriter().print("{\"success\": false}");
        response.getWriter().flush();
    }
}
 
 
package com.wedul.common.config;
 
import java.io.IOException;
 
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
 
/**
 * 로그인 성공 핸들러
 * 
 * @author wedul
 *
 */
@Component
public class AuthSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
 
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
            Authentication authentication) throws ServletException, IOException {
 
        response.setStatus(HttpServletResponse.SC_OK);
        // 성공 시 response를 json형태로 반환
        response.getWriter().print("{\"success\": true}");
        response.getWriter().flush();
    }
 
}
cs




스프링 시큐리티를 처음 적용해 볼때는 어려움도 많았지만 적용하니 엄청 간단하게 사용할 수 있어서서 좋았다. 적용된 소스코드에 대한 정확한 정보는 https://github.com/weduls/wedulpos_boot 여기서 확인 할 수 있다.



댓글()
  1. 지나가는사람 2018.12.20 11:01 댓글주소  수정/삭제  댓글쓰기

    MyAuthenticaion 클래스랑 . userService 클래스랑 ,Constant.ROLE_TYPE.ROLE_ADMIN 이부분 의 소스 UserDTO부분좀 요청 드려도될까요 ??

  2. Favicon of https://daesoop.tistory.com BlogIcon 김대숲 2019.06.17 13:32 신고 댓글주소  수정/삭제  댓글쓰기

    글 너무 잘 읽었습니다.
    읽던 도중 깃헙 확인 중에 core부분이 gitignore 되어 있는것 같은데 혹시 저도 위에 분과 같이 소스좀 요청드려도 괜찮을까요?

    • Favicon of https://wedul.site BlogIcon 위들 wedul 2019.06.17 20:57 신고 댓글주소  수정/삭제

      안녕하세요.
      우선 도움이 되었다니 감사합니다.

      https://github.com/weduls/wedulpos_boot/blob/master/core/src/main/java/com/wedul/wedulpos/core/user/dto/MyAuthenticaion.java

      필요하신 코드가 어떤 코드인지 모르겠으나, 다 있는것 같아요.

      혹시 필요한 별도 클래스가 있으시다면 찾아드리겠습니다.

  3. 지나가는사람2 2019.10.16 20:45 댓글주소  수정/삭제  댓글쓰기

    pom.xml이 pom.sml로 오타 난 것 아닌가요?

  4. Favicon of https://requiem-blog.tistory.com BlogIcon Requiem0615 2019.11.06 12:24 신고 댓글주소  수정/삭제  댓글쓰기

    MyAuthenticaion 클래스 어디에있나요? 깃허브 확인해도 찾을 수가 없네요 ...

    • Favicon of https://wedul.site BlogIcon 위들 wedul 2019.11.06 16:21 신고 댓글주소  수정/삭제

      안녕하세요. 위에 어떤분도 말씀하셔서 댓글로 안내드렸습니다.

      이 위치에 있습니다.
      https://github.com/weduls/wedulpos_boot/blob/master/core/src/main/java/com/wedul/wedulpos/core/user/dto/MyAuthenticaion.java

      감사합니다.

  5. 주니어 2020.03.27 15:46 댓글주소  수정/삭제  댓글쓰기

    안녕하세요 시간이 많이지나서 git repository가 남아있는지 모르겠지만.. 이름이 혹시 바뀐건가요 ㅠㅠ