2017년 8월 2일 수요일

외부 인증 정보를 기반으로 Authentication 처리

자제 로그인처리가 아닌 외부연동을 통한 로그인 선처리가 필요한 경우가 있다.

spring security를 사용하여 인증 처리를 하던 프로젝트의 경우 어떤 부분을 건들여야 할지 애매모호한 부분이 있다.

해당 기능 처리를 위해서 본인은 filter를 이용하여 BasicAuthenticationFilter가 실행되기 전에 전처리로 해당 세션에 authentication 정보를 주입하는 방식으로 처리를 하였다.


선처리 filter 코드 일부

1.해당 context에 인증 정보가 있는지 체크 (현재 인증된 정보가 있으면 스킵)
2.인증 정보를 연동 및 습득
3.해당 정보 기반으로 현재 context 인증 정보를 입력(인증은 security의 Authentication interface  기반으로 필요한 구현체로 선택해서 결정)

 if(!SecurityContextHolder.getContext().getAuthentication().isAuthenticated()){
                Map<String,Object> loginInfo = (Map<String,Object>)request.getSession().getAttribute("loginInfo");
                if(loginInfo !=null){
                    LoginOperator loginOperator = loginService.searchLoginOperator((String)loginInfo.get("oprtrId"));
                    boolean enabled = true;
                    boolean accountNonExpired = true;
                    boolean credentialsNonExpired = true;
                    boolean accountNonLocked = true;      
                    Collection<? extends GrantedAuthority> authorities = getAuthorities();
                    if(loginOperator !=null){
                        Authentication authentication = new UsernamePasswordAuthenticationToken(new LoginUser(
                                String.valueOf(loginOperator.getLoginId()),
                                loginOperator.getLoginPassword(),
                                enabled,
                                accountNonExpired,
                                credentialsNonExpired,
                                accountNonLocked,
                                getAuthorities(),
                                new ArrayList<String>(),
                                loginOperator.getOperatorNumber(),
                                loginOperator.getLoginId(),
                                loginOperator.getOperatorName()
                                ),  loginOperator.getLoginPassword(), authorities);
                        //
                        SecurityContextHolder.getContext().setAuthentication(authentication);
                    }
                }
            }

2017년 8월 1일 화요일

Consul에 대해서

Consul에 대해서 대략적으로 설명하고자 한다. 
HashiCorp에서 만든 OpenSource로 기존의 Zookeeper의 역활을 하는 Application으로 보면 된다. 

아래는 해당 consul 홈페이지에 기제된 내용으로 기본적으로 4가지으로 항목으로 구분하여 설명하고 있다.

Service Discovery/Failure Detection/Multi Datacenter/KV Storage

Service Discovery
DNS 또는 HTTP 인터페이스를 통해 서비스를 스스로 등록하고 다른 서비스를 발견 할 수 있도록합니다. SaaS(Software as a Service) 제공되는 외부 서비스에 또한 등록할 수 있다 

Failure Detection
각 노드에 대해서 health check를 통하여 서비스 정상 여부를 체크하고 Service Discovery를 하거나 서비스가 가능한 노드로 routing을 제공한다. 

Multi Datacenter
여러개의 Datacenter를 구축하고 Datacenter간 WAN GOSSIP 프로토콜을 이용하여 정보를 조회 할 수 있도록 기능 제공한다. 

KV Storage
Key/Value를 저장할 수 있는 저장소를 제공하여 서비스 별 Config 정보나 특정 데이타를 저장하여 instance간 config 정보 수정이나 데이타 공유를 할 수 있도록 지원한다. 


아래의 이미지는 Stackshare에서 consul vs Zookeeper vs Eureka를 비교한 화면이다. 
선호도나 사용 횟수면에서 Consul이 우월 하다는 것이 보여집니다. 

저도 stackshare의 데이타를 신용하여 해당 Consul을 이용하여 Monitoring 시스템을 구현하는 것으로 결정하였습니다. 



공식 사이트https://www.consul.io

Consul Architecture
consul 공식 사이트에 등록된 Architecture 이미지이다.

Consul의 Check 기능을 이용하여 해당 서버의 Resource 현황과 health 체크를 할 수 있도록 Consul 서버 및 클라이언트를 설치하고 설정을 진행 하도록 하겠습니다. 

Consul Agent 서버 노드 설치
 - Master 서버에 consul web-ui를 같이 설치


압축 해제

/data/consul
/data/consul-ui  (디렉토리 이름 주의)
두개의 디렉토리를 생성 후  web_ui관련 파일을 다운받아 압축을 푼다.
  
  cd  /data/consul-ui
  unzip  consul_0.8.5_web_ui.zip

  consul keygen
해시값을 생성성
이 해시값으로 모든 서버의 환경설정(encrypt) 값을 설정해야하니 꼭 저장해둘것!

  vim  /etc/consul.d/server/config.json
config 파일 생성

 config.json 내용은 아래와 같이 설정한다.

{
  "bootstrap": true,
  "server": true,
  "datacenter": "test",
  "ui_dir": "/data/consul-ui",  // <- 디렉토리 경로 확인
  "data_dir": "/data/consul",
  "encrypt": "위에서얻은해시값",
  "addresses": {
      "http": "외부망IP"
  }, 
  "bind_addr": "내부망 IP",
  "advertise_addr": "외부망 IP",
}

consul 서버 시작
nohup consul agent --server -config-dir=/etc/consul.d/server -bind=서버IP &

config-dir 옵션으로 config 파일 위치 설정 

- 위와같이 설정하고 위의 명령어로 실행한뒤에 localhost:8500으로 접속하면 404에러가 남
  이때 sudo reboot를하면 위에서 만들어둔 /data/consul-ui 디렉토리가 삭제되어 있는데
  디렉토리를 다시 만들고 1번 명령어로 실행하면 정상 작동함



Consul Agent 설치 방법 

압축 해제한 consul 아래의 경로로 복사/이동 시킨다.
  cp  ./consul   /usr/local/bin
  mv  ./consul  /usr/bin
설치 확인을 위해 consul version 명령 실행

- 아래의 디렉토리를 만든다.
  /data/consul

- 아래의 경로에 config.json 파일을 만든다
  vim  /etc/consul.d/agent/config.json

 config.json 내용은 아래와 같이 설정한다.

{
  "server": false,
  "datacenter": "test",
  "data_dir": "/data/consul",
  "encrypt": "7mASsjRU1X0ORmQyymo9Hw==",
  "start_join": ["192.168.1.155"]   // <- Array형식으로 입력해야됨
}

- Agent 실행
  (1) consul agent  -config-dir=/etc/consul.d/agent
  (2) nohup consul agent  -config-dir=/etc/consul.d/agent -bind=AgentIP &
  정상적으로 실행되었다면 서버쪽 웹화면의 [node]탭에 Agent가 추가된다.



# consul을 서버(개발) 모드로 실행하는 방법
  consul  agent  -dev

# 터미널을 하나 떠 띄워서 아래와 같은 명령어를 치면 Cluster Members를 볼 수 있다
  consul members  [-detailed]


# consul에서 사용하는 포트 번호

  SERVER RPC : 8300/tcp
  Serf LAN : 8301/tcp&udp
  Serf WAN : 8302/tcp&udp
  CLI RPC : 8400/tcp
  HTTP API : 8500/tcp (UI접속용)
  DNS Interface : 8600/tcp&udp

 ufw  allow  8300/tcp 
 ufw  allow  8301/tcp 
 ufw  allow  8500/tcp 
 ufw  allow  8600/tcp


# # 서비스 정의 (Defining a Service)
  서비스는 json을 통해 정의하는 방법과 HTTP API를 이용해 정의하는 두가지 방법이 있다.

- /etc/consul.d/server 폴더를 만든다
- 다음의 명령으로 [80번 포트를 사용하고, rails라는 태그를 가진 "web"이라는 서비스]를 만든다.

  echo '{"service": {"name": "web", "tags": ["rails"], "port": 80}}' | sudo tee /etc/consul.d/server/web.json

- 서비스를 등록하기 위해 다음의 명령어로 Consul을 재시작한다.
  consul agent -dev -config-dir=/etc/consul.d/server
  "Synced service 'web'"라는 출력을 통해 웹 서비스를 "동기화"한 것을 알 수 있다.
  이는 에이전트가 위에서 만든 json 파일에서 서비스 정의를 로드했으며
  서비스 카탈로그에 성공적으로 등록했음을 의미한다.
  추가 서비스 역시 위와같은 방식으로 만들 수 있다.

- agent가 시작되고 서비스가 동기화되면 DNS HTTP API를 사용해 서비스를 쿼리할 수 있다.

# DNS API를 이용한 쿼리 방법

- dig  @127.0.0.1  -p 8600 web.service.consul
- dig  @127.0.0.1  -p 8600 web.service.consul  SRV

- DNS API를 이용해 태그로 필터링해서 서비스를 요청하는 방법
  dig  @127.0.0.1  -p 8600 rails.web.service.consul

# HTTP API를 이용한 쿼리 방법

   주어진 서비스를 호스팅하는 모든 노드를 출력한다

[
    {
        "ID": "15e1a433-8f0a-2996-cfdd-ed788d9c6a83",
        "Node": "ef06bc916151",
        "Address": "127.0.0.1",
        "Datacenter": "dc1",
        "TaggedAddresses": {
            "lan": "127.0.0.1",
            "wan": "127.0.0.1"
        },
        "NodeMeta": {},
        "ServiceID": "web",
        "ServiceName": "web",
        "ServiceTags": [
            "rails"
        ],
        "ServiceAddress": "",
        "ServicePort": 80,
        "ServiceEnableTagOverride": false,
        "CreateIndex": 6,
        "ModifyIndex": 6
    }
]

   노드 중에 healthy한것만 출력 (문제없이 작동하는것만 출력?)   





----------------------------------------------------------------------------------

# AWS에서 설치하는 방법

---------------------------------------------------------------------------------

### Health HTTP Request ===================================


# List Checks for Node
  node별로 모든 Check를 요청한다.
      "Client" 노드의 모든 Check json 형식으로 출력한다.
# List Checks for Service
  Service별로 모든 Check를 요청한다.
# List Nodes for Service
  지정한 서비스에 대한 결과 출력
  http://192.168.1.155:8500/v1/health/service/consul
# List Checks in State
 모든 Server/Agent에 대해 상태(state) passing인것을 출력

### SERVICE ==========================================


서비스는 아래와 같은 json 형식으로 정의할 수 있다.
agent 실행시 -config-file의 옵션으로 지정하거나 -config-dir 명령줄 옵션으로 
서비스 파일의 경로를설정해서 읽어들일 수 있다.
HTTP API를 사용해서 동적으로 등록 가능하다.

# 개별 서비스 설정 예제
{
  "service": {
    "name": "redis",
    "tags": ["primary"],
    "address": "",
    "port": 8000,
    "enableTagOverride": false,
    "checks": [
      {
        "script": "/usr/local/bin/check_redis.py",
        "interval": "10s"
      }
    ]
  }
}
... or ...
{  
   "service":{  
      "name":"web",
      "tags":[ "rails" ],
      "port":80
   }
}

# 멀티 서비스 설정 예제
{
  "services": [              // services처럼 s가 붙고, Array형식([])이다.
    {
      "id": "red0",
      "name": "redis",
      "tags": [
        "primary"
      ],
      "address": "",
      "port": 6000,
      "checks": [
        {
          "script": "/bin/check_redis -p 6000",
          "interval": "5s",
          "ttl": "20s"
        }
      ]
    },
    {
      "id": "red1",
      "name": "redis",
      "tags": [
        "delayed",
        "secondary"
      ],
      "address": "",
      "port": 7000,
      "checks": [
        {
          "script": "/bin/check_redis -p 7000",
          "interval": "30s",
          "ttl": "60s"
        }
      ]
    },
    ...
  ]
}

name은 필수고, 이외의 값은 선택적으로 사용 가능하다.
id는 지정하지 않을경우 name값으로 설정되지만, 노드마다 고유값을 가져야하기 때문에
가급적 고유한 이름으로 직접 지정해주는게 좋다.
tags는 노드나 서비스에서 필터링 조건으로 사용된다.
서비스마다 IP port번호를 지정할 수 있는데, address/port에서 지정해주면되지만,
기본적으로 agent ip port번호가 지정되기 때문에 별도로 지정할 필요는 없다.
check/checks에 가능한 속성은 script, HTTP, TCP, TTL이고, script, http, tcp 속성이
사용될경우 꼭 interval 속성을 사용해 시간간격을 설정해줘야한다. (10s = 10)
ttl ttl만 제공하면된다.
check/checks 이름은 service:<service-id> 형식으로 자동 생성된다
enableTagoverride는 이 서비스의 anti-entropy기능을 비활성화 시키기 위해 선택적으로 사용한다.