TigerCow.Door


안녕하세요.

오늘은 Tornado web framework를 이용한 Authentication and security를 알아보도록 하겠습니다.

공식문서를 확인하시면  'Authentication and security' 을 주제로한 몇가지 예제가 나와있습니다.

오늘은 그 중 첫번째인 Cookies and secure cookies 에 대해 공부해보겠습니다.


직접적으로 Tornado를 이용하기에 앞서서 먼저 cookie라는 것에 대해 알아볼게요.


1. 쿠키(cookie)란?

쿠키란 특정 웹서버에 대한 접근 정보를 담고 있는 것 입니다.

많은 분들께서 웹브라우저를 이용하시면서 특정 웹사이트가 로그인 정보를 기억한 경험이 있을거에요.

한번 로그인한 웹사이트를 다음에 접속할때는 따로 ID와 password를 입력하지 않아도 자동으로 로그인 되는 상황이죠.


이렇게 로그인 정보를 기억할때 웹 사이트에서 여러분의 로그인 정보를 쿠키(Cookie)로 저장하게 됩니다. 그리고 다음번에 여러분이 해당 사이트에 접근하게되면 해당 쿠키를 통해서 자동으로 로그인이 되도록 하는 것이죠.

순서를 통해 한번 더 이해해보겠습니다.


웹 브라우저  ---------->    웹서버

  1. 요 청


웹 브라우저  <---쿠키----    웹서버

2. 응 답: 쿠키생성 및 저장

3. 웹 브라우저는 쿠키저장소에 쿠키 저장


웹 브라우저  ---쿠키---->    웹서버

4. 쿠키를 통한 요청


웹 브라우저  <----------    웹서버

5. 응답: 받은 쿠키를 이용한 응답



위와 같은 순서로 쿠키가 생성되고 이용된다고 보시면 됩니다.


이러한 쿠키는 클라이언트에 저장된다고 하는데, 쉽게 말해서 사용자의 컴퓨터에 저장된다고 생각하시면 됩니다.

그럼 쿠키에 개인정보나 중요한 정보가 저장되어 있다면 보안상으로 많은 문제를 불러올 수도 있겠죠?

따라서 쿠키에 중요한 정보를 담지 않고 서버에 중요한 정보를 저장하여 이용하는 방지대책도 있으며

쿠키에 저장되는 정보자체를 암호화 하는 방법도 있습니다.


오늘 포스팅에서는 쿠키가 생성되고 저장되는 것을 확인하고,

방지대책으로써 쿠키에 저장되는 정보자체를 암호화시켜보는 과정을 진행해보겠습니다.


2. 쿠키생성

우리는 tornado에서 set_cookie 메소드를 이용하여 쿠키를 생성할 수 있습니다.

이렇게 만들어진 쿠키는 안전하지 않으며 사용자에 의해 쉽게 변경될 수 있습니다.

(보안성 강화는 뒤에서 알아보겠습니다.)


그럼 코드를 직접 작성하여 쿠키가 생성되는 것을 확인해보겠습니다.


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
import tornado.httpserver
import tornado.ioloop
import tornado.web
 
class MainHandler(tornado.web.RequestHandler):     
     def get(self):
          if not self.get_secure_cookie("mycookie"):
               self.set_secure_cookie("mycookie","myvalue")         
               self.write("Your cookie was NOT set yet!")
          else:
               self.write("Your cookie was set!")
               items = ["Item 1""Item 2""Item 3"]                  
               self.render("template_test.html", title="My title", items=items, sayHello=self.sayHello) 
               
     def sayHello(self, name):
          return 'Hello, '+name + '!!!'
 
application = tornado.web.Application([     
     (r"/", MainHandler),   
]) 
 
if __name__ == "__main__":     
     http_server = tornado.httpserver.HTTPServer(application)     
     http_server.listen(8888)     
     tornado.ioloop.IOLoop.instance().start()
cs


지난 번 포스팅에서 진행된 코드에서 수정을 진행하였습니다.

7번 라인을 보시면 현재 사용자가 쿠키를 가지고 있는지 확인합니다.

사용자에게 쿠키가 없다면 8번 라인을 통해 mycookie라는 키(key)와 myvalue라는 정보(value)를 가진 쿠키를 생성하고

9번라인을 통해 화면에는 Your cookie was NOT set yet! 이라는 문구를 띄어줍니다.



그럼 이 화면을 보고 있는 상태라면 이미 8번 라인이 실행된 것이므로 쿠키가 생성되었겠죠?

다시 접속해보면 아래와 같이 화면이 바뀌게 됩니다.


네, 쿠키가 생성되어서 이제 코드 8,9 라인이 진행되지 않고 10번라인부터 진행이 되었습니다.

이제 생성된 쿠키를 확인해볼게요.

웹 콘솔창(Shift + I 을 누른뒤 Console 클릭) 또는 명령어 자체를 주소 창에 입력하시는 것을 통해 확인 할 수 있습니다.

javascript:document.cookie 를 입력합니다.



그럼 좀 전 코드에서 확인한 바와 같이 mycookie 라는 키(key)와 그에 대응하는 myvalue 라는 정보(value)가 생성되어 저장된 것을 확인할 수 있습니다.

하지만 처음에 알아본 것과 같이 이렇게 쿠키가 저장되어 있다면 사용자가 이를 쉽게 수정할 수 있으며 또는 쉽게 정보를 해킹당할 수 있습니다.

예를 들어 특정 웹사이트에서 권한을 1~5로 나누고 A라는 유저는 2 정도의 권한을 가지고 있었는데, 위와 같이 쿠키의 정보가 보안이 되어 있지 않다면 사용자가 2라는 권한을 5로 수정하여 허락되지 않은 행동들을 취할 수 있을 것 입니다.



3. 쿠키 암호화 하기

따라서 우리는 쿠키에 중요한 정보를 저장하지 않거나, 쿠키의 정보 자체를 암호화하는 방법을 이야기했습니다.

그 중 tornado에서는 쿠키의 정보 자체를 암호화 하는 함수를 제공하고 있습니다.

set_secure_cookieget_secure_cookie라는 함수를 제공하고 있으며 이러한 함수들을 사용하기 위해서는 cookie_secret이라는 값을 설정해야 합니다. 

set_secure_cookie를 통해 생선된 인증된 쿠키는 암호화된 정보에 추가적으로 timestamp와 HMAC인증이 저장됩니다. 만약 쿠키가 오래된 것이거나 인증이 맞지 않다면 get_secure_cookieNone값을 리턴하여 쿠키가 존재하지 않는 것처럼 처리하게 됩니다. 

위의 두가지 함수를 이용한 python 코드는 아래와 같습니다.


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
import tornado.httpserver
import tornado.ioloop
import tornado.web
 
class MainHandler(tornado.web.RequestHandler):     
     def get(self):
          if not self.get_secure_cookie("mycookie_2"):
               self.set_secure_cookie("mycookie_2","myvalue")         
               self.write("Your cookie was NOT set yet!")
          else:
               self.write("Your cookie was set!")
               items = ["Item 1""Item 2""Item 3"]                  
               self.render("template_test.html", title="My title", items=items, sayHello=self.sayHello) 
               
     def sayHello(self, name):
          return 'Hello, '+name + '!!!'
 
application = tornado.web.Application([     
     (r"/", MainHandler),   
],cookie_secret="owb4t22n3or$239fn=nefo23/1rf13"
 
if __name__ == "__main__":     
     http_server = tornado.httpserver.HTTPServer(application)     
     http_server.listen(8888)     
     tornado.ioloop.IOLoop.instance().start()
cs


7번 코드를 확인하면 이전과 달리 mycookie_2 가 존재하는지 확인합니다.

그리고 존재하지 않는다면 8번 라인에서 set_secure_cookie 함수를 통해 mycookie_2라는 키와 myvalue 라는 값을 가진 쿠키를 생성합니다. 이후의 동작은 이전의 코드와 동일합니다.

추가적으로 20번라인에 cookie_secret 이 새로 추가된 것을 확인하실 수 있습니다.

저는 해당 cookie_secret을 무작위로 입력하였습니다.

해당 코드로 웹에 접속해 본다면 이전과 같은 결과를 얻을 수 있습니다.

첫 접속에서는, Your cookie was NOT set yet! 이라는 화면이 뜨고 다시 접속하면 필요한 정보들이 출력됩니다.

그럼 다른점을 확인해볼까요?

아까와 같이 웹 콘솔창을 띄우거나 주소창을 통해 이전과 같은 명령어(javascript:document.cookie)를 입력합니다.


그럼 이전에 생성된 mycookie라는 키와 방금 생성된 mycookie_2 라는 키, 두개에 대한 정보를 함꼐 확인하실 수 있습니다.

그 차이점이 명확히 보이시나요?

mycookie_2 라는 키에 대응되는 정보(value)는 암호화되어 사용자가 무슨내용인지 알 수 없습니다.

이런 암호화된 정보를 확인하기 위해서는 위에서 추가한 cookie_secret이 필요할 것 입니다.


토네이도의 공식문서를 통해 확인해보면, 이러한 쿠키는 무결성(integrity)를 보장하지만 기밀성(confidentiality)는 보장하지 않는다고 합니다. 즉, 우리가 쿠키에 대한 정보를 확인할 수 있었듯이 사용자가 쿠키를 볼수는 있지만 수정할 수는 없습니다.

그리고 cookie_secret는 비밀로 유지되어야 하며 이 키의 값을 얻은 사용자는 자신의 쿠키를 생성하거나 수정할 수 있습니다.

기본적으로 tornado의 보안 쿠키는 30일 후에 만료됩니다. 이를 변경하기 위해서는 set_secure_cookie함수에서 expires_days 키워드와 get_secure_cookie의 함수에서 max_age_days 키워드를 수정하면 됩니다.

보다 자세한 정보가 궁금하다면 tornado의 공식문서를 참고하시기를 바랍니다.


이렇게 하여 쿠키(cookie)에 대해 알아보고, Tornado에서 쿠키를 생성하고 보안성을 높이는 방법을 알아보았습니다.

다음에는 유저 인증(User authentication)에 대해서 알아보도록 하겠습니다.

수정되어야 할 점에 대한 피드백이나 문의사항은 덧글 및 이메일(doorbw@outlook.com)을 이용해주세요!

블로그 이미지

Tigercow.Door

Data-Analysis / AI / back-end / Algorithm / DeepLearning / etc


Tornado (python web server) 관련 3번째 포스팅입니다. 

이번 포스팅에서 다뤄볼 내용은 토네이도에서 템플릿 사용하기(Using template in Tornado) 입니다.

바로 시작해보도록 하겠습니다.


Tornado 에서는 python에서 사용할 수 있는 어떠한 템플릿 언어도 사용할 수 있습니다.

Tornado는 다른 템플릿 언어들에 비해 매우 빠르고 유연한 템플릿 언어를 기본적으로 포함하고 있다고 합니다.

자세한 내용은 Tornado의 template 모듈 문서를 참조하시면 됩니다.

http://www.tornadoweb.org/en/stable/_modules/tornado/template.html?highlight=template )


Tornado 템플릿은 마크업 언어 내에 python의 제어문과 표현식을 포함하고 있는 HTML 입니다.

아래 예제를 통해 확인해보도록 하겠습니다.

먼저 HTML 코드입니다.


1
2
3
4
5
6
7
8
9
10
11
12
<html>   
 <head>       
  <title>{{ title }}</title>    
 </head>   
 <body>     
  <ul>        
   {% for item in items %}          
    <li>{{ escape(item) }}</li>        
   {% end %}      
  </ul>    
 </body
</html>
cs


위의 코드를 저는 template_test.html 으로 저장하고 tornado로 구동시킬 python파일과 같은 디텍토리에 넣었습니다.

7번 라인과 9번 라인은 python의 제어문을 표현한 것인데 밑에서 설명하도록 하겠습니다.

그리고 아래 python 코드를 통해 위의 템플릿을 rendering 할 수 있습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import tornado.httpserver
import tornado.ioloop
import tornado.web
 
class MainHandler(tornado.web.RequestHandler):     
     def get(self):         
          items = ["Item 1""Item 2""Item 3"]                  
          self.render("template_test.html", title="My title", items=items) 
 
application = tornado.web.Application([     
     (r"/", MainHandler),   
]) 
 
if __name__ == "__main__":     
     http_server = tornado.httpserver.HTTPServer(application)     
     http_server.listen(8888)     
     tornado.ioloop.IOLoop.instance().start()
cs


그리고 해당 python 파일을 구동시켜서 확인합니다.

제대로 랜더링이 되었다면 아래 사진과 같은 결과를 확인할 수 있습니다.




Tornado template 는 python의 제어문과 표현식을 함께 지원한다고 하였습니다.

제어문은 {% 와 %} 로 묶어 주면됩니다.

다시 위와 동일한 HTML 코드를 확인하겠습니다.


1
2
3
4
5
6
7
8
9
10
11
12
<html>   
 <head>       
  <title>{{ title }}</title>    
 </head>   
 <body>     
  <ul>        
   {% for item in items %}          
    <li>{{ escape(item) }}</li>        
   {% end %}      
  </ul>    
 </body
</html>
cs


위에서 언급하였듯이 7번 라인 ~ 9번 라인을 보시면 제어문이 사용됨을 알 수 있습니다.

그리고 제어문은 {% , %}으로 둘러쌓여 있습니다.


제어문은 python의 제어문과 동일합니다.

Tornado 에서는 if, for, while, try를 지원하지만 모두 {% end %} 로 끝내주어야 합니다.

또한 extends 와 block 문을 이용하여 템플릿 상속(template inheritance)를 지원합니다.


표현식은 함수 호출을 포함한 어떤 python 표현식이나 가능합니다.

'escape() <.xhtml_escape>', '.url_escape()', '.json_encode()', '.squeeze()' 함수는 default 로 제공하고 있습니다.

또한 다른 함수를 템플릿에 넘겨주는 것 또한

템플릿 렌더 함수에게 함수 이름을 keyword argument로 넘겨주기만 하면 가능합니다.


아래 예제를 확인해보겠습니다.

먼저 python 코드입니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import tornado.httpserver
import tornado.ioloop
import tornado.web
 
class MainHandler(tornado.web.RequestHandler):     
     def get(self):         
          items = ["Item 1""Item 2""Item 3"]                  
          self.render("template_test.html", title="My title", items=items, sayHello=self.sayHello) 
 
     def sayHello(self, name):
          return 'Hello, '+name + '!!!'
 
application = tornado.web.Application([     
     (r"/", MainHandler),   
]) 
 
if __name__ == "__main__":     
     http_server = tornado.httpserver.HTTPServer(application)     
     http_server.listen(8888)     
     tornado.ioloop.IOLoop.instance().start()
cs


그리고 HTML 파일 코드 입니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<html>   
 <head>       
  <title>{{ title }}</title>    
 </head>   
 <body>     
  <ul>        
   {% for item in items %}          
    <li>{{ escape(item) }}</li>        
   {% end %}
   <p>
       {{ sayHello('DoorBW') }}
   </p>      
  </ul>    
 </body
</html>
cs


먼저 python 파일을 보시면 10번 라인에 sayHello 라는 이름의 함수가 새로 선언되었습니다.

name 이라는 인자를 받아서, Hello, name !!! 을 출력하도록 하는 함수입니다.

그리고 랜더링 시에 sayHello = self.sayHello 를 통해 sayHello 라는 이름으로 함수를 전달합니다.


HTML 파일을 확인하시면 11번 라인이 추가되었음을 확인할 수 있습니다.

sayHello라는 이름으로 함수를 받았기에 {{ sayHello('DoorBW') }} 라는 식으로 함수를 사용합니다.


실행결과는 아래와 같습니다.


내부에서 Tornado 템플릿은 python으로 직접 번역됩니다.

템플릿에 포함된 표현식은 템플릿을 나타내는 python 함수로 글자 그대로가 복사됩니다.

다른 말로 템플릿 표현식에서 이상한 내용을 쓴다면?

당연히 그 템플릿을 실행할 떄 이상한 python에러를 접할 것 입니다.


실제로 어플리케이션을 제작할 떄, Tornado 템플릿의 모든 기능, 특히 템플릿 상속 기능을 원활하게 사용하기위해

Tornado 공식 문서에서 template module 섹션에 대한 내용을 모두 읽어보시는 것을 추천드립니다.

위 예제에서 다룬 것은 극히 일부이며 공식문서에서 보다 많은 내용들을 참고하실 수 있습니다.



이렇게 해서 파이썬 웹서버 토네이도에서 템플릿 사용하는 방법에 대해 간략히 알아보았습니다.

다음에는 Cookies(쿠키)에 관한 내용에 대해 알아보도록 하겠습니다.









블로그 이미지

Tigercow.Door

Data-Analysis / AI / back-end / Algorithm / DeepLearning / etc

지난 번에 이어서 토네이도(Tornado) 공부를 진행하겠습니다.

이번 포스팅에서는

Request handler and Request argument 에 대해서 공부를 진행합니다.


토네이도 웹 어플리케이션은 URL 또는 URL 패턴을 tornado.web.RequestHandler의 subclass(서브클래스)로 매핑합니다.

이 클래스들은 해당 URL로의 HTTP GET 또는 POST request를 처리하기 위한 get() 또는 post() 메소드를 정의합니다.


지난 시간에 진행했던 기본 테스트 예제를 먼저 확인하겠습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# from : http://www.tornadoweb.org/en/stable/#hello-world
# myapp.py
import tornado.ioloop
import tornado.web
 
class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("Hello, world")
 
def make_app():
    return tornado.web.Application([
        (r"/", MainHandler),
    ])
 
if __name__ == "__main__":
    app = make_app()
    app.listen(8888)
    tornado.ioloop.IOLoop.current().start()
cs


6번 라인을 확인하면 MainHandler 가 존재합니다.

12번 라인을 확인하면 URL인 '/' 는 MainHandler에 매핑되고 있습니다.


다른 예제를 통해 한번 더 확인하겠습니다.

아래 코드는 URL인 '/'는 MainHandler에, URL 패턴인 '/story/([0-9]+)' 는 StoryHandler 로 매핑하고 있습니다.

정규식(regular expression) 그룹은 Requesthandler 메소드에게 argument로 전달됩니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import tornado.httpserver 
import tornado.ioloop 
import tornado.web
 
class MainHandler(tornado.web.RequestHandler):     
     def get(self):         
          self.write("You requested the main page")
 
class StoryHandler(tornado.web.RequestHandler):     
     def get(self, story_id):         
          self.write("You requested the story " + story_id) 
 
application = tornado.web.Application([     
     (r"/", MainHandler),     
     (r"/story/([0-9]+)"
     StoryHandler), 
]) 
 
if __name__ == "__main__":     
     http_server = tornado.httpserver.HTTPServer(application)           
     http_server.listen(8888)    
     tornado.ioloop.IOLoop.instance().start()
cs


빨간색 동그라미를 확인하면 MainHandler 매핑되는 '/' 를 확인할 수 있다.



빨간색 동그라미를 확인하면 StoryHandler 매핑되는 '/story/([0-9]+)' 를 확인할 수 있다.





또한 get_argument () 메소드를 사용하면 query string 을 가져와서 POST 바디의 내용을 파싱할 수 있습니다.

코드와 실행결과는 아래와 같습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import tornado.httpserver 
import tornado.ioloop 
import tornado.web 
 
class MainHandler(tornado.web.RequestHandler):     
     def get(self):         
          self.write('<html><body><form action="/" method="post">'                    
                    '<input type="text" name="message">'                    
                    '<input type="submit" value="Submit">'
                    '</form></body></html>')    
 
     def post(self):         
          self.set_header("Content-Type""text/plain")         
          self.write("You wrote " + self.get_argument("message"))
 
application = tornado.web.Application([     
     (r"/", MainHandler),   
]) 
 
if __name__ == "__main__":     
     http_server = tornado.httpserver.HTTPServer(application)
     http_server.listen(8888)     
     tornado.ioloop.IOLoop.instance().start()
cs


코드의 7~10 라인을 통해 input 칸과 button이 만들어짐을 확인할 수 있다.

input의 이름은 message로 설정되어 있다.

form tag로 묶여있으며 form tag의 내부를 확인하면 post 방식으로 데이터를 보낸다.



따라서 버튼을 누르면 input 에 입력된 값이 post 방식으로 '/' 주소로 넘어가며

그때 넘어가는 데이터의 이름은 message 이다.

12번 라인의 post 함수를 보면 이를 받아 페이지를 구성하는 것을 알 수 있다.

14번 라인에서 message 라는 argument를 얻어서 

You wrote ~ 을 만들어 낸다.



클라이언트에 에러 response ( 예를들면 403 Unauthorized 같은 ) 를 보내려면

tornado.web.HTTPError exception을 raise해주면 됩니다.


1
2
if not self.user_is_logged_in():     
     raise tornado.web.HTTPError(403)
cs


request handler에서 현재 request 오브젝트를 self.request로 access할 수 있습니다.

HTTPRequest 오브젝트는 여러가지 유용한 attribute를 포함하고 있습니다.


▶ arguments - GET 과 POST의 전체 argument

▶ files - (multipart/form-data POST request를 통한) 모든 업로드 된 파일들

▶ path - request 패스 (URL 에서 '?' 앞쪽 전체)

▶ headers - request 헤더


모든 attribute 목록은 httpserver의 HTTPRequest 클래스 정의를 확인하면 됩니다.



이상으로 Tornado에서 Request handler and request argument 에 대한 포스팅을 마치도록 하겠습니다.

궁금한 점이 있거나 추가적인 도움을 주고싶으신 분이 있다면

댓글이나 doorBW@outlook.com 을 통해 메일주세요!

블로그 이미지

Tigercow.Door

Data-Analysis / AI / back-end / Algorithm / DeepLearning / etc

최근 특정 일로 인해 Tornado 공부를 시작하게 되었습니다.

Tornado는 python언어를 이용한 web server 입니다.

python 언어를 이용한 web server로는 가장 이용자가 많은(특히 한국에서) web server라고 하네요!


Tornado를 공부하는데 있어서는 공식문서를 주로 다루도록 하고

구글링 또한 함께.. 열심히 하면서 노력해보겠습니다.


먼저 그 시작은 간단하게 Tornado를 설치하고

화면에 "Hello world"를 띄어보도록 할게요!


Tornado 공식문서는 아래 링크를 참고해주세요!

http://www.tornadoweb.org/en/stable/



1. Tornado 설치하기

기본적으로 python은 모두 설치 되어있음을 가정하고 진행합니다.

저의 python 설치 환경은 아래와 같습니다.


Windows 10 +



Tornado 를 설치해보도록 하겠습니다!

매우 간단합니다.

아래와 같은 명령어를 입력해줍니다.


pip install tornado


그럼 아래와 같이 실행이 되면 정상입니다.




이렇게 Tornado 설치는 끝 입니다!

(매우간단...!!!)


2. Tornado 테스트


바로 예제를 통해 테스트를 진행해볼게요.


자신이 원하는 폴더에 python파일을 생성하고 아래와 같이 작성합니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# from : http://www.tornadoweb.org/en/stable/#hello-world
# myapp.py
import tornado.ioloop
import tornado.web
 
class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("Hello, world")
 
def make_app():
    return tornado.web.Application([
        (r"/", MainHandler),
    ])
 
if __name__ == "__main__":
    app = make_app()
    app.listen(8888)
    tornado.ioloop.IOLoop.current().start()
cs



그리고 터미널에 다음 명령어를 입력!


python test.py


아래와 같이 실행됩니다.


그리고 인터넷을 통해 localhost:8888 에 접속하여 확인합니다.



결과가 잘 나옵니다!



이렇게해서 Tornado 설치와 간단한 테스트 마무리하도록 하겠습니다.


블로그 이미지

Tigercow.Door

Data-Analysis / AI / back-end / Algorithm / DeepLearning / etc