Web 開發學習筆記(4) — 重定向與HSTS

FrozenMap發表於2019-02-16

回顧

  • 通過前幾篇文章的內容, 我們已經搭建了基於 Flask 框架的一個簡單的 Web 應用, server.py 的程式碼如下

    from flask import Flask
    from flask.views import MethodView
    app = Flask(__name__)
    
    class IndexHandler(MethodView):
        def __init__(self, name):
            print(name)
    
        def get(self):
            return `It is a GET request`
    
        def post(self):
            return `It is a POST request`
    
    if __name__ == `__main__`:
        app.add_url_rule(`/`, view_func=IndexHandler.as_view(`index`))
        context = (`./server.cer`, `./server.key`)
        app.run(port=443, host=`0.0.0.0`, debug=True, threaded=True, ssl_context=context)

    此外, 我們還為其申請了公網 IP 和域名 www.awesome.com , 並且部署了 Let`s Encrypt 的 HTTPS 證照. 現在, 當我們在瀏覽器位址列輸入 https://www.awesome.com 即可訪問我們的網站.

  • 不過, 我們的網站目前還存在幾個問題:

    1.無法訪問 http://www.awesome.com
    2.每次都需要使用者手動輸入 https:// 字首以制定 https 形式的訪問

    為此, 我們需要重新編寫一個 server 並監聽 80 埠, 並對所有請求返回一個 redirect 響應, 把所有 http 請求都重定向為 https 請求. 最後, 我們還將開啟 HSTS, 方便使用者、提高安全性的同時減少無效的訪問.

監聽 80

  • 考慮我們的目的只是為了進行重定向, 我們不如暫且撇開 Flask, 用 Python 自帶的網路庫寫一個簡單的 server, 把它當成一個練手的 demo.

    結合文件 wsgiref, 我們可以新建 ~/webapp/redirect.py 並填寫如下內容

    from wsgiref.util import setup_testing_defaults
    from wsgiref.simple_server import make_server
    
    def simple_app(environ, start_response):
        status = `200 OK`
        headers = [(`Content-type`, `text/plain; charset=utf-8`)]
        start_response(status, headers)
    
        ret = [("%s: %s
    " % (key, value)).encode("utf-8") for key, value in environ.items()]
        return ret
    
    with make_server(`0.0.0.0`, 80, simple_app) as httpd:
        httpd.serve_forever()

http 請求重定向為 https 請求

  • 為了實現重定向(redirect), 我們需要構造這樣一個 http response:

    • 它的 Status Code301 Moved Permanently
    • 它的 headers 中包含了 redirect 的目標地址 Location: https://..., 其中 … 是使用者請求的 URI, 如首頁的 URI 就是 https://www.awesome.com/, 下面以這個 URI 為例.
    • 最後將 response body 設為空即可

  • 因此我們可以得到這樣的 response headers

    HTTP/1.1 301 Moved Permanently
    Content-length: 0
    Location: https://www.awesome.com/

    我們可以據此修改 redirect.py 的內容

    from wsgiref.util import request_uri
    from wsgiref.simple_server import make_server
    
    def simple_app(environ, start_response):
        uri = request_uri(environ)            # 獲取 client 請求的地址 URI
        location = uri[:4] + `s` + uri[4:]    # 將 http 替換成 https
        status = `301 Moved Permanently`      # 設定 Status Code
        headers =   [ (`Content-length`, `0`), (`Location`, location) ] # 設定 headers
    
        start_response(status, headers)
        return b``
    
    httpd = make_server(`0.0.0.0`, 80, simple_app)
    httpd.serve_forever()
  • 至此, 我們新編寫的 server 已經完成了, 我們在 ~/webapp/ 目錄下開啟一個 Terminal, 然後執行如下命令

    python3 redirect.py

    接著, 我們開啟瀏覽器的開發者工具, 並在位址列輸入 www.awesome.com. 如果一切順利, 我們將在開發者工具中看到一個 301 跳轉, 然後被重定向到 https://www.awesome.com

開啟 HSTS

  • 為了開啟 HSTS, 我們需要在 http response headers 中新增如下記錄

    Strict-Transport-Security: max-age=15768000; includeSubDomains; preload

    以上內容在提供 https 服務的 server 中新增即可, 因此我們需要修改 ~/webapp/server.py. 首先引入 make_response, 然後在 get() 方法中生成 resp = make_response(`It is a GET request`), 以替換原來的生成響應的方法. 接著加上新的 headers 記錄 resp.headers[`Strict-Transport-Security`]. 因此可以得到如下 server.py

    from flask import Flask, make_response
    from flask.views import MethodView
    app = Flask(__name__)
    
    class IndexHandler(MethodView):
        def get(self):
            resp = make_response(`It is a GET request`)
            resp.headers[`Strict-Transport-Security`] = `max-age=15768000; includeSubDomains; preload`
            return resp
    
    if __name__ == `__main__`:
        app.add_url_rule(`/`, view_func=IndexHandler.as_view(`index`))
        context = (`./server.cer`, `./server.key`)
        app.run(port=443, host=`0.0.0.0`, debug=True, threaded=True, ssl_context=context)
  • 儲存 server.py 之後, 我們執行 server.py

    python3 server.py

    接著, 我們用無痕模式訪問 www.awesome.com, 在開發者工具中, 我們首先可以看到一個 301 跳轉, 然後在自動進行的對 https://www.awesome.com 的請求之後, 我們就可以在 response headers 看到新新增的 Strict-Transport-Security 記錄了.

    此時, 如果我們再次輸入 www.awesome.com, 從開發者工具中我們可以看到, 跳轉碼從 301 變成了 307, 也就是 Internal Redirect, 這是在瀏覽器內部進行的重定向, 瀏覽器直接幫我們在本地把 http 換成了 https, 而不需要經過我們的 redirect.py, 減少了一次不必要的訪問. 這也是 HSTS 帶來的好處之一.

  • 限於篇幅, 本文只說明如何在 http response headers 中加入 HSTS. 如果想了解更多關於 HSTS 的內容, 可以參考這篇部落格: HSTS學習筆記.

參考資料

相關文章