Morepath的JWT访问认证身份策略
项目描述
more.jwtauth:Morepath 的 JWT 认证集成
概述
这是一个用于 JSON Web Token (JWT) 认证的 Morepath 认证扩展。
有关 JWT 的更多信息,请参阅
JSON Web Token 草案 - 官方 JWT 草案
使用 JSON Web Tokens 进行认证 - José Padilla 的有趣博客文章
要使用 JWT 访问认证访问资源,客户端必须获得 JWT 以对服务器进行签名请求。令牌对于客户端来说是透明的,尽管如此,除非它被加密,否则客户端可以读取令牌中声明的声明。
JWT 使用签名验证声明集的真实性。
此插件使用 José Padilla 的 PyJWT 库 验证 JWT。
简介
- JWT 访问认证的一般工作流程
客户端发送登录表单后,我们检查用户是否存在以及密码是否有效。
在这种情况下,more.jwtauth 生成一个包含声明集中所有信息的 JWT 令牌,并将其放在 HTTP 认证头中发送回客户端。
客户端将其存储在本地存储中,并在每次请求时将其发送回认证头。
more.jwtauth 使用令牌中包含的签名验证声明集的真实性。
注销应由客户端处理,通过删除令牌并根据实现进行一些清理。
您可以在令牌中包含有关身份的所有必要信息,这样 JWT 访问认证就可以由无状态服务使用,例如与外部密码验证一起使用。
需求
Python (3.6, 3.7, 3.8, 3.9)
morepath (> 0.19)
PyJWT (2.4.0)
可选:cryptography (> 3.3.2)
安装
您可以使用 pip 安装 more.jwtauth
pip install -U more.jwtauth[crypto] - 用于安装 cryptography
pip install -U more.jwtauth - 不安装 cryptography
用法
对于基本设置,只需设置必要的设置,包括密钥或密钥文件,并将它们传递给 JWTIdentityPolicy
import morepath
from more.jwtauth import JWTIdentityPolicy
class App(morepath.App):
pass
@App.setting_section(section="jwtauth")
def get_jwtauth_settings():
return {
# Set a key or key file.
'master_secret': 'secret',
# Adjust the settings which you need.
'leeway': 10
}
@App.identity_policy()
def get_identity_policy(settings):
# Get the jwtauth settings as a dictionary.
jwtauth_settings = settings.jwtauth.__dict__.copy()
# Pass the settings dictionary to the identity policy.
return JWTIdentityPolicy(**jwtauth_settings)
@App.verify_identity()
def verify_identity(identity):
# As we use a token based authentication
# we can trust the claimed identity.
return True
登录可以使用标准的 Morepath 方法进行。您可以在 JWT 令牌中添加有关身份的额外信息,这些信息将被存储在 morepath.Identity 对象中
class Login:
pass
@App.path(model=Login, path='login')
def get_login():
return Login()
@App.view(model=Login, request_method='POST')
def login(self, request):
username = request.POST['username']
password = request.POST['password']
# Here you get some extra user information.
email = request.POST['email']
role = request.POST['role']
# Do the password validation.
if not user_has_password(username, password):
raise HTTPProxyAuthenticationRequired('Invalid username/password')
@request.after
def remember(response):
# We pass the extra info to the identity object.
identity = morepath.Identity(username, email=email, role=role)
request.app.remember_identity(response, request, identity)
return "You're logged in." # or something more fancy
请勿使用保留声明名称“iss”、“aud”、“exp”、“nbf”、“iat”、“jti”、“refresh_until”、“nonce”或用户_id_claim(默认:“sub”,见 settings)。它们将被静默忽略。
- 高级
对于测试或如果我们想直接使用 JWTIdentityPolicy 类的方法,我们可以将设置作为参数传递给类
identity_policy = JWTIdentityPolicy( master_secret='secret', leeway=10 )
刷新令牌
使用长期令牌存在一些风险
如果您使用无状态解决方案,令牌包含可能不再更新的用户数据。
如果令牌被泄露,无法在服务器端销毁会话。
一种解决方案是使用短期令牌并在它们即将过期之前或甚至在它们过期之前刷新它们,直到 refresh_until 声明过期。
为了帮助您做到这一点,more.jwtauth 有一个刷新 API,它使用 4 个设置
- allow_refresh:当为 True 时启用令牌刷新 API。
默认为 False
- refresh_delta:令牌可以刷新的时间差
考虑容差。默认为7天。当为None时,您可以随时刷新令牌。
- refresh_nonce_handler:可以是回调函数的点分路径,或者
回调函数本身,该函数接收当前请求和用户ID作为参数,并返回一个nonce,该nonce在刷新之前将被验证。当为None时,不会创建或验证任何nonce来刷新。
- verify_expiration_on_refresh:如果为False,JWT令牌的
“expiration_delta”不会在刷新时检查。否则,只有当令牌尚未过期时,您才能刷新令牌。默认为False。
当通过设置refresh_delta启用刷新时,令牌可以获取2个额外的声明
refresh_until:令牌可以刷新的截止时间戳。
nonce:由refresh_nonce_handler生成的nonce。
因此,当您想要刷新令牌时,无论是由于它已过期还是即将过期,您都应该调整您的jwtauth设置
@App.setting_section(section="jwtauth")
def get_jwtauth_settings():
return {
# Set a key or key file.
'master_secret': 'secret',
'allow_refresh': True,
'refresh_delta': 300,
'refresh_nonce_handler': 'yourapp.handler.refresh_nonce_handler'
}
或者,您可以通过装饰返回处理函数的闭包来设置refresh_nonce_handler
from .app import App
from .model import User
@App.setting(section="jwtauth", name="refresh_nonce_handler")
def get_handler():
def refresh_nonce_handler(request, userid):
# This returns a nonce from the user endity
# which can just be an UUID you created before.
return User.get(username=userid).nonce
return refresh_nonce_handler
之后,您可以发送请求到刷新端点以刷新令牌
from morepath import Identity
from more.jwtauth import (
verify_refresh_request, InvalidTokenError, ExpiredSignatureError
)
from .app import App
from .model import User
class Refresh:
pass
@App.path(model=Refresh, path='refresh')
def get_refresh():
return Refresh()
@App.view(model=Refresh)
def refresh(self, request):
try:
# Verifies if we're allowed to refresh the token.
# In this case returns the userid.
# If not raises exceptions based on InvalidTokenError.
# If expired this is a ExpiredSignatureError.
username = verify_refresh_request(request)
except ExpiredSignatureError:
@request.after
def expired_nonce_or_token(response):
response.status_code = 403
return "Your session has expired."
except InvalidTokenError:
@request.after
def invalid_token(response):
response.status_code = 403
return "Could not refresh your token."
else:
# get user info from the database to update the claims
User.get(username=username)
@request.after
def remember(response):
# create the identity with the userid and updated user info
identity = Identity(
username, email=user.email, role=user.role
)
# create the updated token and set it in the response header
request.app.remember_identity(response, request, identity)
return "Token sucessfully refreshed."
因此,现在每次刷新令牌时,用户数据都会更新。
当使用refresh_nonce_handler时,如果令牌遭到泄露(例如,通过在用户实体中存储新的UUID),您只需更改nonce,现有令牌将不再刷新。
异常
当刷新令牌失败时,会抛出异常。所有异常都是more.jwtauth.InvalidTokenError的子类,因此您可以使用except InvalidTokenError来捕获它们。对于每个异常,都会添加一个失败描述。可能会引发以下异常
InvalidTokenError:当刷新API被禁用、JWT令牌找不到或刷新nonce无效时,使用纯InvalidTokenError。
ExpiredSignatureError:当refresh_until声明已过期或当启用verify_expiration_on_refresh时JWT令牌已过期。
MissingRequiredClaimError:当提供了refresh_delta但缺少refresh_until声明或当使用refresh_nonce_handler时缺少nonce声明。
DecodeError:当JWT令牌无法解码时。
设置
有一些设置您可以覆盖。以下是所有默认值
@App.setting_section(section="jwtauth")
def get_jwtauth_settings():
return {
'master_secret': None,
'private_key': None,
'private_key_file': None,
'public_key': None,
'public_key_file': None,
'algorithm': "HS256",
'expiration_delta': datetime.timedelta(minutes=30),
'leeway': 0,
'allow_refresh': False,
'refresh_delta': timedelta(days=7),
'refresh_nonce_handler': None,
'verify_expiration_on_refresh': False,
'issuer': None,
'auth_header_prefix': "JWT",
'userid_claim': "sub"
}
以下设置是可用的
- master_secret
只有服务器才知道的秘密,用于默认的HMAC(HS*)算法。默认为None。
- private_key
用于EC(EC*)或RSA(PS*/RS*)算法的椭圆曲线或RSA私钥。默认为None。
- private_key_file
包含椭圆曲线或RSA编码(PEM/DER)私钥的文件。默认为None。
- public_key
用于EC(EC*)或RSA(PS*/RS*)算法的椭圆曲线或RSA公钥。默认为None。
- public_key_file
包含椭圆曲线或RSA编码(PEM/DER)公钥的文件。默认为None。
- algorithm
用于签名密钥的算法。默认为HS256。
- expiration_delta
从现在起到令牌过期的时差。设置为None以禁用。这可以是datetime.timedelta或秒数。默认为30分钟。
- leeway
容差值,允许验证过去的过期时间,但不是很久以前。可以使用datetime.timedelta或秒数。默认值为0。
- allow_refresh
将此设置为True将启用刷新API。默认为False。
- refresh_delta
考虑容差值的令牌刷新的时间差。这可以是datetime.timedelta或秒数。默认值为7天。当为None时,您可以始终刷新令牌。
- refresh_nonce_handler
回调函数的点路径,该函数接收用户ID作为参数,并返回一个在刷新前要验证的非ce。当为None时,不会创建或验证任何nonce以刷新。默认为None。
- verify_expiration_on_refresh
如果为False,则JWT令牌的expiration_delta在刷新期间将不会被检查。否则,只有在令牌尚未过期的情况下才能刷新令牌。默认为False。
- issuer
这是一个字符串,将与其令牌的iss声明进行比较。例如,如果您有多个相关应用程序,它们有专有的用户受众,则可以使用此功能。默认为None(不要在JWT上检查iss)。
- auth_header_prefix
您可以根据需要修改需要与令牌一起发送的Authorization头值的默认值。默认值是JWT。另一个常见的值是Bearer。
- userid_claim
包含用户ID的声明。默认声明是“sub”。
该库接受master_secret或private_key/public_key对。在后一种情况下,算法必须是EC*、PS*或RS*版本。
算法
JWT规范支持多种加密签名算法。该库当前支持
- HS256
使用SHA-256哈希算法的HMAC(默认)
- HS384
使用SHA-384哈希算法的HMAC
- HS512
使用SHA-512哈希算法的HMAC
- ES256 [1]
使用SHA-256哈希算法的ECDSA签名算法
- ES384 [1]
使用SHA-384哈希算法的ECDSA签名算法
- ES512 [1]
使用SHA-512哈希算法的ECDSA签名算法
- PS256 [1]
使用SHA-256和MGF1填充的SHA-256的RSASSA-PSS签名
- PS384 [1]
使用SHA-384和MGF1填充的SHA-384的RSASSA-PSS签名
- PS512 [1]
使用SHA-512和MGF1填充的SHA-512的RSASSA-PSS签名
- RS256 [1]
使用SHA-256哈希算法的RSASSA-PKCS1-v1_5签名算法
- RS384 [1]
使用SHA-384哈希算法的RSASSA-PKCS1-v1_5签名算法
- RS512 [1]
使用SHA-512哈希算法的RSASSA-PKCS1-v1_5签名算法
开发更多.jwtauth
为开发安装更多.jwtauth
从github克隆more.jwtauth
.. code-block:: console
git clone git@github.com:morepath/more.jwtauth.git
如果这不起作用,并且您收到错误“Permission denied (publickey)”,则需要将您的ssh公钥上传到github。
然后转到more.jwtauth目录
.. code-block:: console
cd more.jwtauth
请确保您已安装virtualenv。
在more.jwtauth目录内创建一个新的virtualenv
.. code-block:: console
python -m venv .venv
激活virtualenv
.. code-block:: console
source .venv/bin/activate
在virtualenv内部,请确保您已安装最新的setuptools和pip
.. code-block:: console
pip install -U setuptools pip
从develop_requirements.txt安装各种依赖和开发工具
.. code-block:: console
pip install -Ur develop_requirements.txt
要升级依赖项,只需再次运行该命令。
注意
以下命令仅在激活了virtualenv的情况下才有效。
安装Black集成前的pre-commit钩子
我们使用Black来格式化代码,并建议在提交之前安装pre-commit钩子以集成Black。
pre-commit install
运行测试
您可以使用pytest运行测试
.. code-block:: console
pytest
要生成HTML格式的测试覆盖率信息,请执行
.. code-block:: console
pytest –cov –cov-report html
然后您可以将网络浏览器指向项目目录中的htmlcov/index.html文件,并单击模块以查看详细的覆盖率信息。
Black
要在根目录中使用Black代码格式化器格式化代码,请运行
black .
Black还提供了大多数流行编辑器的集成。
各种检查工具
flake8是一个工具,可以使用pyflakes对常见的Python错误进行检查,检查PEP8样式合规性,并进行循环复杂度检查。要执行pyflakes和pep8检查,请执行
.. code-block:: console
flake8 more.jwtauth
要显示循环复杂度,请使用此命令
.. code-block:: console
flake8 –max-complexity=10 more.jwtauth
Tox
使用tox,您可以在不同的Python环境中测试Morepath。
我们在Morepath的GitHub仓库中安装了Travis持续集成,并在每次提交后运行相同的tox测试。
首先,您应该安装所有要测试的Python版本。未安装的版本将被跳过。您至少应该安装Python 3.7,这是flake8、coverage和doctests所必需的。
您可以使用pyenv安装多个Python版本。
要找出tox.ini中为Morepath定义的测试环境,请运行
.. code-block:: console
tox -l
您可以使用以下命令运行所有tox测试
.. code-block:: console
tox
您还可以指定要运行的测试环境,例如
.. code-block:: console
tox -e py37 tox -e pep8 tox -e coverage
变更记录
0.14 (2024-03-02)
升级PyJWT和Cryptography依赖项。
修复test_refresh。
已移除:取消对Python 3.6和3.7的支持。
添加对Python 3.10、3.11和3.12的支持。
在测试输出中显示完整差异。
更新pre-commit修订版本。
调整README。
0.13 (2022-06-19)
移除对已编码令牌的过时解码。
升级PyJWT和Cryptography依赖项。
取消对Python 3.5的支持。
添加对Python 3.9的支持。
使用GitHub Actions进行CI。
0.12 (2020-04-26)
已移除:移除对Python 2和Python 3.4的支持。
如果您想使用此版本,您必须升级到Python 3。
添加了对Python 3.7和3.8以及PyPy 3.6的支持。
将Python 3.7设置为默认测试环境。
将PyJWT升级到版本1.7.1,将cryptography升级到版本2.9.2。
添加对Black代码格式化器的集成。
0.11 (2018-01-18)
取消对Python 3.3的支持,并添加对Python 3.6的支持。
将PyJWT升级到版本1.5.3,将cryptography升级到版本2.1.4。
0.10 (2017-12-08)
破坏性更改:向refresh_nonce_handler添加请求参数(请参阅问题#8)。
0.9 (2017-03-02)
新增:添加刷新JWT令牌的API(请参阅问题#6)。
本实现增加了4个新设置
allow_refresh:当为 True 时启用令牌刷新 API。
refresh_delta:考虑宽容度后,令牌可以刷新的时间间隔。
refresh_nonce_handler:回调函数的点路径,该函数接收用户ID作为参数,并返回一个在刷新前需要验证的nonce。
verify_expiration_on_refresh:如果为False,则不会在刷新时检查JWT令牌的expiration_delta。否则,只有当令牌尚未过期时,您才能刷新令牌。
此外,当启用刷新时,还会向令牌添加2个声明
refresh_until:令牌可以刷新的截止时间戳。
nonce:由refresh_nonce_handler返回的nonce。
详细信息请参阅README.rst。
已删除: verify_expiration设置已被删除,因为它主要用于自定义处理令牌刷新,现在已经过时。
将算法显式传递给jwt.decode()以避免一些漏洞。有关详细信息,请参阅Tim McLean关于一些“JSON Web Token库中的关键漏洞”的博客文章。
除了datetime.timedelta外,还允许将expiration_delta和leeway作为秒数传递。
进行了一些代码清理和重构。
0.8 (2016-10-21)
我们现在使用virtualenv和pip来设置开发环境,并在README中相应地添加了开发部分。
审查和优化tox配置。
升级到PyJWT 1.4.2和Cryptography 1.5.2。
0.7 (2016-07-20)
升级到Morepath 0.15。
升级到PyJWT 1.4.1和Cryptography 1.4。
添加Python 3.5的testenv并将其设置为默认测试环境。
将作者更改为“Morepath开发者”。
清理分类。
0.6 (2016-05-19)
将Cryptography设置为可选。
重大更改:现在,如果您要使用HMAC以外的其他算法,则需要显式安装crypto依赖项。请参阅需求部分和README.rst中的新安装部分中的说明。
在README中添加安装部分。
重构cryptography测试套件。
0.5 (2016-04-25)
添加一些测试。
将覆盖率提高到100%。
添加travis-ci和tox集成。
一些清理。
升级到Morepath 0.14。
改进了设置和发布工作流程。
0.4 (2016-04-13)
升级到Morepath 0.13.2并更新测试。
将PyJWT升级到1.3.0并将cryptography升级到1.3.1。
将其作为PyPI软件包发布。修复了Issue #1。
0.3 (2016-04-13)
将PyJWT升级到1.4.0并将cryptography升级到0.9.1。
Python 3.2不再受支持。这个版本的Python很少使用。受此影响的PyUsers应升级到3.3+。
一些清理。
0.2 (2015-06-29)
将set_jwt_auth_header函数集成到身份策略中作为remember方法。
添加对PS256、PS384和PS512算法的支持。
直接将设置作为参数传递给JWTIdentityPolicy类,并可以使用Morepath 0.11中引入的方法使用Morepath设置覆盖它们。
现在直接使用JWTIdentityPolicy而无需从JwtApp继承,因此已删除JwtApp。
在README中添加简介和用法部分。
将所有函数作为JWTIdentityPolicy类的方法集成。
重构测试套件。
0.1 (2015-04-15)
首次公开发布。
项目详情
下载文件
下载适用于您平台的文件。如果您不确定选择哪一个,请了解有关安装包的更多信息。