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이 아닐 경우, 거부

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로 버전 올려야징

MKWeb에 로그인 기능을 구현할 때, Session/Cookie를 이용하여 구현할지, 구현한다면 어떤 방식으로 현재의 MKWeb과 결합할 수 있을 지 많은 고민을 했다.

로그인이라는 것 자체가 꽤 민감한 부분인 만큼 해당 부분을 구현하기 위해서 많은 공부가 필요했고 21년도 1학기가 시작되었기 때문에 그러기에는 좀 바빴다.

특히 20년 8월~ 21년 7월까지 진행한(아직 진행중인) 오픈배지 프로젝트에서도 사용자의 로그인을 구현해야 했는데, 이를 통해 해당 고민을 해결할 수 있었다.

일단 세션/쿠키 방식이 아닌 Json Web Token을 이용하여 사용자 인증을 구현하려 한다.

쉽게 설명하자면, Json 형태의 어떤 정보를 Token화 하여서 서버와 클라이언트가 주고받는 방식이다.

사용자가 로그인을 시도하면, 서버에서는 아이디와 패스워드를 확인하고, 로그인에 성공했다면 사용자의 정보를 이용하여 Payload를 만든다.

또한 JWT에 사용되는 암호화 방식과 토큰정보를 사용하고있는 Header가 존재하고, JWT에 대한 서명인 Signature가 있다.

이 세 파트는 연결되어서 xxxx.yyyyyy.zzzzz와 같은 형태로 존재하게 된다.

header와 payload는 base64URL로 encoding 되기 때문에 사용자의 비밀번호 등 민감한 정보를 절대 포함해서는 안된다.


JWT를 사용하기 위해 잠깐 생각해본 방법은 다음과 같다.

컨트롤러에 대하여 상위 옵션으로 "auth"에 대하여 [ all, part, none ]과 같은 설정을 할 수 있고, all의 경우 해당 페이지(혹은 컨트롤러)는 로그인과 같은 사용자 인증이 필수인 경우.

part는 컨트롤러의 일부 서비스는 사용자 인증이 필요한 경우.

none은 컨트롤러는 사용자 인증 없이 이용 가능한 경우로 정하면 어떨까 싶다.

오늘부로 내 방학 계획표 중 노는날이 끝나고 (...) 바쁜 일상이 시작되었으니, 이번주는 조금 널널하게 하기 위해 브레인스토밍 형식으로 진행하고 있기 때문에, 사실 이 방향으로 fix 한다는 보장은 없다.

그래도 나중에 돌아봤을 때, 꽤 좋은 기록물이 되지 않을까 싶어 오늘도 글을 쓰지만..

 

'웹서버 > MKWeb' 카테고리의 다른 글

[RESTful API] MkWeb RestAPI 메모장  (0) 2021.07.31
[Authorization] MkWeb JWT를 이용한 Authorization 설계  (0) 2021.07.21
MkWeb의 Controller  (0) 2021.03.29
MkWeb이란 무엇인가?  (0) 2021.03.29
MkWeb을 시작하게 된 이유  (0) 2021.03.29

MkWeb의 Controller는 json으로 정의되는데, 그 생김새는 다음과 같다.

{
	"Controller": {
		"name":"",
		"last_uri":"",
		"device":{
			"desktop":{
				"default":{
					"path":"/views/root",
					"file":"main.jsp",
					"uri":""
				},
				"en":{
					"path":"/views/root/eng",
					"file":"main.jsp",
					"uri":""
				}
			},
		 	"android":{
				"default":{
					"path":"/views/root/android",
					"file":"main.jsp",
					"uri":"/and"
				},
				"en":{
					"path":"/views/root/android/eng",
					"file":"main.jsp",
					"uri":"/and"
				}
			}
		},
		"debug":"error",
		"api":"no",
		"services":[
			{
				"page_static":"true",
				"type":{
					"kind":"sql",
					"id":"selectName"
				},
				"method":"get",
				"obj":"list",
				"parameter_name":"",
				"value":{
					"1":""
				}
			}
			{
				"page_static":"false",
				"type":{
					"kind":"sql",
					"id":"selectUserByClass"
				},
				"method":"post",
				"obj":"list",
				"parameter_name":"byclass",
				"value":{
					"1":"user_class"
				}
			}
		]
	}
}

위 json파일을 보고 어지러운가? 아니면 어느정도 이해할만 한가? 아래 표 먼저 보게되면 어지러울 수 있기 때문에, 생김새 먼저 보여준 점 이해 부탁한다.

Controller 속성
이름 설명
name 컨트롤 이름 컨트롤러마다 Unique한 이름을 가진다.
last_uri Page URI의 마지막 세그먼트 dir이 같다면, 이 값은 서로 달라야한다.
debug MkLogger의 기록 레벨. MkLogger의 기본 설정보다 낮은 값이면 기록되지 않는다. debug, info, warn, error
api RESTful API 컨트롤러인가? yes, no
device 접속 기기, 그리고 언어마다 서로 다른 jsp 파일, 그리고 uri를 설정할 수 있다. 접속 기기는 우측과 같으며, 각 기기마다 JSONObject를 가진다. desktop, android, ios
device 속성
default 기본 언어에 대한 페이지. device마다 default는 반드시 가져야 하며, 만약 추가적인 언어로 만들어진 페이지를 지정할 경우, 언어의 공식 두글자를 사용하여야 한다. default가 아니라면, ko, en 등 언어의 2글자 표현
path JSP View가 위치할 경로. /WEB-INF 아래에 위치하여야 한다.  
file JSP View 파일의 이름  
uri Dispatching을 위한 Page URI. 여기에 last_uri가 더해져 최종 URI가 된다.  
Service 속성
이름 설명
page_static 서비스가 client의 요청과 무관하게 load될 때 실행되는지를 설정한다. true, false
type 서비스의 종류와 이름을 설정하는 JSONObject. type 아래에는 "kind"와 "id"가 있다.

이 때, "id"는 내가 사용하고자 하는 service가 controller급이라면, 해당 controller 설정 json 파일에 같은 id를 가지는 서비스가 존재하여야 한다.
"kind":sql, jwt
"id": unique한 값을 가지며, 서비스를 구분하는 이름.
method 서비스를 사용하기 위해 어떤 HTTP method를 사용하여야 하는가를 설정한다. GET, POST
obj 서비스를 통해 받아오거나 생성한 데이터를 어떤 자료구조로 표현할지를 설정한다. list, map
parameter_name 서비스를 사용하기 위해 필수 parameter를 client로 부터 받을 때, 데이터의 앞첨자를 가리킨다. 각 서비스마다 유니크한 앞첨자를 가져야 한다. page_static의 경우 아무 값도 가지지 않는다. unique한 name
value 서비스를 사용하기 위해 필수 parameter를 client로부터 받을 때, 데이터를 받을 parameter 뒷첨자 이름이다. parameter_name과 함께 쓰인다. 특히 SQL 서비스의 경우, 이 값들은 SQL 서비스에도 똑같은 이름이어야 하며, SQL controller 내에서는 @로 감싸져야 한다.  


내가 이 글을 쓰면서 느낀건데,, 나는 MkWEb의 사용법을 여기에 올리려 한게 아니라 개발일지를 적으려 했다..

흠.. 어떻게 블로그 글을 써야할까?


MKWeb은 정말 내가 json파일만 수정하면 웹서버가 동작하는, 그런 기능이 필요했다. 처음에는 모든 웹 개발자가 접근하는 HTML, 마크업 언어와 비슷한 XML로 MkWeb의 모든 설정을 하였다.

하지만 이는 기능이 늘어감에 따라 복잡해지는 XML과 그로 인한 떨어지는 가독성 때문에 JSON 으로 대체하였다.

다행인점은, 컨트롤러와 서비스를 읽어오는것과 그 기능이 하게 하는것은 쉽게 프로그래밍이 가능하다는 것이다.

내가 이때까지 FTP 서비스나, 세션 등 많은 컨트롤러를 개발하면서 느낀 점은 다음과 같았다.

1. 이렇게 많은 서비스를 만든다 해서, 프론트 개발자가 모두 사용할까?
2. 내가 지정한 규칙에 맞춰(hard) 사용한다면, 프론트 개발자가 어려움을 느끼지 않을까?

그래서 나는 정말 기본적인 Logger, SQL, RESTful API, JWT와 같은 기능만 구현하여 사용자에게 제공하고, 나머지 추가 컨트롤러나 서비스(FTP와 같은)는 개발하여 MkWeb에 적재시키도록 할 예정이다.

사실 프론트 개발자는 React나 vue를 많이 사용하는데, 요즘 fresh한 느낌과는 거리가 먼 jsp를 굳이 사용할까? 하는 느낌도 있었기 때문에, 최대한 가볍게, 그러나 편리하게 쓸 수 있는 방향으로 살짝 방향을 전환하였다. (RESTful API만을 이용해서 React의 데이터 서버로 사용한다든지 등.. 근데, node가 아닌 tomcat을 따로 설치해야 한다는게 좀.. 이걸 굳이 프론트 개발자가 하려나..?)

 

MkWeb

MkWeb은 Minwhoan-Kihyeon's Webserver framework의 약자이다. 이름에서 볼 수 있듯이 최초 시작은 나와 내 친구인 hyeonic 둘이서 개발을 시작하였다.


MkWeb은 앞장에서 언급한 바 있지만, 프론트 개발자가 더 쉽고 간편하게, 내가 알지 못하는 서버에 대한 고민 없이 웹 서비스를 할 수 있게 도와주는 프레임 워크이다.

사실 https://mkweb.dev-whoan.xyz 이 페이지에 들어가면 MkWeb에 대한 설명이 조금이나마 더 문서에 맞는 어투를 사용하여 작성하였으나, 처음 공식문서를 작성하다보니 그 어색함이 있고, 표현하기에도 애매한 바가 있고, 편하게 그 내용을 정리하고 싶어서 여기에도 글을 쓴다.


MkWeb은 MVC 패턴에 맞춰 디자인된 웹서버 프레임워크이다. 그렇다고 해서 Spring Project는 아니다. Tomcat과 같은 WAS에서 동작하는 JSP 기반 웹 서버이다. (JSP라 쓰고 HTML/Javascript 라 읽는다. ㅋㅋ)그럼에도 불구하고 MVC에 맞춰 디자인한 이유는 다음과 같다.

1. 프론트 개발자가 웹서버를 쉽게 정의할 수 있어야 한다.
2. 프론트 개발자가 웹서버를 쉽게 유지/보수 할 수 있어야 한다.
3. 설계만 잘 한다면, 그에 맞게 웹서버가 동작할 수 있어야 한다.

지금이야 기본 기능이 구현된 후에서야 쓰는 글이지만, 위 3가지를 바꿔 말하면,

1. 프론트 개발자가 사용하고자 하는 View를 정의하고 (JSP), 미리 짜여진 Controller의 설계에 맞춰 기능을 잘 만든다면 웹서버가 동작하여야 한다.

2. 유지보수를 쉽게 하기 위해 프론트 개발자는 일절 Server-side Programming 지식이 없어도 되야 하므로, 프론트 개발자가 잘 사용하는 json/xml 방식을 통해 유지/보수 할 수 있어야 한다.

와 같다. 어느정도 그림이 그려 지는가?

따라서 우리 MkWeb는 MVC를 다음과 같이 정의했다.

Model: 서버의 자원 (DB, File Server 등)

View: Client가 볼 웹 페이지

Controller: 모델과 뷰 사이의 관계

Service: 컨트롤러 내에 존재하여 실제 동작하는 워커


정리해보면, MkWeb은 프론트 개발자가 json으로 정의된 컨트롤러들을 잘 정의하고, 사용하고자 하는 Service를 잘 등록해주면 웹서버가 돌아간다!

지금 지원하는 Service(Controller)는 다음과 같다.

1. RDBMS
2. RESTful API
3. MkLogger
4. FTP

개발 연혁(?)
2020.05: dev.whoan과 hyeonic의 웹서버 공부를 목적으로 MkWeb 개발 시작

2020.08: RESTful API 기능을 추가하고자 하였으나, 우리 둘의 지식 부족으로 인해 MkWeb 개발 중단

2020.09: 학기 시작과 hyeonic의 4학년 준비로 인해 MkWeb의 공동개발 중단, dev.whoan의 독자개발 진행

2021.01: koh의 관심으로 koh의 합류.

2021.03: 학기가 시작됨에 따라 MkWeb 개발 중단.


 

MkWeb

MkWeb이라는 웹서버 프레임워크를 만들기 시작했다.

누군가의 부탁이나, 어떤 대회에 참가하기 위해서가 아니라 '이러한 방법으로도 만들 수 있구나!' 하는 경험과 백엔드를 공부하기 위함이 그 목적이다.

따라서 MkWeb은 웹서버 프레임워크이며, MkWeb을 사용함으로써 편리하게 웹을 개발하자는게 그 취지이다.

특별한 점이 있다면, SSR(Server-Side Rendering) 방식이지만 어떻게 해야 프론트 개발자가 더 쉽고 간편하게, 내가 알지 못하는 서버에 대한 고민 없이 웹 서비스를 할 수 있을까 하는 방향에서 접근하기 시작했다.

위에서 언급한 '이러한 방법으로도 만들 수 있구나!'에 대한 경험은 SkyLove의 전창렬 이사님의 SkyFramework를 보고 경험했고, 나도 웹서버를 독자적으로 만들어보고 구조를 이해하고자 함이다.

처음에는 'MkWeb을 모든 대학생이 한번쯤 써보면 어떨까?' 하는 큰 꿈이 있었으나, 추가하고자 하는 기능이 많아짐에 따라 이제는 그냥 '누군가는 써봤으면' 하는 그러한 꿈도 있다.


 

정의된 RESTful API를 구현하는 도중 Client측에서 요청한 정보에 대해 결과를 모두 반환하지 않고 중간에 글자가 잘리는 버그가 있었다.

중간에 메시지가 잘린다.

해당 버그를 해결하지 않은 시점에서 본 게시글을 작성하기 시작했는데, 버그는 쉽게 해결됐다.

버그의 발생은 라즈베리파이에서 MkWeb의 RESTful API를 사용할 경우 모두 출력되지 않고 종료되는 (성능상의 이슈로 인해) 문제가 있었는데, 해당 문제를 해결하기 위해 PrintWriter의 버퍼를 일정 주기를 내가 강제하여 flush 하였더니, 기존에 잘 동작하던 플랫폼에서도 해당 이슈가 발생하였다.

flush를 지워봐도 해당 문제가 지속되어, flush를 내장하고 있는 PrintWriter의 close() 메소드를 사용해보니 이유는 모르겠지만 기존 플랫폼에서는 버그가 해결됐다.

왜 잘 나오는거지?

아무리 생각해봐도 flush를 강제적으로 해주면, 성능 저하의 이슈가 발생할지라도 정상적으로 출력돼야 하는데 잘 모르겠다.

해당 문제의 원인을 알아내면 이어쓰도록 하겠다.


2021/01/07 21:32분 수정

오류의 원인을 발견하였다. HttpServletResponse 객체의 헤더 중 Content-Length를 직접 설정해 주었는데, 이로 인해 response의 PrintWriter 객체가 내가 Content-Length를 설정한 만큼만 출력하게 되어 있었다. 따라서 실제 출력하고자 하는 내용이 모두 출력되지 않는 버그가 간간히 있었다.

따라서 Content-Length를 설정해 주는 부분을 삭제하니 정상적으로 출력 됐다.

+ Recent posts