Flask-HTTPAuth
### 1. Basic authentication example
# coding=utf-8
from flask import Flask
from flask_httpauth import HTTPBasicAuth
app = Flask(__name__)
auth = HTTPBasicAuth()
users = {
"john": "hello",
"susan": "bye"
}
@auth.get_password
def get_pw(username):
if username in users:
return users.get(username)
return None
@app.route('/')
@auth.login_required
def index():
return "Hello, %s!" % auth.username()
if __name__ == '__main__':
app.run()
请求方法需要登录,添加注解@auth.login_required
登录的默认验证行为:get_password(username) == password.
Test:
$ curl -i http://localhost:5000
HTTP/1.0 401 UNAUTHORIZED
Content-Type: text/html; charset=utf-8
Content-Length: 19
WWW-Authenticate: Basic realm="Authentication Required"
Server: Werkzeug/0.12.2 Python/2.7.13
Date: Thu, 26 Oct 2017 06:11:34 GMT
Unauthorized Access
$ curl -u "john:hello" -i http://localhost:5000
HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 12
Server: Werkzeug/0.12.2 Python/2.7.13
Date: Thu, 26 Oct 2017 06:12:33 GMT
Hello, john!
授权失败处理
from flask import make_response, jsonify
# 授权失败
@auth.error_handler
def unauthorized():
return make_response(jsonify({'error': 'Unauthorized access'}), 403)
Test:
$ curl -i http://localhost:5000
HTTP/1.0 403 FORBIDDEN
Content-Type: application/json
WWW-Authenticate: Basic realm="Authentication Required"
Content-Length: 37
Server: Werkzeug/0.12.2 Python/2.7.13
Date: Thu, 26 Oct 2017 06:26:20 GMT
{
"error": "Unauthorized access"
}
密码加密处理
from hashlib import md5
users = {
"john": md5("hello").hexdigest(),
"susan": "bye"
}
@auth.hash_password
def hash_pw(password):
return md5(password).hexdigest()
如果加密处理需要username,则使用两个参数的函数
@auth.hash_password
def hash_pw(username, password):
return md5(username + password).hexdigest()
如果添加了加密函数,则验证行为是:get_password(username) == hash_password(password)
当然也可以使用verify_password来替代验证行为。
# 验证密码
@auth.verify_password
def verify_pw(username, password):
if username in users:
if password == users.get(username) or md5(password).hexdigest() == users.get(username):
return True
return False
class flask_httpauth.HTTPBasicAuth
- __init__ 构造函数
- get_password 获取密码
- hash_password 加密密码,可以是password一个参数,也可以username,password两个参数
- verify_password 验证密码,可以自定义验证逻辑
- error_handler 错误处理,如果验证失败,则执行
- login_required 登录授权,添加该注解后,则需要登录才能访问
- username 用户名
### 2. Digest authentication example
from flask import Flask
from flask_httpauth import HTTPDigestAuth
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret key here'
auth = HTTPDigestAuth()
users = {
"john": "hello",
"susan": "bye"
}
@auth.get_password
def get_pw(username):
if username in users:
return users.get(username)
return None
@app.route('/')
@auth.login_required
def index():
return "Hello, %s!" % auth.username()
if __name__ == '__main__':
app.run()
Digest认证原理:
Digest Authentication在基本身份验证上面扩展了安全性. 服务器为每一连接生成一个唯一的随机数, 客户端对用这个随机数对密码进行MD5加密. 然后发送到服务器. 服务器端也用此随机数对密码加密, 然后和客户端传送过来的加密数据进行比较.
过程:客户端请求资源->服务器返回认证标示->客户端发送认证信息->服务器查验认证,
如果成功则继续资源传送,否则直接断开连接。
nonce & opaque
@auth.generate_nonce
def generate_nonce():
"""Return the nonce value to use for this client."""
pass
@auth.generate_opaque
def generate_opaque():
"""Return the opaque value to use for this client."""
pass
@auth.verify_nonce
def verify_nonce(nonce):
"""Verify that the nonce value sent by the client is correct."""
pass
@auth.verify_opaque
def verify_opaque(opaque):
"""Verify that the opaque value sent by the client is correct."""
pass
realm:授权域,至少应该包含主机名
nonce:服务端产生的随机数,用于增加摘要生成的复杂性,从而增加破解密码的难度,防范“中间人”与“恶意服务器”等攻击类型,这是相对于不使用该指令而言的;另外,nonce本身可用于防止重放攻击,用于实现服务端对客户端的认证。RFC 2617 建议采用这个随机数计算公式:nonce = BASE64(time-stamp MD5(time-stamp “:” ETag “:” private-key)),服务端可以决定这种nonce时间有效性,ETag(URL对应的资源Entity Tag,在CGI编程中通常需要自行生成ETag和鉴别,可用于鉴别URL对应的资源是否改变,区分不同语言、Session、Cookie等)可以防止对已更新资源版本(未更新无效,故需要设定nonce有效期)的重放请求,private-key为服务端私有key
opaque:这是一个不透明的数据字符串,在盘问中发送给客户端,客户端会将这个数据字符串再发送回服务端器。如果需要在服务端和客户端之间维护一些状态,用nonce来维护状态数据是一种更容易也更安全的实现方式
默认生成与认证
def _generate_random():
return md5(str(self.random.random()).encode('utf-8')).hexdigest()
def default_generate_nonce():
session["auth_nonce"] = _generate_random()
return session["auth_nonce"]
def default_verify_nonce(nonce):
return nonce == session.get("auth_nonce")
def default_generate_opaque():
session["auth_opaque"] = _generate_random()
return session["auth_opaque"]
def default_verify_opaque(opaque):
return opaque == session.get("auth_opaque")
def generate_ha1(self, username, password):
a1 = username + ":" + self.realm + ":" + password
a1 = a1.encode('utf-8')
return md5(a1).hexdigest()
算法:
1. 算法的一般性表示
H(data) = MD5(data)
KD(secret, data) = H(concat(secret, ":", data))
2. 与安全信息相关的数据用A1表示,则
a) 采用MD5算法:
A1=(user):(realm):(password)
b) 采用MD5-sess算法:
A1=H((user):(realm):(password)):nonce:cnonce
3. 与安全信息无关的数据用A2表示,则
a) QoP为auth或未定义:
A2=(request-method):(uri-directive-value)
b) QoP为auth-int:
A2=(request-method):(uri-directive-value):H((entity-body))
4. 摘要值用response表示,则
a) 若qop没有定义:
response
= KD(H(A1),<nonce>:H(A2))
= H(H(A1),<nonce>:H(A2))
b) 若qop为auth或auth-int:
response
= KD(H(A1),<nonce>:<nc>:<cnonce>:<qop>:H(A2))
= H(H(A1),<nonce>:<nc>:<cnonce>:<qop>:H(A2))
class flask_httpauth.HTTPDigestAuth
- __init__ 构造函数
- generate_ha1 构造函数中use_ha1_pw为True时,生成HA1密码
- generate_nonce
- verify_nonce
- generate_opaque
- verify_opaque
- get_password 获取密码
- error_handler 错误处理,如果验证失败,则执行
- login_required 登录授权,添加该注解后,则需要登录才能访问
- username 用户名
### 3. Token Authentication Scheme Example
from flask import Flask, g
from flask_httpauth import HTTPTokenAuth
app = Flask(__name__)
auth = HTTPTokenAuth(scheme='Token')
tokens = {
"secret-token-1": "john",
"secret-token-2": "susan"
}
@auth.verify_token
def verify_token(token):
if token in tokens:
g.current_user = tokens[token]
return True
return False
@app.route('/')
@auth.login_required
def index():
return "Hello, %s!" % g.current_user
if __name__ == '__main__':
app.run()
TEST
curl -X GET -H "Authorization: token secret-token-2" http://localhost:5000
class flask_httpauth.HTTPTokenAuth¶
- __init__ 构造函数
- verify_token 验证Token,可以自定义验证逻辑
- error_handler 错误处理,如果验证失败,则执行
- login_required 登录授权,添加该注解后,则需要验证过Token才能访问
### 4. Using Multiple Authentication Schemes
# coding=utf-8
import base64
import unittest
from flask import Flask
from flask_httpauth import HTTPBasicAuth, HTTPTokenAuth, MultiAuth
app = Flask(__name__)
app.config['SECRET_KEY'] = 'my secret'
basic_auth = HTTPBasicAuth()
token_auth = HTTPTokenAuth('MyToken')
multi_auth = MultiAuth(basic_auth, token_auth)
@basic_auth.verify_password
def verify_password(username, password):
return username == 'john' and password == 'hello'
@token_auth.verify_token
def verify_token(token):
return token == 'this-is-the-token'
@token_auth.error_handler
def error_handler():
return 'error', 401, {'WWW-Authenticate': 'MyToken realm="Foo"'}
@app.route('/')
@multi_auth.login_required
def index():
return 'index'
if __name__ == '__main__':
app.run()
多重认证,只需要使用其中的一种认证方式来通过认证。
TEST:
$ curl -u john:hello http://localhost:5000
index
$ curl -H "Authorization: MyToken this-is-the-token" http://localhost:5000
index