8.9 将装饰器用于安全性

    软件中总是充满了需要提供一致实现的横切关注点和方面,即使它们属于不同的类层次结构。试图将类层次结构强加于一个横切关注点是一种常见错误。我们已经看了一些这样的例子,例如日志和审计。

    强制每个需要写日志的类都从一些loggable的基类继承是不合理的。我们可以设计一个loggable的mixin或者一个loggable的装饰器。这并不会影响我们需要设计出一种能够让多态正常工作的正确的继承结构。

    有一些和安全性相关的横切关注点。在一个Web应用程序中,有两个方面的安全问题需要考虑。

    • 验证:我们知道是谁发起的请求吗?
    • 授权:通过验证的用户是否有权限发起请求?

    一些Web框架允许我们根据对于安全性的需求装饰我们的请求处理器。例如,Django框架包含了许多装饰器,它们允许我们为一个视图函数或者一个视图类指定安全要求。下面是其中的一些装饰器。

    • user_passes_test:这是一个底层的通用装饰器,并且被用于创建另外两个装饰器。它需要一个测试函数,请求关联的已登录的User对象必须通过这个给定函数来进行测试。如果User实例无法通过这个测试,它们会被重定向到一个登录页面,这样用户就可以提供能够发起请求的凭据。
    • login_required:这个装饰器基于user_passses_test。它用于确保已登录的用户是通过验证的。这种类型的装饰器会应用在所有访问者的请求上。例如,像更改密码或者登出这种请求,就不应该需要任何特殊的许可。
    • permission_required:这个装饰器与Django内置的数据库权限系统配合使用。它用来确认已登录的用户(或者用户组)拥有某种特定的许可。这种类型的装饰器用于处理需要特定管理权限的请求。

    其他的包和框架中也有表达这种Web应用程序中横切方面的方法。在许多情况下,Web应用程序甚至有更严格的安全性要求。我们可以考虑设计一个能够根据合约条例有选择性地完成解锁功能的Web应用程序,但必须设计一个下面这样的测试。

    def user_has_feature( feature_name ):
      def has_feature( user ):
        return feature_name in (f.name for f in user.feature_set())
      return user_passes_test( has_feature )

    我们定义了一个函数,用于检测登录用户的feature_set集合确定某个功能是否和当前的用户相关。我们在has_feature()函数中用Django的uesr_passes_test装饰器创建了一个可以被应用于相关的view函数的新装饰器。然后,我们可以像下面这样创建一个view函数。

    @user_has_feature( 'special_bonus' )
    def bonus_view( request ):
      pass

    这确保了安全性关注点会一致地应用于多个不同的view函数。