MkAuthToken을 개발하긴 했는데,, Node와는 너무 다른 그것이기 때문에 살짝 사용법을 정립하기가 어려웠다.

JWT 특성상 서버에서는 단지 토큰 생성, 검증만 수행하기 때문에 모든 관리를 클라이언트가 해야하는 부담이 있고, MkWeb은 프론트 개발자가 최대한 이러한 활동에 집중하지 않게 하자는 목표가 있었기 때문에 더욱 그러했다.

하지만, JWT를 사용하기로 결정했고, 따라서 "클라이언트에서 이를 관리할 필요가 있다." 고 결론을 내렸다.

그럼 어떻게 Client에서 사용하게 할 것인가를 고민해 봤는데, html에서 지원하는 cookie를 이용하여 저장할 예정이다.


먼저 토큰을 생성하자.

JWT를 저장하려면 우선 JWT가 생성되어 있어야 한다. 로그인과 같은 행위를 통해 JWT를 발급받아고, 해당 토큰 값을 내가 알고 있어야 한다.

서버를 배포할 때 설정한 mkweb.auth.uri를 통해 (ajax를 사용하든 어떤 방법이 되었든...) token을 생성하자. (이 때, 모든 처리는 method: POST, Cotent-Type: application/json 이어야 한다.)

$("#login").click(function(){
	$.ajax({
		type : "POST", 
		url : "/auth/login",
		dataType : "json",
		data : {
        	"user_id" : $("#userid").val(),
			"user_pw" : $("#userpw").val()
		},
		success : function(rd){
			if(rd.token){
				...
			}
		}     
	});
});

토큰생성에 성공했다면, ajax결과로 다음과 같은 형식의 데이터를 전달받는다.

{"code": 200, "token":"your_token"}

이 후, document.cookie를 통해 토큰 값을 저장한다.

$("#login").click(function(){
	$.ajax({
		...
		success : function(rd){
			if(rd.token){
				document.cookie = createCookie(token);
			}
		}     
	});
});

let __MK_TOKEN_LIFETIME__ = 600;
let __MK_TOKEN_NAME__ = 'mkauthtoken';

function createCookie(token){
    var date = new Date();
    date.setTime(date.getTime() + __MK_TOKEN_LIFETIME__ * 1000);
    let cookieInfo = __MK_TOKEN_NAME__ + '=' + token + ';expires=' + date.toUTCString() + ';path=/';
    return cookieInfo;
}

토큰의 사용

토큰을 구하고, 삭제하는 방법은 다음과 같다.

function getToken(cookieInfo){
    var value = document.cookie.match('(^|;) ?' + __MK_TOKEN_NAME__ + '=([^;]*)(;|$)');
    return value ? value[2] : null;
}

function removeTokenCookie(cookieInfo) {
    var date = new Date();
    document.cookie = __MK_TOKEN_NAME__ + "= ; expires=" + date.toUTCString() + "; path=/";
}

주의사항

let __MK_TOKEN_LIFETIME__ = 600;
let __MK_TOKEN_NAME__ = 'mkauthtoken';

자바스크립트에 작성된 위 두 옵션은 MkWeb.conf에 설정된 mkweb.auth.lifetime, mkweb.auth.controller.name 두 속성과 일치하는 값을 가져야 한다.


꼭 JSP / HTML일 필요는 없다!

React 등 프론트 개발을 위해 다른 프레임워크 / 라이브러리를 사용해도 서버를 MkWeb을 이용할 때, 단지 프론트에서 토큰을 잘 가지고 있기만 하면 된다! 나는 프론트 개발은 HTML + Javascript + Css 밖에 안해봤기 때문에,, ㅎㅎ

GET, HEAD, OPTIONS only allow URI options
PUT allow URI options for condition, body parameter for update
DELETE allow body parameter for deleting
POST allow body parameter

기타 pretty, paging 등 : QueryString

키는 기본적으로 Authorization에 포함되어야 하지만, HTTP조회일 경우도 있기 때문에 query string을 예외적으로 허용.

Content-Type이 application/json이 아닐 경우, 거부

ElasticSearch Index 삭제 방법

$ curl --request DELETE "elastic_search_url/index" --user ID:PW

ElasticSearch 인덱스의 특정 데이터 삭제 방법

$curl --request POST "elastic_search_url/index/_delete_by_query" --user ID:PW --data /
"
{
    "query":{
          "match":{
                "컬럼명":"조건"
          }
    }
}
"

이 때, --data부분은 한줄로 써야 한다. 

예시:

$curl --request POST "elastic_search_url/index/_delete_by_query" --user ID:PW --data "{\"query\":{\"match\":{\"id\":\"hongildong@gmail.com\"}}}"

 

오늘은 백준 3052번 문제를 풀자! 해당 문제를 봤을 때, 바로 떠오른 방법이 두 가지가 있다.

MkWeb을 개발하면서 HashMap을 엄청나게 썼기 때문에, 첫 번째로 HashMap이 떠올랐고, 그 다음으로 Queue를 이용한 풀이가 떠올랐다.

해시맵의 경우, 중복되는 키를 허용하지 않기 때문에 나머지를 구한 다음에 HashMap에 각각 넣어준 뒤, HashMap의 사이즈를 반환하면 끝이다. 너무 쉽기 때문에, Queue를 이용하여 문제를 풀이하려 한다.

사실 큐를 이용하나 ArrayList, Stack 등 다른 자료구조를 이용하나 풀이는 비슷하지만, 큐의 풀이가 더 직관적이기 때문에 큐를 선택했다.


이쯤 되면 나머지 수를 구하는 것은 쉬우니, 설명은 넘어가도록 하겠다.

val = 입력값 % 42

우리는 큐에 값을 하나 씩 넣어줄 예정인데, 이전에 큐에 이미 값이 존재하는지 확인해야 한다. 이 때, 값이 존재하지 않는다면 값을 추가하고, 존재한다면 해당 값은 따로 추가하지 않을 것이다.

이때 '하나의 큐로 어떻게 가능하지?'라는 의문이 들텐데, 당연히 두 개의 큐를 사용할 것이다. (물론, 하나는 배열, 하나는 큐 등 어떤 형태를 취하든 상관 없으나, 위에서 말한대로 큐가 직관적이기 때문에 큐를 사용할 것이다.)

mainQueue: 나머지가 저장될 큐
tempQueue: mainQueue에 존재하지 않는 val들을 임시적으로 저장할 큐

매 회차마다 mainQueue로부터 tempQueue로 하나씩 옮겨담을 것이다. 이 때, val이 mainQueue에 존재하는지 확인하고, 존재하지 않는다면 값을 추가할 것이다.

따라서 mainQueue에서는 dequeue가 주로 사용될 것이며, tempQueue에는 enqueue가 주로 사용될 것이다.

While size of mainQueue > 0 Then
    peek is mainQueue.dequeue();
    If peek is not val Then
        tempQueue.enqueue(peek);
    End If
End While

수도 코드를 표현하려 했는데.. VB의 아련한 기억이...

while 반복문이 끝나면, queue를 tempQueue로 바꿔주고, queue에 val을 추가해주면 된다. val을 추가하는 이유는 while문 내에서 val을 제외한 값들만 옮겨 넣었기 때문에, mainQueue에는 val이 없다.

최종적으로 mainQueue의 size를 출력해주면 해당 문제는 끝!


전체 코드

import java.io.*;
import java.util.*;

public class Number3052 {
    public static void main(String args[]) throws IOException {
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
        Queue<Integer> queue = new LinkedList<>();
        for(int i = 0; i < 10; i++) {
            int val = (Integer.parseInt(bufferedReader.readLine())) % 42;
            Queue<Integer> tempQueue = new LinkedList<>();
            while(queue.size() > 0) {
                int peek = queue.poll();
                if(peek != val){
                    tempQueue.add(peek);
                }
            }
            queue = tempQueue;
            queue.add(val);
        }

        System.out.println(queue.size());
    }
}

결과는 정답!

 

[Java 코딩테스트] 백준 10818 문제풀이 / 배열 없이 최소 최대 구하기

이번엔 백준 10818번 문제를 풀려한다. 이 문제는 굳이 [배열]에 분류되었어야 하나 싶은 문제다. 따라서 배열을 쓰지 않고 푸는 방법, 배열을 쓰고 푸는 방법 총 두 가지로 풀 예정이다. 우선 지금

dev-whoan.xyz

지난 글에 이어서 이번 글은 배열을 이용해 최소 최대를 구할 것이다.

아니 근데 아무생각 없이 Bubble Sort를 이용해서 문제를 풀었더니 시간초과가 발생했다. 아 ㅋㅋ 부끄럽다 ㅋㅋ

이게 무슨일이고!

아니 근데 ㅋㅋㅋ QuickSort를 했을 때도 시간초과가 발생했다. 이게 무슨일이고!

남은건 merge sort인가..

Merge Sort는 정상적으로 정답이 나오는 것 보니, QuickSort의 최악의 경우가 n^2이라 그런 것 같다. 아마 테스트케이스에 정렬이 거의 다 되어있는 녀석이 있는거겠지.. 휴


문제풀이

여튼! 본론으로 돌아가서 문제풀이를 해 보자.

문제의 내용은 똑같으니, 앞 글에서 확인해주길 바라며 이 글에서는 본의아니게 Merge Sort에 대해 다루겠다

MergeSort, Divide and Conquer

MergeSort, 병합정렬은 쪼개고 합치는 행위로 수행된다.

Divide it!

정렬하고자 하는 배열 혹은 리스트가 주어지면, 더 이상 쪼갤 수 없을 때 까지 쪼갠다. 그리고 나서 다시 합쳐야 하는데, 이 때 더 이상 쪼갤 수 없는 단위까지 쪼갰기 때문에 두 수만 비교하여 정렬하면서 다시 배열을 만들면 된다.

2개를 넘어가는 수에서 합칠때 비교 방법은 다음과 같다.

M번째 요소: 좌측 혹은 우측에서 증가한 자리. 예를 들어, 좌측에서 1번째 요소를 이미 썼다면 M번째 요소는 2번째. 우측도 마찬가지. 이 때, 좌측과 우측의 M번째는 각각 독립적이다.

1. 좌측 배열의 인덱스가 마지막 까지 갔다면, 우측 배열의 M번째 요소를 선택한다.
2. 우측 배열의 인덱스가 마지막 까지 갔다면, 좌측 배열의 M번째 요소를 선택한다.
3. 좌측 배열의 값이 우측 배열의 값보다 작다면 좌측 배열의 M번째 요소를 선택한다.
4. 우측 배열의 값이 좌측 배열의 값보다 작다면 우측 배열의 M번째 요소를 선택한다.

코드를 보면 알겠지만, 각각의 요소를 모두 비교하여 선택하는 방식이다.

이미지 출처: https://qvault.io/golang/merge-sort-golang/

이 때, Merge Sort의 복잡도는 O(n logn)이 된다. 이 때, O(n)은 배열 전체에 대한 시간이고, O(log n)은 전체에 대하여 절반으로 나누었기 때문이다.

$$ T(N) = 2 * T(N/2) + N $$ 이고, N/2를 대입하면 $$ T(N/2) = 2 * T(N/4) + {N/2} ... $$가 된다. 반복하여 이를 정리하면, $$ T(N) = 2^n * T( {N / 2^n }) + n*N $$


최종 코드

import java.util.Scanner;

public class Number10818Arr {
    public static void main(String args[]){
        Scanner scan = new Scanner(System.in);
        int N = scan.nextInt();
        int arr[] = new int[N];
        for(int i = 0; i < N; i++){
            arr[i] = scan.nextInt();
        }
        int arr2[] = new int[N];
        sort(arr, 0, N-1, arr2);
        System.out.println(arr[0] + " " + arr[N-1]);
    }

    static void sort(int A[], int low, int high, int B[]){
        if(low >= high) return;

        int mid = (low + high) / 2;

        sort(A, low, mid, B);
        sort(A, mid+1, high, B);

        int i=low, j=mid+1;
        for(int k = low; k <= high; ++k){
            if(i > mid)
            	B[k] = A[j++];
            else if(j > high)
            	B[k] = A[i++];
            else if(A[i] <= A[j])
            	B[k] = A[i++];
            else
            	B[k] = A[j++];
        }

        for(i = low; i <= high; ++i)
        	A[i] = B[i];
    }
}

결과는 정답!

Merge Sort에 관한 글은 새로 정리해서 추가로 올리겠다.

이번엔 백준 10818번 문제를 풀려한다. 이 문제는 굳이 [배열]에 분류되었어야 하나 싶은 문제다.

따라서 배열을 쓰지 않고 푸는 방법, 배열을 쓰고 푸는 방법 총 두 가지로 풀 예정이다.

우선 지금은 배열을 쓰지 않고 풀 것이다. 테스트 케이스가 몇 개인지 주어졌기 때문에, 해당 횟수만큼 숫자를 입력받고, 최대 최소를 결정하면 된다.

최솟값은 1000001, 최댓값은 -1000001로 초기화시킨다.
그 이유는 이 값들은 입력될 범위보다 크거나 작기 때문에, 어떤 수도 1000001보다 작고, -1000001보다 크기 때문이다.

위처럼 최소, 최댓값을 초기화 시키고, 앞으로 들어오는 수에 대하여 최소보다 작다면 최솟값을 갱신시킨다. 마찬가지로 최대보다 크다면 최댓값을 갱신시킨다.

이를 통해 배열에 굳이 숫자를 집어넣지 않고도 계산이 가능하다.


최종 코드

import java.util.Scanner;

public class Number10818 {
    public static void main(String args[]){
        Scanner scan = new Scanner(System.in);
        int N = scan.nextInt();
        int min = 1000001, max = -1000001;
        for(int i = 0; i < N; i++){
            int cur = scan.nextInt();
            min = (Math.min(cur, min));
            max = (Math.max(cur, max));
        }

        System.out.println(min + " " + max);
    }
}

결과는 정답!

문제 설명

오늘은 백준 1110번 문제를 풀이하려한다. 사실 코딩 자체는 쉬운데 말이 어려워서..(일부러 어렵게 낸건가..)

그래서 주어진 문제를 내가 조금 정리해봤다.

1. 정수 base가 주어진다. (0 <= base <= 99)
2. base가 10보다 작다면, 앞에 0을 붙여 두 자리 수로 만들고, 각 자리의 숫자를 더한다.
3. base의 일의 자리 숫자와 앞에서 구한 합의 가장 오른쪽 수를 이어 붙여 새로운 수 calc를 만든다.
4. base와 calc가 같다면 프로세스를 종료시킨다.

이 때, 2번의 앞 내용을 빨간줄로 그어 놓은 이유는 다음과 같다.

한 자리 숫자 1은 01로 쓸 수 있다.
한 자리 숫자 2는 02로 쓸 수 있다.
...
한 자리 숫자 n은 0n으로 쓸 수 있다.

이제 base에 대하여 일의 자리 수와 십의 자리 수를 구할 것인데, 이는 나머지 연산으로 쉽게 구할 수 있다. 본문에 가장 오른쪽 자리 수(일의 자리 수)가 먼저 나왔기 때문에, A를 일의 자리 수, B를 십의 자리 수를 표현하는 데 사용할 것이다.

따라서 A와 B는 다음과 같다.

A = base % 10;
B = base / 10;

이제 이 A와 B를 가지고 새로운 calc를 구할 것인데, 3번을 보면 "일의 자리 숫자와 앞에서 구한 합"을 구해야 한다.

일의 자리 숫자는 A이고, "앞에서 구한 합"은 2번을 보면, "각 자리의 숫자를 더한다"기 때문에, A+B가 된다. 여기에 대하여, 가장 오른쪽 수 즉 일의 자리 수이고, 따라서 calc는 다음과 같다.

calc = A + ( (A+B)%10 );

이 calc를 base와 비교하여 종료하면 된다.


최종 코드

import java.util.Scanner;

public class Number1110 {
    public static void main(String args[]){
        Scanner scan = new Scanner(System.in);
        int base = scan.nextInt();
        int calc = base;
        int A = 0, B = 0;
        int repeat = 0;
        do{
            A = calc % 10;
            B = calc / 10;
            calc = (A*10)+((A+B)%10);
            repeat++;
        }while(base != calc);
        System.out.println(repeat);
    }
}

 

결과는 정답!

MkWeb 프레임워크에 로그인과 관련된 Authorization 기능을 제공하기 위해 JWT를 사용하려고 결정했다.

 

[Authorization] 프레임워크 권한 설정 / JWT for MkWeb

MKWeb에 로그인 기능을 구현할 때, Session/Cookie를 이용하여 구현할지, 구현한다면 어떤 방식으로 현재의 MKWeb과 결합할 수 있을 지 많은 고민을 했다. 로그인이라는 것 자체가 꽤 민감한 부분인 만

dev-whoan.xyz

MkWeb의 특징 때문에, Controller마다 다른 Authorize 인증과 관련하여 어떻게 하면 좋을까 고민했다.

생각해 보니 큰 고민이 아니었던게, MkWeb의 Controller들에 각 인증관련 기능을 추가해주면 됐다.


첫 번째, Page Controllers

앞선 글에서 말한것 처럼, 페이지에 크게 "Authorize"와 관련된 속성을 추가해 줄 것이고, 그 값은 다음과 같다.

1. no: 이 페이지는 누구나 볼 수 있다.
2. part: 이 페이지에 종속된 컨트롤러 중 권한이 필요한 것이 있다.
3. yes: 이 페이지를 보려면 반드시 권한이 인증되어야 한다.

페이지 컨트롤러로 접속시켜주는 녀석은 결국 MkDispatcher기 때문에, MkDispatcher의 제일 마지막 부분에 ConnectionChecker를 이용한 getPageAuthorization을 통해 권한 인증 여부를 확인했다. 해당 함수는 0, 1, 2 (각각 no, part, yes에 대응된다.)의 값을 리턴해주고, 2가 아니라면, 페이지를 보여주면 된다.


두 번째, SQL Controllers

SQL Controller가 제일 필요한 곳은 바로 MkWeb의 Custom Tag인 tagSEL다.

tagSEL의 최상단에 HttpServletRequest 객체를 이용하여 authorization이 설정되었는지 보고, 설정되어 있지 않다면 Unauthorized 401을 반환해주면 끝이다.


세 번째, API Controllers

SQL Controller와 마찬가지로, API Controllers는 최초에 Authorization을 확인한 뒤, 검증되지 않았다면 취소시키면 된다. 와! API를 만들 때 가장 코드를 많이 먹던 녀석이 바로 parameter중 어딘가에 포함되어있을 key를 찾는 것이었는데, authorization을 확인함으로써 정말 간단해졌다! 오예!

음... 사실 지난주 까지 쉬기로 했는데 이번주에 작은누나가 휴가라..ㅋㅋ이번주까지 널널하게 할거다! 

이거 올리면 0.0.9로 버전 올려야징

+ Recent posts