跳转到主要内容

Pyramid的JWT身份验证策略

项目描述

JWT身份验证用于Pyramid

此包实现了一个Pyramid的身份验证策略,使用JSON Web Tokens。这个标准(RFC 7519)常用于保护后端API。使用了优秀的PyJWT库来实现JWT编码/解码逻辑。

在Pyramid应用程序中启用JWT支持非常简单

from pyramid.config import Configurator
from pyramid.authorization import ACLAuthorizationPolicy

def main():
    config = Configurator()
    # Pyramid requires an authorization policy to be active.
    config.set_authorization_policy(ACLAuthorizationPolicy())
    # Enable JWT authentication.
    config.include('pyramid_jwt')
    config.set_jwt_authentication_policy('secret')

这将使用带有JWT方案的Authorization HTTP头设置JWT身份验证策略来检索令牌。使用另一个HTTP头非常简单

config.set_jwt_authentication_policy('secret', http_header='X-My-Header')

如果您的应用程序需要解码包含受众声明的令牌,您可以扩展此功能

config.set_jwt_authentication_policy('secret',
                                    auth_type='Bearer',
                                    callback=add_role_principals,
                                    audience="example.org")

为了使创建有效的令牌更简单,在请求中添加了新的create_jwt_token方法。您可以在视图中使用此方法创建令牌。一个简单的REST后端的简单身份验证视图可能如下所示

@view_config('login', request_method='POST', renderer='json')
def login(request):
    login = request.POST['login']
    password = request.POST['password']
    user_id = authenticate(login, password)  # You will need to implement this.
    if user_id:
        return {
            'result': 'ok',
            'token': request.create_jwt_token(user_id)
        }
    else:
        return {
            'result': 'error'
        }

除非您在cookie中使用JWT cookie(见下一节),否则标准的remember()forget()函数在Pyramid中并不有用。在启用常规(基于头的)JWT身份验证时尝试使用它们将导致警告。

在cookie中使用JWT

可选地,您可以使用cookie作为JWT Cookies的传输方式。这是一种有用的技术,允许基于浏览器的Web应用无需管理令牌存储(JWT cookie的存储位置是一个已知问题)来消费您的REST API,因为http_only cookie无法由页面上的JavaScript处理。

在cookie中使用JWT有一些额外的优势,首先是滑动会话:当reissue_time过期时,cookie中的令牌将自动重新颁发。

from pyramid.config import Configurator
from pyramid.authorization import ACLAuthorizationPolicy

def main():
    config = Configurator()
    # Pyramid requires an authorization policy to be active.
    config.set_authorization_policy(ACLAuthorizationPolicy())
    # Enable JWT authentication.
    config.include('pyramid_jwt')
    config.set_jwt_cookie_authentication_policy(
        'secret', reissue_time=7200
    )

当单独使用JWT时,没有手动使令牌失效的标准:要么令牌的有效期过期,要么应用程序需要处理令牌黑名单(或者更好的是,白名单)。

另一方面,当使用cookie时,这个库允许应用程序通过删除cookie来注销特定用户:此策略遵循大多数浏览器所尊重的标准cookie删除机制,因此对Pyramid的forget()函数的调用将指示浏览器删除该cookie,从而有效地丢弃该JWT令牌,即使它可能仍然有效。

有关示例,请参阅在cookie中创建JWT

额外声明

通常,pyramid_jwt只创建一个JWT声明:将主题(或sub声明)设置为主体。您还可以通过将关键字参数传递给create_jwt_token方法来向令牌添加额外声明。

token = request.create_jwt_token(user.id,
    name=user.name,
    admin=(user.role == 'admin'))

可以在请求的jwt_claims字典属性上访问JWT令牌中找到的所有声明。对于上述示例,您可以直接从请求中检索用户的名字和管理员状态。

print('User id: %d' % request.authenticated_userid)
print('Users name: %s', request.jwt_claims['name'])
if request.jwt_claims['admin']:
   print('This user is an admin!')

请注意,jwt_claims数据仅反映JWT令牌中的声明,并不会检查用户是否有效:配置的认证策略的回调不会进行检查。因此,您始终应该使用request.authenticated_userid而不是request.jwt_claims['sub']

您还可以使用额外声明来管理用户的额外主体。例如,您可以将代表添加用户组成员资格或角色的声明添加到用户中。这需要两个步骤:首先,如上所示,将额外声明添加到JWT令牌中,然后使用认证策略的回调钩子将额外声明转换为主体。以下是一个快速示例

def add_role_principals(userid, request):
   return ['role:%s' % role for role in request.jwt_claims.get('roles', [])]

config.set_jwt_authentication_policy(callback=add_role_principals)

然后,您可以在ACL中使用角色主体

class MyView:
    __acl__ = [
        (Allow, Everyone, ['read']),
        (Allow, 'role:admin', ['create', 'update']),
    ]

验证示例

通过API使用create_jwt_token创建和返回令牌后,您可以通过发出JWT类型的HTTP授权头进行测试。

GET /resource HTTP/1.1
Host: server.example.com
Authorization: JWT eyJhbGciOiJIUzI1NiIXVCJ9...TJVA95OrM7E20RMHrHDcEfxjoYZgeFONFh7HgQ

我们可以使用curl进行测试。

curl --header 'Authorization: JWT TOKEN' server.example.com/ROUTE_PATH
config.add_route('example', '/ROUTE_PATH')
@view_config(route_name=example)
def some_action(request):
    if request.authenticated_userid:
        # Do something

设置

有一些标志指定了如何创建和验证令牌。您可以在.ini文件中设置这些标志,或者直接传递/覆盖它们到config.set_jwt_authentication_policy()函数。

参数

ini文件条目

默认值

描述

private_key

jwt.private_key

用于散列或签名令牌的密钥。

public_key

jwt.public_key

用于验证令牌签名的密钥。仅用于非对称算法。

algorithm

jwt.algorithm

HS512

散列或加密算法

expiration

jwt.expiration

在令牌过期之前,以秒(或datetime.timedelta实例)为单位的时间。

audience

jwt.audience

令牌的建议受众

leeway

jwt.leeway

0

在拒绝令牌之前,允许令牌过期的秒数。

http_header

jwt.http_header

Authorization

用于令牌的HTTP头

auth_type

jwt.auth_type

JWT

用于授权头的认证类型。对于其他HTTP头不使用。

json_encoder

None

用于编码主体和声明的JSONEncoder的子类。

以下选项适用于基于cookie的认证策略

参数

ini文件条目

默认值

描述

cookie_name

jwt.cookie_name

Authorization

用于识别cookie的密钥。

cookie_path

jwt.cookie_path

None

cookie的路径。

https_only

jwt.https_only_cookie

True

是否仅通过安全的HTTPS传输发送令牌

reissue_time

jwt.cookie_reissue_time

None

在重新发布cookie(以及其中的令牌)之前,需要多少秒(或datetime.timedelta实例)

Pyramid JWT示例用例

这是一份基本指南(以下所有声明将假设您已遵循此项目的Readme),它将解释如何(以及为什么)使用JWT来保护/限制对Pyramid REST风格后端API的访问,本指南将简要介绍

  • 创建JWT

  • 解码JWT

  • 通过JWT限制对某些Pyramid视图的访问

创建JWT

首先,让我们从我们的Pyramid项目中的第一个视图开始,这通常是一个登录视图,这个视图没有任何权限与之关联,任何用户都可以访问并提交登录凭据,例如

def authenticate_user(login, password):
    # Note the below will not work, its just an example of returning a user
    # object back to the JWT creation.
    login_query = session.query(User).\
        filter(User.login == login).\
        filter(User.password == password).first()

    if login_query:
        user_dict = {
            'userid': login_query.id,
            'user_name': login_query.user_name,
            'roles': login_query.roles
        }
        # An example of login_query.roles would be a list
        # print(login_query.roles)
        # ['admin', 'reports']
        return user_dict
    else:
        # If we end up here, no logins have been found
        return None

@view_config('login', request_method='POST', renderer='json')
def login(request):
    '''Create a login view
    '''
    login = request.POST['login']
    password = request.POST['password']
    user = authenticate(login, password)
    if user:
        return {
            'result': 'ok',
            'token': request.create_jwt_token(
                                            user['userid'],
                                            roles=user['roles'],
                                            userName=user['user_name']
                                            )
        }
    else:
        return {
            'result': 'error',
            'token': None
        }

现在这个操作会返回您的JWT到您可能拥有的任何前端应用程序,包括用户详细信息以及他们的权限,这将返回一个解码后的令牌,如下所示

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyTmFtZSI6Imx1a2UiLCJyb2xlcyI6WyJhZG1pbiIsInJlcG9ydHMiXSwic3ViIjo0LCJpYXQiOjE1MTkwNDQyNzB9.__KjyW1U-tpAEvTbSJsasS-8CaFyXH784joUPONH6hQ

建议您访问JWT.io,将此数据复制到他们的页面,您将看到解码后的令牌

{
  "userName": "luke",
  "roles": [
    "admin",
    "reports"
  ],
  "sub": 4,
  "iat": 1519044270
}

注意,在jwt.io网页底部,签名显示已验证,如果您更改底部的“secret”,它将显示“未验证”,这是因为为了验证任何JWT过程,必须使用有效的“secret”或“private key”。需要注意的是,任何在JWT中发送的数据都可以被任何人访问和读取。

解码JWT

以下部分也适用于Pyramid没有创建JWT的情况,Pyramid解码JWT所需了解的只是创建/签署原始JWT时使用的“secret”或“private key”。JWT本质上是不可靠的,但可以用来“保护”。在我们的示例中,我们在JWT中返回了“roles”数组,它有两个属性“admin”和“reports”,因此我们可以在Pyramid应用程序中设置一个ACL来将JWT权限映射到基于Pyramid的安全性,例如,在我们的项目的__init__.py中,我们可以添加以下内容

from pyramid.security import ALL_PERMISSIONS

class RootACL(object):
    __acl__ = [
        (Allow, 'admin', ALL_PERMISSIONS),
        (Allow, 'reports', ['reports'])
    ]

    def __init__(self, request):
        pass

此ACL将允许具有JWT中“admin”角色的任何用户访问所有受权限保护的视图,而具有“reports”的JWT用户将只能访问受“reports”权限保护的视图。

现在,此ACL本身不足以将JWT权限映射到Pyramid的安全性后端,我们还需要在__init__.py中添加以下内容

from pyramid.authorization import ACLAuthorizationPolicy


def add_role_principals(userid, request):
    return request.jwt_claims.get('roles', [])

def main(global_config, **settings):
    """ This function returns a Pyramid WSGI application.
    """
    config = Configurator(settings=settings)
    ...
    # Enable JWT - JSON Web Token based authentication
    config.set_root_factory(RootACL)
    config.set_authorization_policy(ACLAuthorizationPolicy())
    config.include('pyramid_jwt')
    config.set_jwt_authentication_policy('myJWTsecretKeepThisSafe',
                                        auth_type='Bearer',
                                        callback=add_role_principals)

此代码将映射JWT“roles”属性的任何属性,通过ACL运行,然后将其与Pyramid的安全性框架绑定。

这是如何做到安全的?

例如,JWT可以被轻易操纵,任何人都可以劫持令牌,更改“roles”数组中的值以访问他们实际上没有访问权限的视图。错误!pyramid_jwt在解码过程中会检查所有JWT令牌的签名,如果它发现令牌的签名与预期不符,这意味着要么应用程序已使用错误的私钥正确设置,要么攻击者试图操纵令牌。

使用JWT令牌时的主要安全问题是令牌本身存储在哪里:虽然pyramid_jwt能够检测篡改的令牌,但如果实际有效的令牌泄露,什么也无法做。任何拥有有效令牌的用户都会在您的应用程序中正确地进行身份验证。安全地存储令牌超出了本库的范围。

当在cookie中使用JWT时,浏览器(或消耗cookie的工具)负责存储它,但pyramid_jwt会将所有cookie的http_only标志设置为,因此页面上运行的javascript无法访问这些cookie,这有助于减轻XSS攻击。但仍然要指出的是,令牌仍然可以通过浏览器的调试/检查工具看到。

使用JWT保护视图

在上面的示例中,我们创建了一个“admin”角色,我们在ACL中赋予了所有权限,因此任何拥有此角色的用户都可以访问任何视图,例如。

@view_config(route_name='view_a', request_method='GET',
             permission="admin", renderer='json')
def view_a(request):
    return

@view_config(route_name='view_b', request_method='GET',
             permission="cpanel", renderer='json')
def view_b(request):
    return

此用户可以访问这两个视图,然而任何拥有“reports”权限的用户都无法访问这些视图,他们只能访问带有“reports”的权限。显然,在我们的用例中,一个用户同时拥有“admin”和“reports”权限,因此他们可以访问任何视图。

项目详情


下载文件

为您的平台下载文件。如果您不确定选择哪个,请了解更多关于安装包的信息。

源分布

pyramid_jwt-1.6.1.tar.gz (23.2 kB 查看哈希)

上传时间

构建分布

pyramid_jwt-1.6.1-py2.py3-none-any.whl (12.2 kB 查看哈希)

上传时间 Python 2 Python 3

支持者

AWS AWS 云计算和安全赞助商 Datadog Datadog 监控 Fastly Fastly CDN Google Google 下载分析 Microsoft Microsoft PSF 赞助商 Pingdom Pingdom 监控 Sentry Sentry 错误记录 StatusPage StatusPage 状态页面