Python Flask-HTTPAuth

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

  1. __init__ 构造函数
  2. get_password 获取密码
  3. hash_password 加密密码,可以是password一个参数,也可以username,password两个参数
  4. verify_password 验证密码,可以自定义验证逻辑
  5. error_handler 错误处理,如果验证失败,则执行
  6. login_required 登录授权,添加该注解后,则需要登录才能访问
  7. 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

  1. __init__ 构造函数
  2. generate_ha1 构造函数中use_ha1_pw为True时,生成HA1密码
  3. generate_nonce
  4. verify_nonce
  5. generate_opaque
  6. verify_opaque
  7. get_password 获取密码
  8. error_handler 错误处理,如果验证失败,则执行
  9. login_required 登录授权,添加该注解后,则需要登录才能访问
  10. 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¶

  1. __init__ 构造函数
  2. verify_token 验证Token,可以自定义验证逻辑
  3. error_handler 错误处理,如果验证失败,则执行
  4. 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
posted @ 2017-10-27 09:03  zhuhc  阅读(1716)  评论(0编辑  收藏  举报