Java - ThreadLocal 이란? 쓰레드로컬 사용법!



ThreadLocal(쓰레드로컬)이란?





쓰레드로컬이란 간단히 얘기하면 하나의 스레드의 작업 흐름동안에 전역변수처럼 무엇인가를 저장하여 사용할수 있다.
일반 변수의 수명은 특정 코드 블록(예, 메서드 범위, for 블록 범위 등) 범위 내에서만 유효하다.

{
    int a = 10;
    ...
   // 블록 내에서 a 변수 사용 가능
}
// 변수 a는 위 코드 블록이 끝나면 더 이상 유효하지 않다. (즉, 수명을 다한다.)


반면에 ThreadLocal을 이용하면 쓰레드 영역에 변수를 설정할 수 있기 때문에, 특정 쓰레드가 실행하는 모든 코드에서 그 쓰레드에 설정된 변수 값을 사용할 수 있게 된다. 아래 그림은 쓰레드 로컬 변수가 어떻게 동작하는 지를 간단하게 보여주고 있다. 



위 그림에서 주목할 점은 동일한 코드를 실행하는 데, 쓰레드1에서 실행할 경우 관련 값이 쓰레드1에 저장되고 쓰레드2에서 실행할 경우 쓰레드2에 저장된다는 점이다.



ThreadLocal의 기본 사용법

ThreadLocal의 사용방법은 너무 쉽다. 단지 다음의 네 가지만 해 주면 된다.
  1. ThreadLocal 객체를 생성한다.
  2. ThreadLocal.set() 메서드를 이용해서 현재 쓰레드의 로컬 변수에 값을 저장한다.
  3. ThreadLocal.get() 메서드를 이용해서 현재 쓰레드의 로컬 변수 값을 읽어온다.
  4. ThreadLocal.remove() 메서드를 이용해서 현재 쓰레드의 로컬 변수 값을 삭제한다.

아래 코드는 ThreadLocal의 기본적인 사용방법을 보여주고 있다.

// 현재 쓰레드와 관련된 로컬 변수를 하나 생성한다.
ThreadLocal<UserInfo> local = new ThreadLocal<UserInfo>();

// 로컬 변수에 값 할당
local.set(currentUser);

// 이후 실행되는 코드는 쓰레드 로컬 변수 값을 사용
UserInfo userInfo = local.get();


위 코드만으로는 ThreadLocal이 어떻게 동작하는 지 잘 이해가 되지 않을테니, 구체적인 예제를 이용해서 ThreadLocal의 동작 방식을 살펴보도록 하겠다. 먼저 ThreadLocal 타입의 static 필드를 갖는 클래스를 하나 작성해보자.

public class Context {
    public static ThreadLocal<Date> local = new ThreadLocal<Date>();
}

이제 Context 클래스를 사용해서 쓰레드 로컬 변수를 설정하고 사용하는 코드를 작성할 차례이다. 아래는 코드의 예이다.

class A {
    public void a() {
        Context.local.set(new Date());
        
        B b = new B();
        b.b();

        Context.local.remove();
    }
}

class B {
    public void b() {
        Date date = Context.local.get();

        C c = new C();
        c.c();
    }
}

class C {
    public void c() {
        Date date = Context.local.get();
    }
}

위 코드를 보면 A, B, C 세 개의 클래스가 존재하는데, A.a() 메서드를 호출하면 다음 그림과 같은 순서로 메서드가 실행된다.


위 그림에서 1~10은 모두 하나의 쓰레드에서 실행된다. ThreadLocal과 관련된 부분을 정리하면 다음과 같다.

  • 2 - A.a() 메서드에서 현재 쓰레드의 로컬 변수에 Date 객체를 저장한다.
  • 4 - B.b() 메서드에서 현재 쓰레드의 로컬 변수에 저장된 Date 객체를 읽어와 사용한다.
  • 6 - C.c() 메서드에서 현재 쓰레드의 로컬 변수에 저장된 Date 객체를 읽어와 사용한다.
  • 9 - A.a() 메서드에서 현재 쓰레드의 로컬 변수를 삭제한다.

위 코드에서 중요한 건 A.a()에서 생성한 Date 객체를 B.b() 메서드나 C.c() 메서드에 파라미터로 전달하지 않는다는 것이다. 즉, 파라미터로 객체를 전달하지 않아도 한 쓰레드로 실행되는 코드가 동일한 객체를 참조할 수 있게 된다.



ThreadLocal의 활용

ThreadLocal은 한 쓰레드에서 실행되는 코드가 동일한 객체를 사용할 수 있도록 해 주기 때문에 쓰레드와 관련된 코드에서 파라미터를 사용하지 않고 객체를 전파하기 위한 용도로 주로 사용되며, 주요 용도는 다음과 같다.

  • 사용자 인증정보 전파 - Spring Security에서는 ThreadLocal을 이용해서 사용자 인증 정보를 전파한다.
  • 트랜잭션 컨텍스트 전파 - 트랜잭션 매니저는 트랜잭션 컨텍스트를 전파하는 데 ThreadLocal을 사용한다.
  • 쓰레드에 안전해야 하는 데이터 보관

이 외에도 쓰레드 기준으로 동작해야 하는 기능을 구현할 때 ThreadLocal을 유용하게 사용할 수 있다.

ThreadLocal 사용시 주의 사항


쓰레드 풀 환경에서 ThreadLocal을 사용하는 경우 ThreadLocal 변수에 보관된 데이터의 사용이 끝나면 반드시 해당 데이터를 삭제해 주어야 한다. 그렇지 않을 경우 재사용되는 쓰레드가 올바르지 않은 데이터를 참조할 수 있다.


▶︎▶︎▶︎자바캔


posted by 여성게
:
Middleware/Kafka&RabbitMQ 2019. 2. 18. 00:39

Spring - Rabbitmq를 이용한 비동기 메시징 서비스

     -리액티브 마이크로서비스



RabbitMQ에 대해

Mac OS 환경에서 작성되었습니다.


오늘은 간단히 Spring boot + Rabbitmq를 이용한 비동기 메시징 서비스를 구현해볼 것이다. 

일단 이 포스팅을 진행하는 이유는 요즘 시대에는 일체형 애플리케이션이 작은 서비스 단위인 마이크로서비스 단위로 나누어 

서비스 단위로 배포하는 아키텍쳐가 대세인듯하다. 

이 말은 즉슨, 아주 큰 애플리케이션이 작은 서비스 단위(마이크로서비스)로 나뉘어 각각 단독적으로 독립적으로 실행가능한 상태로 배포가 된다. 

이런 경우 마이크로서비스끼리의 통신은 RESTful한 통신도 있지만 메시지 큐와 같은 서비스를 이용하여 비동기적으로 통신하기도 한다.

그리고 이 구조를 발행구독구조라고 한다.


위의 사진과 같이 두 마이크로서비스 애플리케이션이 외부의 큐로 연결되는 구조인 것이다.

간단히 설명하면 Sender라는 하나의 애플리케이션에서 큐로 발신하면 

Receiver라는 애플리케이션이 해당 큐에 대한 이벤트를 수신하여

로직을 처리하게 되는 것이다.





외부의 큐도 어찌됬는 하나의 데몬으로 떠있는 애플리케이션 같은 것이다.

Rabbitmq를 다운로드한다.


▶︎▶︎▶︎래빗엠큐다운로드



Standalone MacOS binary를 클릭하여 받는다.




Rabbitmq 관리자 페이지 플러그인을 활성화하는 명령이다.



관리자 페이지에 들어갈 계정을 만들어준다.


만약 계정을 만드는 데 밑의 에러가 발생한다면 


Error:

{:undef, [{:crypto, :hash, [:sha256, <<94, 223, 167, 31, 97, 108, 105, 118, 101>>], []}, {:rabbit_password, :hash, 2, [file: 'src/rabbit_password.erl', line: 34]}, {:rabbit_auth_backend_internal, :add_user_sans_validation, 3, [file: 'src/rabbit_auth_backend_internal.erl', line: 252]}, {:rpc, :"-handle_call_call/6-fun-0-", 5, [file: 'rpc.erl', line: 197]}]}


>brew install openssl 로 인스톨하면 된다.



Rabbitmq를 실행시켜 준다. 그리고 localhost:15672로 접속한 후에 방금 전에 만들었던 계정으로

로그인한다.




위의 사진에 Set permisstion 버튼을 클릭하여 해당 계정으로 큐에서 읽거나 쓰는 권한을 부여한다.


여기까지 간단한 Rabbitmq의 설정이 완료되었다. 이것보다 더 많은 설정 요소들이 존재하겠지만,

예제로 간단하게 이정도로만 설정한다.


참고: 관리자페이지 포트 : 15672

애플리케이션(amqp) :5672

클러스터링 : 25672





소스 예제 회원가입 이후에 외부 큐에 가입 메시지를 날리면(Sender) 수신애플리케이션에서 가입 회원의 이메일을 커맨드에 뿌려준다.(Receiver)



<Sender>


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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
package org.rvslab.chapter3;
 
import java.util.Optional;
 
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
 
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.core.RabbitMessagingTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
 
import reactor.core.publisher.Mono;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
 
 
@SpringBootApplication
@EnableSwagger2
public class Application {
 
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
    
    
    @Bean
    CommandLineRunner init(CustomerRespository customerRepository) {
            return (evt) ->  {
                              customerRepository.save(new Customer("Adam","adam@boot.com"));
                              customerRepository.save(new Customer("John","john@boot.com"));
                              customerRepository.save(new Customer("Smith","smith@boot.com"));
                              customerRepository.save(new Customer("Edgar","edgar@boot.com"));
                              customerRepository.save(new Customer("Martin","martin@boot.com"));
                              customerRepository.save(new Customer("Tom","tom@boot.com"));
                              customerRepository.save(new Customer("Sean","sean@boot.com"));
            };
    }
    
 
}
 
 
@RestController
class CustomerController{
        
        CustomerRegistrar customerRegistrar;
        
        @Autowired
        CustomerController(CustomerRegistrar customerRegistrar){
                this.customerRegistrar = customerRegistrar;
        }
        
        @RequestMapping( path="/register", method = RequestMethod.POST)
        Mono<Customer> register(@RequestBody Customer customer){                
                return customerRegistrar.register(customer);     
        }
}
 
@Component
@Lazy
class CustomerRegistrar {
        
        CustomerRespository customerRespository;
        Sender sender;
        
        @Autowired
        CustomerRegistrar(CustomerRespository customerRespository, Sender sender){
                this.customerRespository = customerRespository;
                this.sender = sender;
        }
        
        
        public Mono<Customer> registerMono(Mono<Customer> monoCustomer){
                monoCustomer.doOnNext(customer -> {
                        if(customerRespository.findByName(customer.getName()).isPresent())
                                System.out.println("Duplicate Customer");
                        else {
                                customerRespository.save(customer);
                                //sender.send(customer.getEmail());             
                        }
                }).subscribe();
                return monoCustomer;
        }
 
        // ideally repository will return a Mono object
        public Mono<Customer> register(Customer customer){
                        if(customerRespository.findByName(customer.getName()).isPresent())
                                System.out.println("Duplicate Customer. No Action required");
                        else {
                                customerRespository.save(customer);
                                //외부 큐에게 메시지전송
                                sender.send(customer.getEmail());       
                                System.out.println("Rabbitmq send :::: "+customer.toString());
                        }
                return Mono.just(customer);
        }
}
 
 
//외부 큐와 연결하기 위한 Sender를 빈으로 등록한다.
//또 빈으로 Queue객체를 등록해준다. 해당 문자열로 Receiver쪽에서도 동일하게 받아야한다.
//RabbitMessagingTemplate으로 외부 큐에게 메시지를 전송한다.
@Component
@Lazy
class Sender {
        
        RabbitMessagingTemplate template;
        
        @Autowired
        Sender(RabbitMessagingTemplate template){
                this.template = template;
        }
 
        @Bean
        Queue queue() {
                return new Queue("CustomerQ"false);
        }
        
        public void send(String message){
                template.convertAndSend("CustomerQ", message);
                System.out.println("Ready to send message but suppressed "+ message);
                 
        }
}
 
//repository does not support Reactive. Ideally this should use reactive repository
@RepositoryRestResource
@Lazy
interface CustomerRespository extends JpaRepository <Customer,Long>{
        Optional<Customer> findByName(@Param("name"String name);
}
 
 
//Entity class
@Entity
class Customer{
        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        private Long id;
        private String name;
        private String email;
        
        public Customer (){}
 
        
        public Customer(String name, String email) {
                super();
                this.name = name;
                this.email = email;
        }
 
 
        public Long getId() {
                return id;
        }
 
        public void setId(Long id) {
                this.id = id;
        }
 
        public String getName() {
                return name;
        }
 
        public void setName(String name) {
                this.name = name;
        }
 
        public String getEmail() {
                return email;
        }
 
        public void setEmail(String email) {
                this.email = email;
        }
 
        @Override
        public String toString() {
                return "Customer [id=" + id + ", name=" + name + ", email=" + email + "]";
        }
        
         
        
}
cs


  1. management.security.enabled=false
  2.  
  3. spring.rabbitmq.host=localhost
  4. spring.rabbitmq.port=5672
  5. spring.rabbitmq.username=yeoseong
  6. spring.rabbitmq.password=yeoseong


<Receiver>


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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
package org.rvslab.chapter3;
 
import java.util.Optional;
 
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
 
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.core.RabbitMessagingTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
 
import reactor.core.publisher.Mono;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
 
 
@SpringBootApplication
@EnableSwagger2
public class Application {
 
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
    
    
    @Bean
    CommandLineRunner init(CustomerRespository customerRepository) {
            return (evt) ->  {
                              customerRepository.save(new Customer("Adam","adam@boot.com"));
                              customerRepository.save(new Customer("John","john@boot.com"));
                              customerRepository.save(new Customer("Smith","smith@boot.com"));
                              customerRepository.save(new Customer("Edgar","edgar@boot.com"));
                              customerRepository.save(new Customer("Martin","martin@boot.com"));
                              customerRepository.save(new Customer("Tom","tom@boot.com"));
                              customerRepository.save(new Customer("Sean","sean@boot.com"));
            };
    }
    
 
}
 
 
@RestController
class CustomerController{
        
        CustomerRegistrar customerRegistrar;
        
        @Autowired
        CustomerController(CustomerRegistrar customerRegistrar){
                this.customerRegistrar = customerRegistrar;
        }
        
        @RequestMapping( path="/register", method = RequestMethod.POST)
        Mono<Customer> register(@RequestBody Customer customer){                
                return customerRegistrar.register(customer);     
        }
}
 
@Component
@Lazy
class CustomerRegistrar {
        
        CustomerRespository customerRespository;
        Sender sender;
        
        @Autowired
        CustomerRegistrar(CustomerRespository customerRespository, Sender sender){
                this.customerRespository = customerRespository;
                this.sender = sender;
        }
        
        
        public Mono<Customer> registerMono(Mono<Customer> monoCustomer){
                monoCustomer.doOnNext(customer -> {
                        if(customerRespository.findByName(customer.getName()).isPresent())
                                System.out.println("Duplicate Customer");
                        else {
                                customerRespository.save(customer);
                                //sender.send(customer.getEmail());             
                        }
                }).subscribe();
                return monoCustomer;
        }
 
        // ideally repository will return a Mono object
        public Mono<Customer> register(Customer customer){
                        if(customerRespository.findByName(customer.getName()).isPresent())
                                System.out.println("Duplicate Customer. No Action required");
                        else {
                                customerRespository.save(customer);
                                //외부 큐에게 메시지전송
                                sender.send(customer.getEmail());       
                                System.out.println("Rabbitmq send :::: "+customer.toString());
                        }
                return Mono.just(customer);
        }
}
 
 
//외부 큐와 연결하기 위한 Sender를 빈으로 등록한다.
//또 빈으로 Queue객체를 등록해준다. 해당 문자열로 Receiver쪽에서도 동일하게 받아야한다.
//RabbitMessagingTemplate으로 외부 큐에게 메시지를 전송한다.
@Component
@Lazy
class Sender {
        
        RabbitMessagingTemplate template;
        
        @Autowired
        Sender(RabbitMessagingTemplate template){
                this.template = template;
        }
 
        @Bean
        Queue queue() {
                return new Queue("CustomerQ"false);
        }
        
        public void send(String message){
                template.convertAndSend("CustomerQ", message);
                System.out.println("Ready to send message but suppressed "+ message);
                 
        }
}
 
//repository does not support Reactive. Ideally this should use reactive repository
@RepositoryRestResource
@Lazy
interface CustomerRespository extends JpaRepository <Customer,Long>{
        Optional<Customer> findByName(@Param("name"String name);
}
 
 
//Entity class
@Entity
class Customer{
        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        private Long id;
        private String name;
        private String email;
        
        public Customer (){}
 
        
        public Customer(String name, String email) {
                super();
                this.name = name;
                this.email = email;
        }
 
 
        public Long getId() {
                return id;
        }
 
        public void setId(Long id) {
                this.id = id;
        }
 
        public String getName() {
                return name;
        }
 
        public void setName(String name) {
                this.name = name;
        }
 
        public String getEmail() {
                return email;
        }
 
        public void setEmail(String email) {
                this.email = email;
        }
 
        @Override
        public String toString() {
                return "Customer [id=" + id + ", name=" + name + ", email=" + email + "]";
        }
        
         
        
}
cs


  1. server.port=8090
  2.  
  3. spring.rabbitmq.host=localhost
  4. spring.rabbitmq.port=5672
  5. spring.rabbitmq.username=yeoseong
  6. spring.rabbitmq.password=yeoseong
  7.  
  8. spring.mail.host=localhost
  9. spring.mail.port=2525


부트 프로젝트생성할 때 의존성 설정 부분에서 I/O>AMQP를 선택해준다.



posted by 여성게
:
Tools/Git&GitHub 2019. 2. 16. 13:00

Github - eclipse(이클립스)와 local repository(로컬레포지토리) 연동



이번 포스팅은 간단하게 이클립스와 로컬 레포지토리 연동입니다. 간단하게 flow를 설명하면,


Remote Repository(Github) <-> Local Repository <-> Eclipse(이클립스) 입니다.


혹시나 git에 대한 사전 지식이 없으시다면 이전 포스팅 글을 한번 읽고 와주세요!.

▶︎▶︎▶︎GitHub - 간단한 Git사용법

▶︎▶︎▶︎GitHub - Git 사용법 2



첫번째 작업은 원격 레포지토리 생성과 로컬 레포지토리 생성 및 연결입니다. 해당 설명은

위의 포스팅에서 참조하시길 바랍니다.




이클립스 & Git(로컬 레포지토리) 연동




로컬 레포지토리와 연동할 프로젝트를 우클릭한 후 Team > Share Project를 클릭해줍니다.




Git을 선택한 후에 Next를 클릭해줍니다.





생성한 로컬레포지토리의 상위 폴더를 선택해줍니다.(mkdir한 디렉토리 경로)



끝입니다! 굉장히 간단합니다. 소스를 수정하고 이클립스 내의 Show view > Git을 선택하여 

변경된 파일을 commit해주고 push해주면 로컬레포지토리에 들어가게 됩니다.


그리고 로컬 레포지토리에서 push해주면? Github에 모든 소스가 반영됩니다.



이번 포스팅을 간단히 이클립스와 로컬 레포지토리 연결입니다.
연결 후 사용하시는 기능은 이전 포스팅에서 참조하셔서 사용하면 될듯합니다.


다음 포스팅은 Git Repository를 maven 저장소로 사용하는 방법입니다.!





posted by 여성게
:

Java - You need to run build with JDK or have tools.jar on the classpath 오류




You need to run build with JDK or have tools.jar on the classpath. 

If this occures during eclipse build make sure you run eclipse under JDK as well 

(com.myseam.maven:apt-maven-plugin:1.1.3:process:default:generate-sources)


만약 위와 같은 에러가 pom.xml에 났다면?






project folder 우클릭 > Properties > Java Build Path > Libraries Tab 에서 JRE System Library를 현재 로컬에 

설치되어 있는 JDK의 패스로 잡아줍니다.


그리고 이클립스 eclipse.ini 파일을 열고 -vm 옵션을 사용해서 실제 JDK가 설치된 경로를 추가합니다.

여기서 -vm 위치가 중요한데 다음과 같이 -vmargs 위에 해당 경로를 추가합니다.


....

-vm

$JDKPATH/bin/javaw.exe

-vmargs

...


이클립스를 재실행하고 Maven> Update Project...를 선택해서 해당 프로젝트를 클릭하고

Update 해줍니다.



posted by 여성게
:
일상&기타/IT News 2019. 2. 13. 16:19

웹 브라우저를 통해 마이닝 동작하는 악성코드(가상화폐,암호화폐 악성 채굴기)

HTML/CoinMiner,JAVA/CoinMiner,JS/CoinMiner,WASM/Cryptojs



암호화폐 시장이 달아오름과 동시에 채굴 방식 또한 점차 다양해지고 있다. PC에 악성 채굴기 실행파일을 몰래 설치하는 악성코드뿐만 아니라1, 웹 브라우저를 기반으로 하여 암호화폐를 채굴하는 방식이 2017년부터 폭증하고 있다. 

웹 브라우저 기반으로 동작하는 마이닝 방식은 기존의 실행 파일 기반과는 다르게 악성 자바스크립트(JavaScript)를 이용해 동작한다. 자바스크립트는 추가로 악성 자바(JAVA) 파일 또는 웹어셈블리(WebAssembly) 파일을 로드할 수 있으며, 해당 웹 페이지에 접속했을 때 접속자는 개인 하드웨어 자원을 이용하여 채굴을 하게 되고 그 결과는 공격자의 지갑 주소에 전송된다. 

파일 기반의 악성코드는 최종 페이로드를 사용자 시스템에 전달하기 위해 파일 다운로드 후 실행하기까지 과정이 필요하지만, 웹 기반의 마이닝 악성코드는 단순 웹 페이지 접속 조건만을 만족하면 되기 때문에 공격자 입장에서는 좀 더 손쉬운 채굴 방식이 될 수 있다.

웹 브라우저를 이용한 최초의 마이닝 방식은 암호화폐 가격이 지금보다 낮았던 2011년에 이미 확인된 바 있다. ‘Bitcoin Plus’란 이름의 서비스는 비트코인을 채굴할 목적으로 작성된 자바스크립트를 제공하였다. 이 스크립트는 악성 자바아카이브(JAR) 파일을 다운로드 하고 자바 실행 환경의 시스템에서 채굴하였다.



<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js" type="text/javascript"></script>

<script src="http://www.bitcoinplus.com/js/miner.js" type="text/javascript"></script>

<script type="text/javascript">BitcoinPlusMiner("donny@bitcoinplus.com")</script>

miner.js - 악성 JAR 파일 bitcoinplus-miner.jar 로드

bitcoinplus-miner.jar - 실제 마이닝 기능을 수행

최근에는 암호화폐 종류가 다양해지고 마이닝이 인기가 높아짐에 자바스크립트 마이너 파일 역시 다양하게 제작, 유포되고 있다. 대표적으로는 CoinHive (XMR 채굴)이 있으며 이 외에도 CryptoNoter(XMR, BCN, ETN), DeepMiner(XMR, ETN 외) 등 다양하다. 이러한 마이너 파일들은 GitHub 등 저장소에 소스가 공개되어 있어 접근하기도 쉽다. 자바스크립트 파일 외에 지갑 주소, 프록시, 스레드 개수 등을 설정하는 Config 파일을 별도로 구현하기도 하며 이는 마이너마다 차이가 있다.

공격자는 다음과 같은 방식 등을 이용하여 악성 마이너 파일을 웹 브라우저에 로드할 수 있다. 

  • 개인 사이트 또는 블로그에 악성 자바스크립트 포함
  • 해킹으로 탈취한 사이트에 악성 자바스크립트 포함 (Ex. iframe 태그 이용)
  • 정상 사이트에 포함된 광고 배너에 악성 자바스크립트 포함
다음은 최근 접수된 샘플로 ad1.html 이름의 광고성 페이지 안에 악성 자바스크립트 파일을 로드하는 코드가 삽입되어 있다.

ad1.html – 마이닝 목적의 자바스크립트 파일 로드



로드 된 deepMiner.js 파일은 다음과 같이 난독화 되어 있는 파일이다. 소켓 관리, 마이닝 기능 수행 시작 등 실질적인 기능에 대한 구현 부분만 포함하고 있으며 접속 풀 주소, 호스트, 포트 등의 기본 정보는 파일 내에 없다. 이 부분은 서버에 업로드 된 별도의 설정 파일로 구현 되어있을 것으로 보인다. 

deepMiner.js – 난독화 되어 있음

ad1.html 페이지 접속시 로드 된 deepMiner.js 파일은 채굴을 위한 CryptoNight 알고리즘 수행을 위해 worker.min.js 파일을 로드한다. 

추가 파일 로드

worker.min.js 파일 또한 난독화 되어 있는데 이 파일은 유포지의 /lib/ 경로에 존재하는 해시 알고리즘 구현 파일인 cryptonight.asm.js 파일 또는 cryptonight.wasm 웹어셈블리 파일을 로드하여 메모리에 올리는 역할을 한다. 웹어셈블리 파일은 웹 브라우저 상에서 실행 가능한 컴파일된 바이너리로서 자바스크립트(인터프리터 방식)보다 훨씬 빠른 속도로 실행 가능하다. 다만 웹어셈블리 WASM 파일은 컴파일 된 형태이기 때문에 외형만 봐서는 그 기능을 파악하기 어렵다. 따라서 이를 웹어셈블리를 텍스트 형식 파일인 WAT 파일로 변환하면 부분적으로 그 기능을 파악할 수 있다. 

웹어셈블리 파일 – 해시 알고리즘 구현

웹어셈블리 파일 텍스트 포맷 – 해시 알고리즘 구현



웹어셈블리를 이용한 것은 채굴을 좀 더 빠르고 효과적으로 하기 위한 방식으로, 최근 발견되는 악성코드는 대부분 해시 알고리즘 부분을 웹어셈블리 파일로 구현하여 로드하는 것으로 확인된다.

참고로 웹어셈블리는 브라우저 종류와 버전에 따라 지원 여부가 다르다. 만약 지원하지 않는 브라우저 환경일 경우에는 원본 코드인 자바스크립트 파일을 로드한다. 작성 기준 현재 Internet Explorer 는 웹어셈블리를 지원하지 않는다.2 Internet Explorer 로 ad1.html 페이지를 방문한 사용자는 웹어셈블리 파일 대신 asm.js 자바스크립트 파일이 로딩된다. 

최종적으로 마이닝 기능을 위한 파일이 모두 로드 되면 웹 브라우저의 CPU 사용량이 급등하며 채굴 기능이 수행된다. 

지금까지 살펴본 바와 같이 암호화폐 채굴 악성코드는 채굴자 개인의 PC 자원 혹은 다른 사람의 자원을 이용하기 위해 최근 들어서 그 수가 증가하고 있다. 웹 브라우저에 로드되어 마이닝 기능을 수행되는 방식은 웹브라우저를 종료하면 그 기능이 종료되고 현재까지는 채굴 이외의 기능은 확인되지 않고 있다. 하지만 사용자의 시스템 자원을 무단으로 이용하고 불특정 다수를 대상으로 악성 행위를 하기 때문에 주의를 기울일 필요가 있다. 

V3 제품군에서는 다음과 같은 진단명으로 탐지하고 있다.


HTML/CoinMiner

JAVA/CoinMiner

JS/CoinMiner

WASM/Cryptojs


▶︎▶︎▶︎참조:안랩블로그

posted by 여성게
:

Java - ConnectionTimeout,ReadTimeout,SocketTimeout 차이점?


사실 지금까지 웹개발을 해오면서 ConnectionTimeout,ReadTimeout,SocketTimeout에 대해 대략적으로만 알고있었지 

사실 정확히 설명해봐라 혹은 차이점을 설명해봐라하면 대답하기 힘든 부분이 있었다. 이번 포스팅으로 

정확한 타임아웃 개념을 잡아보려고한다.

 

 

 

ConnectionTimeout이란?

ConnectionTimeout이라는 개념을 설명하기 전에 URL로 HTTP호출을 할 때 어떤 방식으로 수행되는지 이해가 필요하다.웹 브라우저가 네이버 서버에 접속하기 위해서 서버와 연결된 상태가 되어야한다. 보통 연결을 구성하기위해TCP Connection과 동일하게 3-way-HandShake 방식으로 수행하게 된다. 3-way HandShake가 정상적으로
수행하게 되면 웹 브라우저와 네이버 서버는 연결된 상태가 되는데, 이때까지 소요된 시간을 Connection에 소요된 시간이라고 할 수 있다. 

"즉,ConnectionTimeout이란 3-way HandShake가 정상적으로 수행되어 서버에 연결되기까지 소요된시간이다."

 

SocketTimeout이란?

클라이언트와 서버가 연결된 상태 이후에 서버는 페이지를 브라우저에 랜더링하기 위해 데이터를 전송한다. 이때 하나의 패킷으로데이터를 전송하는 것이 아니라 여러 개의 패킷으로 나눠서 전송하게 된다. 여러개의 패킷이 전송될 때, 각 패킷 간의 시간 Gap이생길 수 있는데 이 시간의 임계치를 SocketTimeout이라고 한다.

"즉, SocketTimeout이란 데이터를 여러개의 패킷으로 나누어 보낼때 각 패킷간의 시간 Gap을 이야기한다."

 


위의 두개를 그림으로 표현해보면,

 

결국 위의 두개의 시간설정은 URL을 호출 할때 꼭 필요한 설정이다. 만약 두 시간이 설정되지 않는다면?URL 접속 무한 대기가 발생될 수 있다.

 

ReadTimeout이란?

"Connection 맺은 후 Response(응답)을 받기까지 소요될 시간의 임계치이다."

 

사소하게 넘어 갈 수 있는 것들이지만 네트워크 상에 돌아가는 무엇인가를 개발한다면 꼭 숙지해야 될 것들중 하나인 것 같다.

 

posted by 여성게
:
일상&기타/Apple 2019. 2. 12. 18:00


Apple - AirPods2,에어팟2가 출시된다?


사실 얼마전에 여자친구 것까지 총 2개의 에어팟을 구매했는데, 그때도 사실 에어팟2가 출시된다고 해서 살까 고민하다가 질러 버렸는데.. 진짜 출시되려고 하나봅니다. 기사를 보다 에어팟2 출시 글이 있길래 참조해보았습니다.





▶︎▶︎▶︎네이버기사



■음질향상과 검은 색상이 출시될거란 에어팟2

에어팟2의 디자인 자체는 크게 바뀌지 않을 것 같고, 기타 세부적인 기능의 향상 및 추가가 있을 것이라고 합니다.


사용자의 편의성을 위하여 그립감과 귀에 잘빠지지 않게 하려고 특수 코딩이 이루어질 것이고, 음질 향상을 위해 내부적인 구조가 바뀔 전망이라고 합니다.


에어팟2에는 심방수측정과 같은 건강기능이 탑재된다는 게 아주 큰 이슈 인 것 같습니다.

배터리 수명은 기존 모델과 비슷할 것으로 보인다.


에어팟2은 블랙과 화이트 두 가지 색상으로 제공되며, 가격은 기존 가격보다는 비싼 200달러 정도로 책정될 것 같다고 하지만

아직 확정되지는 않았다고 합니다.


작년 11월 에어팟2 정보가 블루투스 SIG 웹 사이트에서 확인돼 블루투스 5.0 지원이 확인된바 있다.

■ 에어파워 무선충전 패드, 올 봄에 출시



에어파워 무선충전 패드가 올 봄 경 1세대 에어팟 무선충전 케이스와 함께 발표될 예정이다. 공개될 예정인 에어팟 무선충전 케이스의 배터리는 현재의 에어팟 충전 케이스와 비교하여 용량이 약간 작은 것으로 알려져 있다.

에어파워는 원래 예상했던 것보다 약간 두꺼운 편이며, 몇 가지 독점적인 기능이 제공될 예정이지만 이 기능들은 iOS13 출시 전까지는 사용할 수 없을 것으로 매체는 내다봤다. 에어파워는 아이폰XS 맥스, 아이폰XS, 아이폰XR, 아이폰X, 아이폰8, 아이폰8플러스와 호환 가능하다.

에어파워 무선충전 패드의 가격은 약 150달러이며, 현재 에어파워를 위한 TV 광고가 촬영 중이라고 해당 매체는 전했다.

posted by 여성게
:
Tools/Git&GitHub 2019. 2. 12. 13:48

GitHub - Git 사용법 2 (branch, checkout, reset 등)



이전 포스팅에서는 간단한 Git 사용법에 대하여 다루어봤습니다. 이번에는 조금 더 나아가서 branch, tag, 잘못 반영된 작업을 되돌리는 작업 등 조금 더 진화된 예제를 다루어보려고합니다. 혹시 이전 포스팅을 보시지 못하셨다면 이전 포스팅을 참고하시고 오시면 좋을 듯 싶습니다. 혹시라도 대부분의 기본 명령어들이 숙지 되어있으시다면 굳이 보시지 않으셔도 됩니다.


▶︎▶︎▶︎GitHub - 간단한 Git사용법(로컬 레포지토리,원격 레포지토리)









로컬 저장소는 git이 관리하는 세그루의 나무로 구성되어 있습니다.

첫번째 나무인 작업 디렉토리(Working Directory)는 이전 포스팅에서 생성한 git 위한 로컬디렉토리입니다.

두번째 Index는 staging 역할을 합니다.("add 명령어로 파일을 관리하에 추가")

마지막 세번째 HEAD는 commit된 스냅샷, 즉 최종 확정본을 나타냅니다.




Branch 만들기 (일명 가지치기)

master라는 최종본의 형상이 있고, 무엇인가를 변경해야하는 작업이 생겨났습니다.

만약 master branch에 직접 수정을 하면? 큰일 날 수도 있겠죠.... 그렇기 때문에

새로운 Branch를 하나 따서 원하는 작업을 진행 할 수 있게 됩니다.

이제 새로운 작업을 새로운 Branch에서 진행하고 개발이 완료된다면

master 가지로 돌아와 병합을 하면 됩니다.










"git checkout -b yeoseong_branch" 라는 명령어로 새로운 Branch 하나를 생성함과 동시에
그 Branch로 스위칭 해줍니다.
"git status" 명령으로 현재 어떠한 Branch에 있는지 확인할 수 있습니다.
만약 다시 Master Branch로 돌아가고 싶다면 "git checkout master"로 돌아가시면 됩니다.
다른 Branch 역시 동일합니다.

만약 Branch를 삭제하고 싶다면 "git branch -d yeoseong_branch"로 삭제해줍니다.







이제 작업을 진행하기 위하여 수정할 소스를 다른 Branch에서 pull 해옵니다.
"git pull origin master"






새로운 파일도 생성했습니다.
이전 포스팅과 똑같은 과정입니다. "git add yeoseong_yoon.txt" 로 
git이 관리할 수 있는 파일로 만들어주고 commit 하여 HEAD에 최종본을
반영합니다.





이제는 변경하려고 pull 했던 파일을 수정합니다.

"add" 하지않은 파일은 "git status" 명령에 수정되었으니 

"add" 해라라는 상태를 보여줍니다.





만약 파일을 add 하지 않고 commit 한다면 이러한 예외문구를 만날 수 있을 겁니다.









파일을 모두 변경하고 commit까지 해놓은 상태입니다. 이제 commit된 모든 변경사항을

원격 레포지토리에 push합니다.




깃허브에 접속해보니 yeoseong_branch에 변경사항들이 모두 잘 적용이 되었습니다.




변경사항을 master Branch에 merge

이제는 변경사항이 모두 yeoseong_branch로 적용되었으니(개발완료) 

master branch에 병합하는 작업입니다.





현재 branch를 master로 스위치 해줍니다. 그리고 yeoseong_branch에 있는 변경내용과
master의 내용을 병합해줍니다.
"git merge yeoseong_branch" 명령어는 현재 Branch와 target Branch를
병합해라 라는 명령어입니다.







master에 추가된 파일과 변경된 파일이 잘 들어온것이 보입니다.
이제는 이것을 원격 레포지토리에 push 해줍니다!



와우... 깃허브에 master Branch를 보면 변경 사항이 잘 merge 된것을 볼 수 있습니다.!!!



로컬 레포지토리에서 잘못 반영되어 되돌리고싶을때(Checkout OR Reset,Fetch)



만약 commit하지 않았다면 "git checkout -- filename"으로 변경내용을 변경 전 상태(HEAD)로 되돌려줍니다.

다만, 이미 인덱스에 추가된 변경 내용과 새로 생성한 파일은 그대로 남습니다.

but add 하지않아서 index에 들어가지도 않았다면 완벽한 reset입니다.






만약 위와 같이 이미 인덱싱했고 commit까지 했다고 가정하면 



"git fetch origin" 명령어를 치고,

"git reset --hard orgin/master" 명령어로 리셋을 합니다. 그렇다면

결과를 보듯이 commit된 것이 없어졌습니다.



그렇다면 여기서

Pull & Fetch의 차이점이란 무엇인가?

Fetch : 중앙 저장소의 소스를 로컬 저장소로 가져온다!  그러나 현재 작업중인 소스들을 변경하는 Merge 작업을 하지는 않는다

즉, 변경된 소스는 버려지고 원격저장소의 소스로 업데이트가 된다.

Pull : 중앙 저장소의 소스를 로컬 저장소로 가져온다! 또한 현재 작업중인 소스들의 Merge 작업까지 통합하여 수행한다

변경된 소스 + 원격저장소 소스가 merge된다.


posted by 여성게
: