Web 开发学习笔记(4) --- 重定向与HSTS

回顾

  • 通过前几篇文章的内容, 我们已经搭建了基于 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\n" % (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学习笔记.


参考资料

  • wsgiref — WSGI Utilities and Reference Implementation, Python Docs
  • how-to-redirect-http-to-https-in-flask, Stack Overflow
  • HSTS学习笔记

你可能感兴趣的:(http,hsts,python,flask)