반응형
반응형
#/**───────────────────────────────────────────────────────────
#
#                  오타교정어
#
#───────────────────────────────────────────────────────────*/


# ─── 오타교정어 생성
PUT typo_correction
{
  "mappings":{
    "properties":{
        "right_word":{
          "type" : "keyword"
        },
       "wrong_words":{
          "type":"nested",
          "properties":{
             "wrong_words":{
                "type":"keyword"
             }
          }
       }
    }
  }
}

# ─── 오타교정어 단건 INSERT
PUT typo_correction/_bulk
{"index" : {}}
{"right_word":"짜장","wrong_words":{"wrong_words":["쨔장","차장"]}}


# ─── 오타교정어 검색
GET typo_correction/_search
{
  "query": {
    "nested": {
      "path": "wrong_words",
      "query": {
        "bool": {
          "must": [
            {
              "match": {
                "wrong_words.wrong_words": "참봉"
              }
            }
          ]
        }
      }
    }
  },"size":1
}

# ─── 조회된 레코드 삭제
POST typo_correction/_delete_by_query
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "right_word": "짬뽕"
          }
        }
      ]
    }
  }
}

# ─── 오타교정어 삭제
DELETE typo_correction


#/**───────────────────────────────────────────────────────────
#
#                  인기검색어
#
#───────────────────────────────────────────────────────────*/

# ─── 인기검색어 생성
PUT popular
{
   "mappings":{
      "properties":{
         "date":{
            "type":"integer"
         },
         "index":{
            "type":"keyword"
         },
         "keyword":{
            "type":"keyword"
         },
         "search_time":{
            "type":"integer"
         },
         "time":{
            "type":"long"
         }
      }
   }
}

# ─── 인기검색어 단건 INSERT
PUT popular/_bulk
{"index" : {}}
{"keyword" : "코스프레", "index" : "covid", "date" :20230402 , "search_time" : 141130 , "time" : 20230402141130}



# ─── 시간 인기검색어 조회
GET popular/_search
{
   "query":{
      "bool":{
         "must":[
            {
               "bool":{
                  "should":[
                     {
                        "match":{
                           "keyword":"코스프레"
                        }
                     }
                  ]
               }
            },
            {
               "bool":{
                  "filter":[
                     {
                        "range":{
                           "time":{
                              "lte":"20230402141130"
                           }
                        }
                     },
                     {
                        "range":{
                           "time":{
                              "gte":"20230402133056"
                           }
                        }
                     }
                  ]
               }
            },
            {
               "bool":{
                  "should":[
                     {
                        "match":{
                           "index":"covid"
                        }
                     }
                  ]
               }
            }
         ]
      }
   },
   "size":10,
   "aggs":{
      "keyword":{
         "terms":{
            "field":"keyword"
         }
      }
   }
}


# ─── 날짜 인기검색어 조회
GET popular/_search
{
  "query":{
    "bool": {
      "filter": [ 
        { "range": { "date": { "gte": "20211220" }}}
      ]
    }
  },
  "size": 0,
  "aggs": {
    "keyword": {
      "terms": {
        "field": "keyword"
      }
    }
  }
}



# ─── 조회된 레코드 삭제
POST popular/_delete_by_query
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "keyword": "종식"
          }
        },
        {
          "match": {
            "index": "covid"
          }
        }
      ]
    }
  }
}

# ─── 인기검색어 삭제
DELETE popular


#/**───────────────────────────────────────────────────────────
#
#                  자동완성어
#
#───────────────────────────────────────────────────────────*/


# ─── 자동완성어 생성
PUT auto_keyword
{
   "mappings":{
      "properties":{
         "auto_keyword":{
            "type":"keyword"
         },
         "count":{
            "type":"integer"
         }
      }
   }
}

# ─── 자동완성어 단건 INSERT
PUT auto_keyword/_bulk
{"index" : {}}
{"auto_keyword" : "금칙어"}


# ─── 자동완성어 조회
GET auto_keyword/_search
{
  "query": {
    "prefix": {
      "auto_keyword": {
        "value": "코로나"
      }
    }
  },
  "collapse":{"field":"auto_keyword"},
   "sort" : [
      { "count": "desc" }
  ]
}


# ─── 조회된 레코드 삭제
POST auto_keyword/_delete_by_query
{
  "query": {
    "match": {
      "auto_keyword": "코로나도"
    }
  }
}

# ─── 자동완성어 삭제
DELETE auto_keyword




#/**───────────────────────────────────────────────────────────
#
#                  금칙어
#
#───────────────────────────────────────────────────────────*/

# ─── 금칙어 생성
PUT forbidden
{
   "mappings":{
      "properties":{
         "forbidden_word":{
            "type":"keyword"
         }
      }
   }
}

# ─── 금칙어 단건 INSERT
PUT forbidden/_bulk
{"index" : {}}
{"forbidden_word" : "금칙어"}


# ─── 금칙어 조회
GET forbidden/_search
{
  "query": {
    "term": {
      "forbidden": {
        "value": "금칙어"
      }
    }
  }
}


# ─── 조회된 레코드 삭제
POST forbidden/_delete_by_query
{
  "query": {
    "match": {
      "forbidden_word": "코로나도"
    }
  }
}

# ─── 금칙어 조회
DELETE forbidden


#/**───────────────────────────────────────────────────────────
#
#                  기본 사전 고도화 분석
#
#───────────────────────────────────────────────────────────*/
PUT normal_dictionary_analyzer
{
   "settings":{
      "analysis":{
         "tokenizer":{
            "korean_nori_tokenizer":{
               "type":"nori_tokenizer",
               "decompound_mode":"none"
            }
         },
         "analyzer":{
            "nori_analyzer":{
               "tokenizer":"korean_nori_tokenizer",
               "filter":[
                 "nori_posfilter"
               ]
            }
         },
         "filter":{
            "nori_posfilter":{
               "type":"nori_part_of_speech",
               "stoptags":[
                  "J",
                  "SP", "SSO", "SSC", "SH", "SF", "SE", "SC", "SY"
                ]
            }
          }
      }
   }
}



# ─── 기본 사전 고도화 분석기 재기동(신조어 즉시 적용)
POST normal_dictionary_analyzer/_close
POST normal_dictionary_analyzer/_open

# ─── 기본 사전 고도화 분석기 삭제
DELETE normal_dictionary_analyzer

# ─── 기본 사전 고도화 분석기 정보 조회
GET normal_dictionary_analyzer/_settings

# ─── 기본 사전 고도화 분석기 문장 상세 분석
GET normal_dictionary_analyzer/_analyze
{
  "analyzer": "nori_analyzer",
  "text":"고소득자",
  "attributes": ["leftPOS", "rightPOS"], 
  "explain": true
}

# ─── 기본 사전 고도화 분석기 문장 분석
GET normal_dictionary_analyzer/_analyze
{
  "analyzer": "nori_analyzer", 
  "text": ""
}



#/**───────────────────────────────────────────────────────────
#
#                  기본 인덱스 생성
#
#───────────────────────────────────────────────────────────*/

# ─── 인덱스 재기동(신조어 즉시 적용)
POST sample/_close
POST sample/_open

# ─── 인덱스 정보 조회
GET sample/_settings

# ─── 분석기 삭제
DELETE sample

# ─── 일반 인덱스 / 분석기 생성 [기본 분석기]
PUT sample
{
   "settings":{
      "analysis":{
         "tokenizer":{
            "korean_nori_tokenizer":{
               "type":"nori_tokenizer",
               "decompound_mode":"none",
               "user_dictionary":"dictionary/newly_coined_word/user_dictionary.txt"
            }
         },
         "analyzer":{
            "nori_analyzer":{
               "tokenizer":"korean_nori_tokenizer",
               "filter":[
                 "nori_posfilter"
               ]
            }
         },
         "filter":{
            "nori_posfilter":{
               "type":"nori_part_of_speech",
               "stoptags":[
                  "J",
                  "SP", "SSO", "SSC", "SH", "SF", "SE", "SC", "SY"
                ]
            }
          }
      }
   }
}

# ─── 분석기 문장 상세 분석
GET sample/_analyze
{
  "analyzer": "nori_analyzer",
  "text":"사용한다",
  "explain": true
}

# ─── 분석기 문장 분석
GET sample/_analyze
{
  "analyzer": "nori_analyzer", 
  "text":"사용한다"
}


#/**───────────────────────────────────────────────────────────
#
#                  인덱스 정보
#
#───────────────────────────────────────────────────────────*/

# ─── 형태소 분석기
GET covid/_analyze
{
  "analyzer": "nori_analyzer", 
  "text":"재발견",
  "explain": true
}

# ─── 스코어 확인하기
PUT score/_doc/1
{
  "contents": "대구·경북 폭염특보…폭염 종합 대책 실시 [KBS 대구] .. ..[앵커] .. .. 오늘 절기상 가장 덥다는 대서인데요, .. .. 연일 무더운 날씨가 이어지면서 대구.경북에도 보름째 폭염특보가 이어지고 있습니다. .. .. 취재기자 연결합니다. .. .. 윤희정 기자,.. 대구시는 코로나 상황을 감안해 실내 무더위 쉼터 대신 강변이나 공원 등에 실외 무더위 쉼터 4백여 곳을 조성했습니다. .."
}



#/**───────────────────────────────────────────────────────────
#
#                  검색 옵션
#
#───────────────────────────────────────────────────────────*/


# ─── 특정 필드만 출력 DSL (title 필드, company 필드만 노출)
# ─── select=title,company
GET covid/_search
{
  "fields": [
    "title",
    "company"
  ],
  "_source": false
}

# ─── 10000건 이상의 결과를 주기 위한 옵션
GET auto_keyword/_search
{
  "track_total_hits": true
}



# ─── offset 및 limit 
# ─── 시작 인덱스 위치 및 보여줄 개수 제한 (0번째 인덱스로부터 30개 보여주기)
# ─── offset=0&limit=30
GET covid/_search
{
  "from" : 0,
  "size" : 30
}



#/**───────────────────────────────────────────────────────────
#
#                  검색
#
#───────────────────────────────────────────────────────────*/


# ─── anyword
# ─── 키워드 or 조건절 (마스크가 있거나 덴탈이 있어야한다.)
# ─── contents = "마스크 덴탈" anyword
GET covid/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "contents": "마스크 덴탈"
          }
        }
      ]
    }
  }
}

# ─── allword
# ─── 키워드 and 조건절 (마스크도 있고 덴탈도 있어야한다.)
# ─── contents = "마스크 덴탈" allword
GET search/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "contents": {
              "query" : "그린",
              "operator" : "and"
            }
          }
        }
      ]
    }
  }
}

# ─── and
# ─── 같은 키워드 and 조건절 (contents의 코로나가 포함되면서 title의 코로나가 포함) 
#     [두개 필드에 키워드가 둘다 존재해야 함]
# ─── contenst = '코로나' and title = '코로나'
GET covid/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "contents": "코로나"
          }
        },
        {
          "match": {
            "title": "코로나"
          }
        }
      ]
    }
  }
}

# ─── 멀티 필드 allword
# ─── 키워드 and 조건절 (contents에 폭염과 더위가 있어야하거나 + title에 폭염과 더위가 있어야한다.)
# ─── contents,title = '폭염 더위' allword
GET covid/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "multi_match": {
            "query": "폭염 더위",
            "fields": ["contents", "title"],
            "operator": "and"
          }
        }
      ]
    }
  }
}

# ─── 멀티 필드 anyword
# ─── 키워드 or 조건절 
#     (contents에 폭염 또는 더위가 있어야하거나 + title에 폭염 또는 더위가 있어야한다.) 
#     [두개 필드에 키워드가 둘다 존재해야 함] 
# ─── contents, title = '폭염 더위' anyword
GET covid/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "multi_match": {
            "query": "폭염 더위",
            "fields": ["contents", "title"]
          }
        }
      ]
    }
  },
    "highlight":{
    "pre_tags":["<em>"],
    "post_tags":["</em>"],
    "fields":{
        "contents":{},
        "title" : {}
    }
  }
}

# ─── 멀티 필드 anyword와 다른점 = (멀티 필드 anyword는 contents 또는 title 중에서 스코어가 높은게 위로가지만 or절로 묶은 건 스코어를 합산한다.)
# ─── contents, title = '폭염 더위' anyword   
#       vs  
#     (contents = '폭염 더위' anyword) or (title = '폭염 더위')
GET covid/_search
{
  "size" : 80,
  "query": {
    "bool": {
      "should": [
        {
          "multi_match": {
            "query": "폭염 더위",
            "fields": ["contents"]
          }
        },
        {
          "multi_match": {
            "query": "폭염 더위",
            "fields": ["title"]
          }
        }
      ]
    }
  }
}


# ─── (contents, title = "코로나" allword and wirter="윤희정" anyword) 
#      or 
#     (contents, title = "코로나" allword and writer = "박종일" anyword)
GET covid/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "bool": {
            "must": [
             {
                "multi_match": {
                  "query": "코로나",
                  "fields": ["contents", "title"],
                  "operator": "and"
                }
              },
              {
                "multi_match": {
                  "query": "윤희정",
                  "fields": ["writer"]
                }
              }
            ]
          }
        },
        {
          "bool": {
            "must": [
             {
                "multi_match": {
                  "query": "코로나",
                  "fields": ["contents", "title"],
                  "operator": "and"
                }
              },
              {
                "multi_match": {
                  "query": "박종일",
                  "fields": ["writer"]
                }
              }
            ]
          }
        }
      ]
    }
  }
}


# ─── (contents, title = "코로나" allword and wirter="박종일" anyword) 
#      and 
#     (contents, title = "코로나" allword and company != "KBS")
GET covid/_search
{
  "query": {
    "bool": {
      "filter": [ 
        { "range": { "reg_date": { "gte": "20221220" }}}
      ],
      "must": [
        {
          "bool": {
            "must": [
             {
                "multi_match": {
                  "query": "코로나",
                  "fields": ["contents", "title"],
                  "operator": "and"
                }
              },
              {
                "multi_match": {
                  "query": "박종일",
                  "fields": ["writer"]
                }
              }
            ]
          }
        },
        {
          "bool": {
            "must": [
             {
                "multi_match": {
                  "query": "코로나",
                  "fields": ["contents", "title"],
                  "operator": "and"
                }
              }
            ],
            "must_not": [
              {
                "multi_match": {
                  "query": "KBS",
                  "fields": ["company"]
                }
              }
            ]
          }
        }
      ]
    }
  }
}




#/**───────────────────────────────────────────────────────────
#
#                  구간 조회
#
#───────────────────────────────────────────────────────────*/


# ─── range = reg_date > 20221226
GET covid/_search
{
  "query": {
    "bool": {
      "filter": [ 
        { "range": { "reg_date": { "lte": "20221226" }}}
      ]
    }
  }
}

# ─── 멀티 필드 allword  + 날짜 조회 
#     (날짜는 무조건 구간이여야한다   <--- |    | ---->  이딴식 불가능)
# ─── 키워드 and 조건절 
#     (contents에 폭염과 더위가 있어야하거나 title에 폭염과 더위가 있어야한다.)
# ─── contents, title = "폭염 더위" allword & range = reg_date > 20221226
GET covid/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "multi_match": {
            "query": "폭염 더위",
            "fields": ["contents", "title"],
            "operator": "and"
          }
        }
      ],
      "filter": [ 
        { "range": { "reg_date": { "gte": "20221206" }}},
        { "range": { "reg_date": { "lte": "20221222" }}}
      ]
    }
  }
}


#/**───────────────────────────────────────────────────────────
#
#                  정렬 (type이 integer여야한다)
#
#───────────────────────────────────────────────────────────*/

# ─── order = reg_date asc
GET covid/_search
{
 "sort" : [
      { "reg_date": "asc" }
  ]
}


# ─── order = reg_date asc
GET covid/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "multi_match": {
            "query": "폭염 더위",
            "fields": ["contents", "title"],
            "operator": "and"
          }
        }
      ]
    }
  },
   "sort" : [
      { "reg_date": "asc" }
  ]
}


#/**───────────────────────────────────────────────────────────
#
#                  하이라이팅
#
#───────────────────────────────────────────────────────────*/

# ─── highlight-start=<em>&highlight-end=</em>&highlight-fields=contents
GET covid/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "contents": "마스크 덴탈"
          }
        }
      ]
    }
  },
  "highlight":{
    "pre_tags":["<em>"],
    "post_tags":["</em>"],
    "fields":{
        "contents":{}
    }
  }
}
──── 검색엔진 셋팅 ────
1. sudo vi /etc/security/limits.conf 

2. 하기 내용 추가
my_search soft memlock unlimited
my_search soft fsize unlimited
my_search hard nofile 65536
my_search soft nofile 65536
my_search hard nproc 65536
my_search soft nproc 65536

3. sudo vi /etc/sysctl.conf 

4. 하기 내용 추가
vm.max_map_count=262144

─── fargate 적용 시 셋팅 값
1. vi ${설치경로}/es/elastic/config/elasticsearch.yml 

2. 하기 내용 추가
node.store.allow_mmap: false
transport.host: localhost
transport.tcp.port: 9300


─── 검색엔진 Dockerfile 내용 ───
FROM company/my_search:1.1

USER search

EXPOSE 9200
EXPOSE 8021

WORKDIR /home

CMD ./start.sh
───────────────────
─── start.sh 내용 ───

${설치경로}/service/bin/startup.sh
${설치경로}/es/elastic/bin/elasticsearch

───────────────────

 

 

참고자료

반응형
반응형

📝세션 변조를 이용한 불법 로그인

로그인시 WAS에 세션을 만들고 해당 세션에 대한 정보(로그인 정보, 장바구니 등)는 서버에 저장되지만 해당 유저가 어떤 유저인지에 대한 키가 있어야합니다. 그래서 계속적인 체크를 위해서 해당 키를 쿠키에 저장해서 사용자에게 보냅니다

그리고 데이터 및 서비스를 요청할때마다 세션의 쿠키의 ID를 같이 보냅니다

이러한 과정에서 해당 Session ID 값인 쿠키를 탈취하게 되어서 내쪽에 입력하게 되면 해당 아이디로 로그인이 되거나 해당 사용자가 이용한 것들(장바구니 등...)을 사용할 수 있게 됩니다

 

이러한 것들을 훔치기 위해서 XSS같은 공격을 사용해 Javascript를 실행하게 쿠키를 읽게하는 스크립트를 이용해 훔쳐갈 수 있습니다

 

방지 방법으로 다양하게 있습니다

  1. 웹 방화벽 사용 (제품이라 돈 필요)
  2. 보안 장비에 쿠키 값 변조 감지 제품 있음 (제품이라 돈 필요)
  3. 쿠키 변조 탐지 및 단순 쿠키 체크가 아닌 다양한 2차 인증 과정 구현

뭐 위에 3가지도 누군가 개발을 한 거이기 때문에 결국엔 개발이 필요합니다

관리자페이지에서 F12로 들어가서 보면 JSESSIONID라는 키로 확인이 가능합니다

반응형
반응형
import java.io.IOException;

import org.springframework.core.io.ClassPathResource;

public class test {

	public static void main(String[] args) throws IOException {

		ClassPathResource resource = new ClassPathResource("properties.ini");

		System.out.println("resource.getFile() : " + resource.getFile());
		System.out.println("resource.getFilename() : " + resource.getFilename());
		System.out.println("resource.getInputStream() : " + resource.getInputStream());
		System.out.println("resource.getPath() : " + resource.getPath());
		System.out.println("resource.getURL() : " + resource.getURL());
		System.out.println("resource.getURI() : " + resource.getURI());

		/**
		 * 
		 	resource.getFile() : F:\project\normal_spring_project\workspace\sample\target\classes\properties.ini
		 	resource.getFilename() : properties.ini
			resource.getInputStream() : java.io.BufferedInputStream@5ebec15
			resource.getPath() : properties.ini
			resource.getURL()) : file:/F:/project/normal_spring_project/workspace/sample/target/classes/properties.ini
			resource.getURI() : file:/F:/project/normal_spring_project/workspace/sample/target/classes/properties.ini
		 * 
		 */
		
	}

}

Spring에서 쉽게 가져올 수 있도록 제공해주는 라이브러리[ClassPathResource]로 쉽게 가져올 수 있습니다

가져올 경로는 src/main/resources가 기본 경로입니다

 

🔗참고 및 출처 

http://june0313.github.io/2018/08/11/read-resource-file-on-spring/

 

반응형
반응형

See the Pen Untitled by Lee (@mondaymonday2) on CodePen.

 

밑에 출처를 기반으로 만든 거라 완전한 이해 없이 만든 거라 난해할 수 있습니다.

별도 노가다하면서 찍었네요 허허

 

 

🔗  참고 및 출처

 

https://hj-tilblog.tistory.com/102

 

CSS로 물결 애니메이션 만들기

🐳 물결치는 애니메이션 만들기 🌊 물결치는 애니메이션 포스팅을 보고 응용해서 만든 달빛이 반짝이는 밤하늘과 바다를 표현한 애니메이션이다. 해당코드는 코드펜에 올라가있으며 하나씩

hj-tilblog.tistory.com

 

반응형
반응형

📝XHR

XMLHttpRequest(XHR) 객체는 서버와 상호작용하기 위하여 사용 
즉, 비동기 통신을 위해 필요한 객체

 


📝 Ajax

Asynchronous JavaScript And XML의 약자로 JavaScript를 이용해 비동기 HTTP 통신 라이브러리입니다.
XMLHttpRequest(XHR) 객체를 이용해 필요한 데이터만 불러올 수 있습니다.
jQuery가 있어야 Ajax가 사용한게 아닙니다.
순수 Ajax를 이용해서 구현이 가능한데 너무 복잡하기 때문에 일반적으로 jQuery를 사용합니다.

 

💗장점

jQuery를 통해 쉽게 구현 가능
성공, 실패  등 Event에 따른 Handle 가능


⚠️단점

jQuery를 이용해야 사용이 편하다
Promise 기반이 아니다.

 

let xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() { // 요청에 대한 처리 
  if (xhr.readyState === xhr.DONE) {  // 요청 완료시
    if (xhr.status === 200 || xhr.status === 201) { // 200 또는 201 서버 상태 코드(성공)
      console.log(xhr.responseText);
    } else { // 실패
      console.error(xhr.responseText);
    }
  }
};

xhr.open('GET', 'https://localhost:3000'); // 통신방식, 통신 할 EndPoint
xhr.send(); // xhr 기반으로 요청 
// xhr.abort(); // xhr 기반 요청 취소

XHR 기반으로 통신 (jQuery 사용 X) 

 

 

$.ajax({
    url: https://localhost:3000, // 통신할 EndPoint
    type: 'GET', // 통신 방식
    success: function onData (data) { // 성공 시
        console.log(data);
    },
    error: function onError (error) { // 실패 시
        console.error(error);
    }
});

ajax를 이용한 통신 (jQuery 사용 O)

 


📝 Axios

Node.js와 비동기 통신을 목적으로 만들어진 Promise 기반 HTTP 통신 라이브러리입니다.
return을 Promise로 하기 때문에 비동기를 좀 더 효율적으로 할 수 있습니다.

 

 

💗장점

Promise 기반으로 데이터 다루기가 편리
fetch에는 없는 다양한 처리방법이 존재

 

 

⚠️단점

사용을 위해 모듈 설치 필요 

 

axios({
  method: 'post',
  url: '/get-member',
  data: {
  	id : "monday2"
  }
});

axios 사용 코드

 

 

 


📝 fetch

ES6부터 들어온 JavaScript 내장 라이브러리입니다.
Promise 기반이며 내장 라이브러리라는 장점으로 타 라이브러리를 import할 필요가 없습니다.


💗장점

별도 import가 없기 때문에 time resource등 이점
Promise 기반으로 데이터 다루기가 편리


⚠️단점

지원 하지 않는 웹브라우저 존재 (IE11 등...)
상대적으로 Axios에 비해 빈약한 기능

 

fetch("https://localhost:3000/user/post", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    id: "monday2",
    description: "hello world",
  }),
}).then((response) => console.log(response));
// Response {type: 'basic', url: 'http://localhost:8080/confirmCaptChaColor?userAnswer=13', redirected: false, status: 200, ok: true, …}
// MIME plain/text       → response.text() 사용
// MIME application/json → response.json() 사용

 

 

 

 

 

🔗 참고 및 출처

https://velog.io/@kysung95/%EA%B0%9C%EB%B0%9C%EC%83%81%EC%8B%9D-Ajax%EC%99%80-Axios-%EA%B7%B8%EB%A6%AC%EA%B3%A0-fetch

 

[개발상식] Ajax와 Axios 그리고 fetch

여러분들이 프로젝트를 진행하다보면 클라이언트와 서버 간 데이터를 주고받기 위해 HTTP 통신을 사용하게될겁니다. 주로 어떤것을 사용하시나요? 또, 그것에 대해 얼마나 알고계시나요? 저와

velog.io

https://cocoon1787.tistory.com/756

 

[개발상식] Ajax, axios, fetch 차이점 장단점

🚀 토이 프로젝트를 진행하다 보면 클라이언트와 서버 간의 데이터를 주고받기 위해 비동기 HTTP 통신을 하게 되는데, 이번 포스팅은 이러한 통신을 위해 사람들이 많이 사용하는 Ajax, axios, fetch

cocoon1787.tistory.com

 

 

 

 

 

반응형
반응형

http://web.cs.ucla.edu/classes/winter13/cs111/scribe/10b/

📝하드디스크 (HDD)

컴퓨터의 보조 기억 장치이다.

하드디스크는 실린더의 집합으로 되어있다.

 

📝구성

  • Platter (플래터)
    • 데이터가 기록되어있는 원판
  • Spindle (스핀들)
    • 플래터를 회전 시킵니다.
  • Actuator Arm (액츄에이터 암)
    • 디스크를 가로 질러 헤드를 움직이고, 원하는 트렉으로 헤드가 찾아가게 하는 장치
  • Head (헤드)
    • 실질적으로 데이터를 읽는 부분
  • Sector (섹터)
    • 데이터 저장할 수 있는 구역을 나눠 놓은 것
  • Track (트랙)
    • 섹터의 집합
  • Cylinder (실린더)
    • 트랙의 집합
  • Boom (붐)
    • 실린더를 선택한다

 

📝동작원리

Boom이 Cylinder를 선택하고 원하는 Sector에 Head를 가져다 놓아서 읽을 수 있도록 Spindle이 회전시킵니다.

그렇기 때문에 물리적으로 찾아가는 시간이 있어서 느립니다

 

https://www.utmel.com/blog/categories/memory%20chip/solid-state-drive-ssd-classification-and-architecture

 

📝SSD (Solid State Drive)

SSD는 모든 구조가 메모리로 되어있는 보조기억장치이다.

 

📝동작원리

전압을 주어서 전자를 메모리 셀에다가 채우고 비우고를 하는 과정으로 데이터를 쓰기, 삭제, 읽기한다 → 메모리와 비슷하게 동작

 

📝Jinja

Jinja는 Python 프로그래밍 언어용 웹 템플릿 엔진입니다 → 템플릿태그 JSTL과 유사

 

📝SQLAlchemy

Python의 ORM

 

📝WebSocket

실시간 양방향 통신을 제공하는 프로토콜입니다

WebSocket을 사용하면 서버와 클라이언트 간에 지속적인 연결을 유지하며 데이터를 실시간으로 주고받을 수 있습니다

이는 푸시 알림, 채팅, 주식 시세 업데이트 등과 같은 상황에서 특히 효과적입니다

WebSocket은 이벤트 리스너를 사용하여 간단히 이벤트를 수신하고 데이터를 전송할 수 있습니다

가상화폐 거래소와 같이 데이터 양이 많은 경우에도 효율적으로 동작하며 업비트나 바이낸스 소켓 API와 같은 서비스에서는 대용량의 실시간 데이터를 처리할 수 있습니다

 

📝Socket.io

WebSocket을 기반으로 한 실시간 웹 소켓 라이브러리입니다 → Javascript 라이브러리

더 쉽게 사용하고 여러 기능을 추가로 제공하는 데 중점을 둔 것이 특징입니다

프론트에서 이벤트 핸들링한다고 생각하시면 됩니다 물론 백엔드 소켓 서버가 존재해야합니다

 


🔗 참고 및 출처
https://velog.io/@davidko/Web-Socket-%EC%9D%B4%EB%9E%80

 

Web Socket 이란?

web socket이란

velog.io

https://inpages.tistory.com/72

 

프로그래밍 언어에서 Syntax와 Semantics의 차이는 무엇인가?

요약Syntax는 문장이 언어으 문법(grammar)에 따라 유효하진 혹은 타당한지 아닌지 확인하는 것과 관련있다.Semantics는 문장이 타당한 의미를 지니는지 아닌지를 판별하는 것과 관련있다. 조금 더 자

inpages.tistory.com

https://duzi077.tistory.com/19

 

[하드웨어] SSD 동작방식 요약

# SSD 동작 방식 전압을 주어서 전자를 메모리 셀에다가 채우고 비우고를 하는 과정으로 데이터를 쓰기, 삭제, 읽기를 한다. 절연체인 산화막에 같혀있어서 전자가 빠져나가지 않고 그 안에 유지

duzi077.tistory.com

 

반응형
반응형
/**
 * const plus = (a, b) => a + b;
 * const minus = (a, b) => a - b;
 * const divide = (a, b) => a / b;
 *
 * -----------------------------------------
 * [Export Whole Thing] export default {plus, minus, divide};
 * [just one] export default plus;
 *
 * --------------- how to import ----------------------
 * [Export Whole Thing]import math_plus from "./math.mjs";
 * [just one] import math_plus from "./math.mjs";
 *
 * [Export Whole Thing] console.log(math_plus.plus(5, 4));
 * [just one] console.log(math_plus(5, 4));
 */


/**
 * export const plus = (a, b) => a + b;
 * export const minus = (a, b) => a - b;
 * export const divide = (a, b) => a / b;
 *
 * -----------------------------------------
 * [Same As Above] export {plus, minus, divide}
 * [just one] export {plus}
 *
 * --------------- how to import ----------------------
 * import { plus, minus, divide } from "./math.mjs"; [just one]
 * import { plus } from "./math.mjs"; [just one]
 *
 * console.log(plus(5, 4));
 */
반응형
반응형

📝OTP

one time password로 무작위 생성된 일회용 패스워드로 인증하는 방식을 의미합니다.

 

 

ℹ️ OTP의 동작과정

  1. 암호화 키를 만든다. (아이디 + 비밀번호를 합쳐서 암호화 하든가 등...)
  2. DB에 암호화키를 저장하고 암호화키 기반으로 Google에서 인증 QR을 만든다.
  3. 인증 QR을 통해 앱에 등록한다.
  4. 앱에서 인증 QR기반으로 랜덤 6자리 숫자키가 만들어진다.
  5. 사용자가 입력한 6자리 숫자키와 DB에 저장된 암호화키와 동일한지 체크한다

 

 

<!DOCTYPE html>
<html lang="en">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
<script src="https://code.jquery.com/jquery-3.6.3.js" integrity="sha256-nQLuAZGRRcILA+6dMBOvcRh5Pe310sBpanc6+QBmyVM=" crossorigin="anonymous"></script>

<head>
	<meta charset="UTF-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>Document</title>
</head>
<body>
<div class="container mt-5">

<div class="mb-3">
	<label for="exampleInputEmail1" class="form-label">Email address</label>
	<input type="email" class="form-control" id="exampleInputEmail1" aria-describedby="emailHelp">
	<div id="emailHelp" class="form-text">We'll never share your email with anyone else.</div>
</div>
<div class="mb-3">
	<label for="exampleInputPassword1" class="form-label">Password</label>
	<input type="password" class="form-control" id="exampleInputPassword1">
</div>

<!-- Button trigger modal -->
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#exampleModal">
	로그인
</button>

<!-- Modal -->
<div class="modal fade" id="exampleModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog">
	<div class="modal-content">
	<div class="modal-header">
		<h5 class="modal-title" id="exampleModalLabel">OTP 인증</h5>
		<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
	</div>
	<div class="modal-body">
		OTP 인증 키를 입력해주세요
		<input type="text" class="form-control mt-3" id="code" name="code">
	</div>
	<div class="modal-footer">
		<button type="button" class="btn btn-primary" onclick="checkOtp()">확인</button>
	</div>
	</div>
</div>
</div>
  

<!-- Button trigger modal -->
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#exampleModal2">
	QR코드 만들기
</button>

<div class="modal fade" id="exampleModal2" tabindex="-1" aria-labelledby="exampleModalLabel2" aria-hidden="true">
<div class="modal-dialog">
	<div class="modal-content">
	<div class="modal-header">
		<h5 class="modal-title" id="exampleModalLabel2">QR 코드</h5>
		<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
	</div>
	<div class="modal-body">
		qr_code : <input type="text" class="form-control mt-3" id="qrCode" name="qr_code">
		url : <input type="text" class="form-control mt-3" id="siteName" name="site_name">
		user name : <input type="text" class="form-control mt-3" id="userName" name="user_name">
	</div>
	<div class="modal-footer">
		<button type="button" class="btn btn-primary" onclick="getQrPage()">URL 생성</button>
	</div>
	</div>
</div>
</div>

</body>
<script>
	    // --------------------- OTP ---------------------

	/** 
     *  @author : 이성재
     *  @why    : qr 만들기
     */

	let getQrPage = () =>{
		getQrPageAjax()
		.then((result) =>{
			window.open(result, 'pop01', 'top=10, left=10, width=500, height=600, status=no, menubar=no, toolbar=no, resizable=no');
		})
	}

	let getQrPageAjax = () => {
		return new Promise((resolve,reject) => {
		$.ajax({
		url: '/src/qr.php',
		type: 'GET',
		contentType: "application/x-www-form-urlencoded; charset=UTF-8",
		data: {
			qr_code : $("#qrCode").val(),
			site_name : $("#siteName").val(),
			user_name : $("#userName").val()
		},
		success: (result) => {resolve(result)}
		}); // ajax end	
		}); // promise end
	}

		

    /** 
     *  @author : 이성재
     *  @why    : otp 체크
     */


	let checkOtp = () => {

		if(validateOtp() !== true) return false;

		checkOtpAjax()
		.then((result) =>{
			if(result === "1") alert("OTP 패스워드가 맞습니다.");
			else alert("OTP 패스워드가 틀렸습니다.");
		})
	}

	/** 
	*  @author : 이성재
	*  @why    : otp 체크 ajax
	*/

	let checkOtpAjax = () => {
		return new Promise((resolve,reject) => {
		$.ajax({
		url: '/src/otp.php',
		type: 'POST',
		contentType: "application/x-www-form-urlencoded; charset=UTF-8",
		data: {
			code : $("#code").val()
		},
		success: (result) => {resolve(result)}
		}); // ajax end	
		}); // promise end
	}

	// otp_check

	/** 
	*  @author : 이성재
	*  @why    : otp Modal창 띄우기
	*/


	let showOtpModal = () =>{
		$(".container").css("background","rgba(0,0,0,0.6)")
		$("#adminLog").fadeOut();
		$(".modal").fadeIn();
	}

	/** 
	*  @author : 이성재
	*  @why    : otp 6자리 validate 체크
	*/

	let validateOtp = () => {

		if($("#code").val().length != 6) {
			alert("otp 6자리를 입력해주세요 (공백 X)");
			$("#code").focus();
			return false;
		}

		return true;

	}   

	/** 
	*  @author : 이성재
	*  @why    : OTP 전용 modal 창 
	*/
	$(function(){ 

		$("#code").keydown(function(key) {
		if (key.keyCode == 13) {
				checkOtp();
			}
		});

	});



</script>
</html>

index.php로 view 화면입니다.

 

<?php
require $_SERVER['DOCUMENT_ROOT']."/src/GoogleAuthenticator.php";

$qrCode = $_GET['qr_code'];     // 암호화 QR CODE값 기본적으로 아이디 + 패스워드를 합쳐서 Base32인코딩 및 추가적 암호화로 OTP 6자리 코드와 연결되는 중요한 부분
$provider = $_GET['site_name']; // 사이트 이름 Google OTP에서 구분할 사이트 명
$name = $_GET['user_name'];     // 사용자 이름 Google OTP에서 구분할 이름

$googleAuthenticator = new GoogleAuthenticator();

$secret = $googleAuthenticator->_base32Encode($qrCode);
$qrUrl = $googleAuthenticator->getQRCodeGoogleUrl($provider, $name, $secret);

echo $qrUrl;


?>

qr.php로 QR코드를 동적으로 만들기 위한 페이지입니다.

 

<?php
require $_SERVER['DOCUMENT_ROOT']."/src/GoogleAuthenticator.php";

$code = $_POST["code"];



$googleAuthenticator = new GoogleAuthenticator();
$secret = "5O4YJ25QQDWYFJHMT2C6XC4I5OF2I==="; // qr.php에서 만든 암호화키와 DB를 연결 안 해서 하드코딩으로 박아둠 ('비밀키입니다' 를 base32인코딩한 값)

$result = $googleAuthenticator->verifyCode($secret, $code);

echo $result;
?>

otp.php로 6자리 otp값이 맞는지 확인하는  부분입니다.

 

<?php 

// if (!defined('BASEPATH')) exit ('No direct script access allowed'); → 테스트를 위해서 막아둠

/**
 * PHP Class for handling Google Authenticator 2-factor authentication
 *
 * @author Michael Kliewe
 * @copyright 2012 Michael Kliewe
 * @license http://www.opensource.org/licenses/bsd-license.php BSD License
 * @link http://www.phpgangsta.de/
 */

class GoogleAuthenticator
{
    protected $_codeLength = 6;

    /**
     * Create new secret.
     * 16 characters, randomly chosen from the allowed base32 characters.
     *
     * @param int $secretLength
     * @return string
     */
    public function createSecret($secretLength = 16)
    {
        $validChars = $this->_getBase32LookupTable();
        unset($validChars[32]);

        $secret = '';
        for ($i = 0; $i < $secretLength; $i++) {
            $secret .= $validChars[array_rand($validChars)];
        }
        return $secret;
    }

    /**
     * Calculate the code, with given secret and point in time
     *
     * @param string $secret
     * @param int|null $timeSlice
     * @return string
     */
    public function getCode($secret, $timeSlice = null)
    {
        if ($timeSlice === null) {
            $timeSlice = floor(time() / 30);
        }

        $secretkey = $this->_base32Decode($secret);

        // Pack time into binary string
        $time = chr(0).chr(0).chr(0).chr(0).pack('N*', $timeSlice);
        // Hash it with users secret key
        $hm = hash_hmac('SHA1', $time, $secretkey, true);
        // Use last nipple of result as index/offset
        $offset = ord(substr($hm, -1)) & 0x0F;
        // grab 4 bytes of the result
        $hashpart = substr($hm, $offset, 4);

        // Unpak binary value
        $value = unpack('N', $hashpart);
        $value = $value[1];
        // Only 32 bits
        $value = $value & 0x7FFFFFFF;

        $modulo = pow(10, $this->_codeLength);
        return str_pad($value % $modulo, $this->_codeLength, '0', STR_PAD_LEFT);
    }

    /**
     * Get QR-Code URL for image, from google charts
     *
     * @param string $name
     * @param string $secret
     * @return string
     */
    public function getQRCodeGoogleUrl($provider, $name, $secret) {
        $urlencoded = urlencode('otpauth://totp/'.$name.'?secret='.$secret.'&issuer='.urlencode($provider).'');
        return 'https://chart.googleapis.com/chart?chs=200x200&chld=M|0&cht=qr&chl='.$urlencoded.'';
    }

    /**
     * Check if the code is correct. This will accept codes starting from $discrepancy*30sec ago to $discrepancy*30sec from now
     *
     * @param string $secret
     * @param string $code
     * @param int $discrepancy This is the allowed time drift in 30 second units (8 means 4 minutes before or after)
     * @return bool
     */
    public function verifyCode($secret, $code, $discrepancy = 1)
    {
        $currentTimeSlice = floor(time() / 30);

        for ($i = -$discrepancy; $i <= $discrepancy; $i++) {
            $calculatedCode = $this->getCode($secret, $currentTimeSlice + $i);
            if ($calculatedCode == $code ) {
                return true;
            }
        }

        return false;
    }

    /**
     * Set the code length, should be >=6
     *
     * @param int $length
     * @return PHPGangsta_GoogleAuthenticator
     */
    public function setCodeLength($length)
    {
        $this->_codeLength = $length;
        return $this;
    }

    /**
     * Helper class to decode base32
     *
     * @param $secret
     * @return bool|string
     */
    protected function _base32Decode($secret)
    {
        if (empty($secret)) return '';

        $base32chars = $this->_getBase32LookupTable();
        $base32charsFlipped = array_flip($base32chars);

        $paddingCharCount = substr_count($secret, $base32chars[32]);
        $allowedValues = array(6, 4, 3, 1, 0);
        if (!in_array($paddingCharCount, $allowedValues)) return false;
        for ($i = 0; $i < 4; $i++){
            if ($paddingCharCount == $allowedValues[$i] &&
                substr($secret, -($allowedValues[$i])) != str_repeat($base32chars[32], $allowedValues[$i])) return false;
        }
        $secret = str_replace('=','', $secret);
        $secret = str_split($secret);
        $binaryString = "";
        for ($i = 0; $i < count($secret); $i = $i+8) {
            $x = "";
            if (!in_array($secret[$i], $base32chars)) return false;
            for ($j = 0; $j < 8; $j++) {
                $x .= str_pad(base_convert(@$base32charsFlipped[@$secret[$i + $j]], 10, 2), 5, '0', STR_PAD_LEFT);
            }
            $eightBits = str_split($x, 8);
            for ($z = 0; $z < count($eightBits); $z++) {
                $binaryString .= ( ($y = chr(base_convert($eightBits[$z], 2, 10))) || ord($y) == 48 ) ? $y:"";
            }
        }
        return $binaryString;
    }

    /**
     * Helper class to encode base32
     *
     * @param string $secret
     * @param bool $padding
     * @return string
     */
    public function _base32Encode($secret, $padding = true)
    {
        if (empty($secret)) return '';

        $base32chars = $this->_getBase32LookupTable();

        $secret = str_split($secret);
        $binaryString = "";
        for ($i = 0; $i < count($secret); $i++) {
            $binaryString .= str_pad(base_convert(ord($secret[$i]), 10, 2), 8, '0', STR_PAD_LEFT);
        }
        $fiveBitBinaryArray = str_split($binaryString, 5);
        $base32 = "";
        $i = 0;
        while ($i < count($fiveBitBinaryArray)) {
            $base32 .= $base32chars[base_convert(str_pad($fiveBitBinaryArray[$i], 5, '0'), 2, 10)];
            $i++;
        }
        if ($padding && ($x = strlen($binaryString) % 40) != 0) {
            if ($x == 8) $base32 .= str_repeat($base32chars[32], 6);
            elseif ($x == 16) $base32 .= str_repeat($base32chars[32], 4);
            elseif ($x == 24) $base32 .= str_repeat($base32chars[32], 3);
            elseif ($x == 32) $base32 .= $base32chars[32];
        }
        return $base32;
    }

    /**
     * Get array with all 32 characters for decoding from/encoding to base32
     *
     * @return array
     */
    protected function _getBase32LookupTable()
    {
        return array(
            'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', //  7
            'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', // 15
            'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', // 23
            'Y', 'Z', '2', '3', '4', '5', '6', '7', // 31
            '='  // padding char
        );
    }
}

GoogleAuthenticator.php로 OTP 인증, Base32 인코딩, 디코딩 기능을 지원해주는 라이브러리입니다. 

 

프로젝트 구조입니다.

 

해당 정보로 만들어진 QR 링크입니다.

 

https://chart.googleapis.com/chart?chs=200x200&chld=M|0&cht=qr&chl=otpauth%3A%2F%2Ftotp%2F%EC%9B%94%EC%9B%94%EC%9D%B4%3Fsecret%3D5O4YJ25QQDWYFJHMT2C6XC4I5OF2I%3D%3D%3D%26issuer%3Dwww.naver.com

 

5O4YJ25QQDWYFJHMT2C6XC4I5OF2I%3D%3D%3D에 해당하는 부분이 "비밀키입니다"를 Base32 인코딩한 값입니다.

(암호화 더 필요한 경우 다양하게 알아서 해주세요)

 

 

 

 

 

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

  www.naver.com (월월이)

  943 316 (QR코드 기반으로 만들어지는 OTP 6자리)

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

 

해당 QR을 Google Authenticator 앱을 통해 등록해줍니다.

그러면 앱에 등록되면 위 같은 형식으로 나오게됩니다. (보안 정책상 캡처가 불가능 해서 사진으로 못 보여드리네요)

 

성공시 "1"이라는 키가 들어오고 그걸로 OTP가 맞는지 Java Script에서 확인했습니다.

반응형
반응형

📝Everything

이 프로그램은 파일을 색인해서 빨리 찾을 수 있게 도와줍니다.

https://everything.softonic.kr/

 

Everything

인덱싱 된 컨텐츠에 대한 원격 액세스가 가능한 강력한 로컬 검색 도구

everything.softonic.kr

 

반응형