认证和授权

身份验证是将请求映射到用户的过程。有不同的方法可以做到这一点,从简单的HTTP基本身份验证到cookie或基于令牌的系统。

授权是定义授权用户可以做什么的过程。除了琐碎的系统之外,还需要一个数据存储来存储这些授权信息。这些数据存储的范围从真正简单的文本文件(允许此文本文件中的所有用户执行所有操作)到具有关系数据库的复杂模式(允许用户A执行B而不是C等操作)。

如您所见,在实现用于身份验证和授权的系统时要选择的选项是多样的。开发人员(SDI的开发人员,而不是软件本身)通常有特定的限制,例如数据库中的现有用户数据或Web GIS网站上的现有登录页面。所以很难提供一刀切的解决方案。

因此,maproxy不附带任何嵌入的身份验证或授权。但是它附带了一个灵活的授权接口,允许您(SDI开发人员)实现定制的系统。

幸运的是,有很多现有的工具包可以用来构建符合您需求的系统。对于身份验证,有 repoze.who 包装用 plugins for HTTP Basic Authentication, HTTP cookies, etc . 授权有 repoze.what 包装用 plugins for SQL datastores, etc .

注解

开发定制的认证和授权系统需要一点python编程和 WSGI 和wsgi中间件。

认证/授权中间件

您的认证系统应该作为一个WSGi中间件实现。中间件位于Web服务器和maproxy之间。

wsgi过滤器中间件

授权随机请求的简单中间件可能如下所示:

class RandomAuthFilter(object):
    def __init__(self, app, global_conf):
        self.app = app

    def __call__(self, environ, start_response):
        if random.randint(0, 1) == 1:
          return self.app(environ, start_response)
        else:
          start_response('403 Forbidden',
            [('content-type', 'text/plain')])
          return ['no luck today']

您需要使用自定义身份验证中间件包装maproxy应用程序。对于部署脚本,可能如下所示:

application = make_wsgi_app('./mapproxy.yaml')
application = RandomAuthFilter(application)

为了 PasteDeploy 你可以使用 filter-with 选择权。这个 config.ini 看起来像:

[app:mapproxy]
use = egg:MapProxy#app
mapproxy_conf = %(here)s/mapproxy.yaml
filter-with = auth

[filter:auth]
paste.filter_app_factory = myauthmodule:RandomAuthFilter

[server:main]
...

您可以使用该方法实现简单的身份验证系统,但是您应该看看 repoze.who 在重新发明轮子之前。

授权回调

授权有点复杂,因为您的中间件需要解释请求以获取授权所需的信息(例如,WMS Getmap请求的层名称)。将getCapabilities响应限制到某些层甚至需要中间件来操作XML文档。所以很明显,授权的某些部分应该由maproxy处理。

maproxy可以在知道要请求什么(例如,WMS getmap请求的层名称)后立即调用中间件进行授权。您必须将回调函数传递给环境,以便maproxy知道要调用什么。

下面是一个更详细的示例,它拒绝对以特定前缀开头的所有层的请求。这些层也隐藏在功能文档中。

class SimpleAuthFilter(object):
    """
    Simple MapProxy authorization middleware.

    It authorizes WMS requests for layers where the name does
    not start with `prefix`.
    """
    def __init__(self, app, prefix='secure'):
        self.app = app
        self.prefix = prefix

    def __call__(self, environ, start_response):
        # put authorize callback function into environment
        environ['mapproxy.authorize'] = self.authorize
        return self.app(environ, start_response)

    def authorize(self, service, layers=[], environ=None, **kw):
        allowed = denied = False
        if service.startswith('wms.'):
            auth_layers = {}
            for layer in layers:
                if layer.startswith(self.prefix):
                    auth_layers[layer] = {}
                    denied = True
                else:
                    auth_layers[layer] = {
                        'map': True,
                        'featureinfo': True,
                        'legendgraphic': True,
                    }
                    allowed = True
        else: # other services are denied
          return {'authorized': 'none'}

        if allowed and not denied:
            return {'authorized': 'full'}
        if denied and not allowed:
            return {'authorized': 'none'}
        return {'authorized': 'partial', 'layers': auth_layers}

这里是 config.py 在这里我们定义过滤器和传递自定义选项:

application = make_wsgi_app('./mapproxy.yaml')
application = SimpleAuthFilter(application, prefix='secure')

映射代理授权API

maproxy在请求环境中查找 mapproxy.authorize 条目。此项应包含可调用(函数或方法)。如果找不到任何可调用的,那么maproxy假定未启用授权,并且允许所有请求。

授权职能部门签字:

authorize(service, layers=[], environ=None, **kw)
参数
  • service -- 应授权的服务

  • layers -- 应授权的层名称列表

  • environ -- 请求环境

返回类型

dictionary with authorization information

这些参数可能在未来版本的maproxy中得到扩展。因此,您应该在catch all关键字参数(即 **kw

注解

可调用文件的实际名称不重要,只有环境键 mapproxy.authorize 很重要。

这个 service 参数是字符串,内容取决于调用authorize函数的服务。通常,它是服务的小写名称(例如 tms 对于TMS服务),但进一步控制服务可能不同(例如 wms.map

函数应返回包含授权信息的字典。该字典的预期内容可能因每个服务而异。只有 authorized 密钥与所有服务一致。

这个 authorized 条目可以有四个值。

full

给定的请求 servicelayers 完全授权。maproxy处理请求时好像没有授权一样。

partial

只允许部分请求。字典应包含有关允许请求的哪些部分和拒绝哪些部分的更多信息。然后,根据服务的不同,maproxy可以基于该信息过滤请求,例如,仅返回具有允许层的WMS功能。

none

请求被拒绝,Mapproxy返回HTTP 403(禁止)响应。

unauthenticated

请求(ER)未通过身份验证,Mapproxy返回HTTP 401响应。您的中间件可以捕获这个信息并请求请求者进行身份验证。 repoze.whoPluggableAuthenticationMiddleware 例如,将执行此操作。

1.1.0 新版功能: 这个 environment 参数和支持 authorized: unauthenticated 结果。

limited_to

您可以为每个请求限制地理区域。maproxy将把每个请求剪辑到所提供的几何图形中——许可区域之外的区域变为透明。

根据服务的不同,maproxy支持对整个请求或每个层进行此剪辑。你需要提供一本字典 bboxgeometry 以及 srs 几何图形。支持以下几何值:

BBOX:

边界框作为minx、miny、maxx、maxy的列表。

WKT多边形:

使用一个或多个多边形和多个多边形作为wkt的字符串。多个WKT必须由新行字符分隔。如果从空间数据库获取几何图形,则返回此类型。

形状几何:

形状几何对象。如果已经用 Shapely .

以下是WMS的回调结果示例 GetMap 请求所有三种几何类型。其他服务示例如下:

{
  'authorized': 'partial',
  'layers': {
    'layer1': {
      'map': True,
      'limited_to': {
        'geometry': [-10, 0, 30, 50],
        'srs': 'EPSG:4326',
      },
    },
    'layer2': {
      'map': True,
      'limited_to': {
        'geometry': 'POLYGON((...))',
        'srs': 'EPSG:4326',
      },
    },
    'layer3': {
      'map': True,
      'limited_to': {
        'geometry': shapely.geometry.Polygon(
          [(-10, 0), (30, -5), (30, 50), (20, 50)]),
        'srs': 'EPSG:4326',
      }
    }
  }
}

性能

剪切速度相当快,但是如果您注意到开销很大,那么应该减少由授权回调返回的几何图形的复杂性。通过返回投影中的几何图形,可以提高性能 query_extent 将其限制在 query_extent 通过简化几何结构。参考 ST_TransformST_IntersectionST_SimplifyPreserveTopology 从Postgis查询几何图形时的函数。

WMS服务

WMS服务需要 layers 授权字典中的条目 partial 结果。 layers 它本身应该是一个包含所有层的字典。所有丢失的层都被解释为拒绝的层。

每一层都包含有关允许功能的信息。丢失的功能被解释为拒绝的功能。

下面是对authorize函数调用的示例结果:

{
  'authorized': 'partial',
  'layers': {
    'layer1': {
      'map': True,
      'featureinfo': False,
    },
    'layer2': {
      'map': True,
      'featureinfo': True,
    }
  }
}

limited_to

1.4.0 新版功能.

WMS服务支持 limited_to 对于 GetCapabilitiesGetMapGetFeatureInfo 请求。maproxy将修改每个限制层的边界框 GetCapabilities 请求。 GetFeatureInfo 只有当信息坐标在允许的区域内时,请求才会返回数据。为了 GetMap 请求时,maproxy会将每个层剪辑到提供的几何图形中-允许区域之外的区域在 bgcolor WMS请求的。

您可以为每个层或整个请求提供几何图形。

limited_to 了解更多详细信息。

以下是两个有限层和一个无限层的回调结果示例:

{
  'authorized': 'partial',
  'layers': {
    'layer1': {
      'map': True,
      'limited_to': {
        'geometry': [-10, 0, 30, 50],
        'srs': 'EPSG:4326',
      },
    },
    'layer2': {
      'map': True,
      'limited_to': {
        'geometry': 'POLYGON((...))',
        'srs': 'EPSG:4326',
      },
    },
    'layer3': {
      'map': True,
    }
  }
}

以下是一个回调结果示例,其中完整请求受到限制:

{
  'authorized': 'partial',
  'limited_to': {
    'geometry': shapely.geometry.Polygon(
      [(-10, 0), (30, -5), (30, 50), (20, 50)]),
    'srs': 'EPSG:4326',
  },
  'layers': {
    'layer1': {
      'map': True,
    },
  }
}

服务类型

WMS服务使用以下服务字符串:

wms.map

这是为WMS GetMap请求调用的。 layers 是包含要渲染的实际层的列表,这意味着组层已解析。这个 map 功能需要设置为 True 对于每个允许的层。如果不允许任何请求层,则拒绝整个请求。如果不允许,解析层(即请求的组层的子层)将被过滤掉。

1.1.0 新版功能: 这个 authorize 使用附加的 query_extent 论点:

authorize(service, environ, layers, query_extent, **kw)
参数

query_extent -- 一组SRS(例如 EPSG:4326 )以及请求授权的bbox。

例子

有一层树像:

- name: layer1
  layers:
    - name: layer1a
      sources: [l1a]
    - name: layer1b
      sources: [l1b]

授权结果:

{
  'authorized': 'partial',
  'layers': {
    'layer1':  {'map': True},
    'layer1a': {'map': True}
  }
}

结果如下:

  • 请求 layer1 渲染 layer1alayer1b 被过滤掉。

  • 请求 layer1a 渲染 layer1a .

  • 请求 layer1b 被拒绝。

  • 请求 layer1alayer1b 被拒绝。

wms.featureinfo

这是为WMS GetFeatureInfo请求调用的,其行为类似于 wms.map .

wms.capabilities

这是为WMS getCapabilities请求调用的。 layers 是包含WMS服务所有命名层的列表。只有具有 map 特征集到 True 包含在能力文档中。不包括缺少的层。

只有在包含父层时才包含子层,因为授权接口无法对层树重新排序。注意,您仍然可以请求这些子层(请参见 wms.map 以上)。

可查询且仅在功能中标记的层,如果 featureinfo 特征集到 True .

有一层树像:

- name: layer1
  layers:
    - name: layer1a
      sources: [l1a]
    - name: layer1b
      sources: [l1b]
    - name: layer1c
      sources: [l1c]

授权结果:

{
  'authorized': 'partial',
  'layers': {
    'layer1':  {'map': True, 'feature': True},
    'layer1a': {'map': True, 'feature': True},
    'layer1b': {'map': True},
    'layer1c': {'map': True},
  }
}

结果得到以下简化功能:

<Layer queryable="1">
  <Name>layer1</Name>
  <Layer queryable="1"><Name>layer1a</Name></Layer>
  <Layer><Name>layer1b</Name></Layer>
</Layer>

TMS/Tile服务

TMS服务期望 layers 授权字典中的条目 partial 结果。 layers 它本身应该是一个包含所有层的字典。所有丢失的层都被解释为拒绝的层。

每一层都包含有关允许功能的信息。TMS服务只支持 tile 特征。丢失的功能被解释为拒绝的功能。

下面是对authorize函数调用的示例结果:

{
  'authorized': 'partial',
  'layers': {
    'layer1': {'tile': True},
    'layer2': {'tile': False},
  }
}

TMS服务使用 tms 作为所有授权请求的服务字符串。

只有具有 tile 特征集到 True 包含在TMS能力文件中 (/tms/1.0.0 )不包括缺少的层。

这个 authorize 使用附加的 query_extent 所有平铺请求的参数:

authorize(service, environ, layers, query_extent=None, **kw)
参数

query_extent -- 一组SRS(例如 EPSG:4326 )以及授权请求的bbox,或 None 功能请求。

limited_to

1.5.0 新版功能.

maproxy将把每个切片裁剪到提供的几何图形上——许可区域之外的区域将变得透明。在这种情况下,maproxy将返回png图像。

下面是一个回调结果示例,其中tile请求受到限制:

{
  'authorized': 'partial',
  'limited_to': {
    'geometry': shapely.geometry.Polygon(
      [(-10, 0), (30, -5), (30, 50), (20, 50)]),
    'srs': 'EPSG:4326',
  },
  'layers': {
    'layer1': {
      'tile': True,
    },
  }
}

1.5.1 新版功能.

还可以将限制添加到层,并将其与用于其他服务的属性混合:

{
  'authorized': 'partial',
  'layers': {
    'layer1': {
      'tile': True,
      'map': True,
      'limited_to': {
        'geometry': shapely.geometry.Polygon(
          [(-10, 0), (30, -5), (30, 50), (20, 50)]),
        'srs': 'EPSG:4326',
      },
    'layer2': {
      'tile': True,
      'map': False,
      'featureinfo': True,
      'limited_to': {
        'geometry': shapely.geometry.Polygon(
          [(0, 0), (20, -5), (30, 50), (20, 50)]),
        'srs': 'EPSG:4326',
      },
    },
  }
}

limited_to 了解更多详细信息。

KML服务

KML授权类似于TMS授权,包括 limited_to 选择权。

KML服务使用 kml 作为所有授权请求的服务字符串。

WMTS服务

WMTS授权类似于TMS授权,包括 limited_to 选择权。

WMTS服务使用 wmts 作为tile请求和的服务字符串 wmts.featureinfo 用于功能信息请求。

1.12 新版功能: wmts.featureinfo

演示服务

演示服务只支持 fullnone 授权。 layers 总是空列表。演示服务不授权概述页面中列出的服务和层。如果您允许用户访问演示服务,那么他可以看到所有服务和层的名称。但是,对这些服务的访问仍然受到相应授权的限制。

服务字符串是 demo .

MultiMapProxy

这个 MultiMapProxy 应用程序将环境中的实例名存储为 mapproxy.instance_name . 当中间件被调用时,此信息不可用,但您可以在授权功能中使用它。

拒绝名称以开头的maproxy实例的示例 secure . ::

class MultiMapProxyAuthFilter(object):
    def __init__(self, app, global_conf):
        self.app = app

    def __call__(self, environ, start_response):
        environ['mapproxy.authorize'] = self.authorize
        return self.app(environ, start_response)

    def authorize(self, service, layers=[]):
        instance_name = environ.get('mapproxy.instance_name', '')
        if instance_name.startswith('secure'):
            return {'authorized': 'none'}
        else:
            return {'authorized': 'full'}