在前面有关如何使用kbmMW创建REST服务器的基础上,现在已经到了考虑该如何控制用户的访问。什么是访问管理?就是“允许谁做什么"的问题。
显然,这个世界中存在数据,应该保护他而不被未授权的人/进程来读取,创建或更改。反过来说,这些数据应该得到保护,只有信任的人/进程才可以访问。另外,有的数据可以被人或进程来访问,但不允许被修改等等。
幸运的是,kbmMW内置了一些功能来解决上面的问题,核心是TkbmMWAuthorizationManager类。
首先在main form中添加一个TkbmMWAuthorizationManager(前文中的Unit7单元)。
我们可以单独使用授权管理器(authorization manager),但通常是通过kbmMWServer实例使用他,所以,将kbmMWServer1.AuthorizationManager属性设置为kbmMWAuthorizationManager1,
这样,授权管理器(authorization manager)将检查每次对应用程序服务器的访问权限。
kbmMW授权管理器是一个可理解为下面主题的实体:
- resource(资源)
- actor(演员)
- role(角色)
- authorization(授权)
- constraint(约束)
- login(登录)
resource(资源)基本上是你要为其添加某种保护的任意内容,它可以是数据库相关的,也可以是一个特定的对象,或者是你希望的功能或服务仅按你预期的方式处理,由你授予访问权限的人/进程处理。资源可以按资源树分组,允许访问其中一个资源,则自动允许访问该资源下的子资源。
actor通常是一个人(或一个人的登录凭证),一个过程或其他标识“某人”的人,他们想要访问您的资源。
role是一种对一般访问模式进行分类的方法。图书馆中的role可能是图书管理员,管理员和借用者。银行中的role可以是客户,出纳员,职员,管理员等等。这个想法是每个role对各种资源都有不同的访问权限。actor通常至少会被赋予一个role。actor可以拥有不同的role,例如:取决于actor登录的方式或来自哪里。
authorization是作为参与者或特定资源上的role运行的“许可”。authorization可以是否定的,如拒绝对特定资源及其子树的参与者或role访问。
constraint(约束)是对授权或登录的限制。授权可能仅在特定时间范围内有效,或者允许从特定设备等访问,或者登录只能在白天等进行。
login(登录)是actor/password和登录令牌之间的匹配。当actor试图登录时,系统会验证登录名,密码,请求的角色以及与登录相关的任何约束。只有在检查完所有内容并允许登录后,才会发出令牌(Token), 对基于kbmMW的服务器发出的每个请求,actor /user/process需要发送Token。
综上所述,让我们来定义两个想要访问REST服务器的角色(role),并将它们命名为“Reader”和“ReadWriter”,但由于kbmMW对角色命名(以及角色和资源)没有任何限制,只要名称在其类别中是唯一的,我们就可以为它们命名(角色,演员,资源)。
- Reader
- ReadWriter
用代码来定义上面说的两个角色(role)Reader与ReadWriter(可以写在main form的OnCreate事件中):
kbmMWAuthorizationManager1.AddRole('READER'); kbmMWAuthorizationManager1.AddRole('READWRITER');
我们还需要告诉授权管理器哪些参与者存在,以便它可以与演员匹配以尝试登录。
简单的方法是将它们预定义给授权管理器。同样,可以写在main form的OnCreate事件中,也可以写在第一次访问服务器之前的其他地方。按你的实际情况,利用数据库或配置文件或LDAP等定义actor。
kbmMWAuthorizationManager1.AddActor('HANS','HANSPASSWORD','READER'); kbmMWAuthorizationManager1.AddActor('CHRISTINE','CHRISTINEPASSWORD','READWRITER');
这里定义了两个参与者的密码,如果他们没有特别要求不同的角色,他们应该在登录时确定了扮演的角色。
有可能不预定义actor,取而代之使用事件处理程序,即通过kbmMWAuthorizationManager1实例的OnLogin事件验证它们是否存在于不同的系统中。
procedure TForm7.kbmMWAuthorizationManager1Login(Sender: TObject; const AActorName, ARoleName: string; var APassPhrase: string; var AActor: TkbmMWAuthorizationActor; var ARole: TkbmMWAuthorizationRole; var AMessage: string); begin ... end;
一个AActorName与其请求的角色名称由ARoleName提供。可选地,如果actor name 为kbmMW已知,则还可以提供actor实例。如果没有,AActor为nil,如果你知道actor则必须由你创建。
ARole可能是nil,如果它是一个被请求的未知角色。可以选择通过返回新创建的TkbmMWAuthorizationRole实例来动态建立角色。但请记住在返回之前,将任何新创建的actor或角色实例添加到kbmMWAuthorizationManagers的Actors和Roles列表属性中。
APassword将包含登录尝试时提供的密码。您可以动态修改它(例如将其更改为SHA256 hash,这样,授权管理器中将不存储明文密码)。
如果您为AActor或ARole返回nil ,则表示登录失败。如果需要,可以在AMessage参数中提供说明。
让我们继续为这个例子,定义一个简单的actor。
现在我们已定义了actors和roles,授权管理器已准备好处理登录尝试。
登录只有一种方法,即通过调用授权管理器的Login方法。例如,可以从REST服务中的新REST函数调用它。
另一种方法是让kbmMW自动检测登录尝试,并为您调用Login方法。为此,请将kbmMWAutorizationManager1的Options属性设置为[ mwaoAutoLogin ]。
您可能还记得,对kbmMW服务器的所有请求都必须附带一个标识有效登录的令牌。如果该令牌不可用,则使用从调用者传递的任何用户名/密码作为登录尝试的数据触发kbmMW(设置了mwaoAutoLogin),并在登录成功时将令牌返回给被调用者。
由于REST服务器本质上是一个Web服务器,遵循HTTP协议标准,当kbmMW检测到无效(或不存在)登录时会发生什么,kbmMW将引发一个EkbmMWAuthException,反过来(当调用来自REST时) streamformat),将被转换为HTTP错误401,呈现给调用者。实际上,如果您在业务代码中的任何位置引发该异常并且您自己不管理它,它将自动作为401转发给调用者。
这将提示大多数浏览器提供登录对话框,其中可以输入用户名/密码,然后下一次调用回服务器,将包括该登录信息。kbmMW将自动检测并使用它。
所以我们有演员,角色和登录。现在我们需要确定我们拥有哪些资源。资源可以是您要标记唯一名称的任何内容。
大多数情况下,将REST方法定义为资源是有意义的。使用kbmMW_Auth属性,让我们在smart service中非常容易完成。我们拥有操作和检索联系人的功能(Unit8)。
[kbmMW_Service('name:MyREST, flags:[listed]')] [kbmMW_Rest('path:/MyREST')] TkbmMWCustomSmartService8 = class(TkbmMWCustomSmartService) public [kbmMW_Auth('role:[READER,READWRITER], grant:true')] [kbmMW_Rest('method:get, path:helloworld, anonymousResult:true')] [kbmMW_Method] function HelloWorld:TMyResult; [kbmMW_Auth('role:[READER,READWRITER], grant:true')] [kbmMW_Rest('method:get, path:contacts, anonymousResult:true')] function GetContacts:TObjectList; [kbmMW_Auth('role:[READWRITER], grant:true')] [kbmMW_Rest('method:put, path:addcontact')] function AddContact([kbmMW_Rest('value:"{$name}"')] const AName:string; [kbmMW_Rest('value:"{$address}"')] const AAddress:string; [kbmMW_Rest('value:"{$zipcode}"')] const AZipCode:string; [kbmMW_Rest('value:"{$city}"')] const ACity:string):string; overload; [kbmMW_Auth('role:[READWRITER], grant:true')] [kbmMW_Rest('method:get, path:"addcontact/{name}"')] function AddContact([kbmMW_Rest('value:"{name}"')] const AName:string):string; overload; [kbmMW_Auth('role:[READWRITER], grant:true')] [kbmMW_Rest('method:delete, path:"contact/{id}"')] function DeleteContact([kbmMW_Rest('value:"{id}"')] const AID:string):boolean; end;
幕后发生的事情是kbmMW自动为这些函数定义资源名称:MyREST..AddContect,MyREST..GetContacts等。
额外注意一点:如果我们为定义的Service定义了版本,当我们建立他时,在中间应放一个“."。
如你所见,资源名称只是一个字符串,您可以自己定义所需的所有资源,但如果你用smart service,可以用上面的格式自动定义资源名称。
kbmMW还将自动要求授权管理员在任何客户端的呼叫时验证是否允许使用资源。
您可以选择通过手动调用授权管理器来进行更细粒度的授权,以验证调用,如下所示:
var res:TkbmMWAuthorizationStatus; sMessage:string; begin ... res:=AuthorizationManager1.IsAuthorized(logintoken, 'YOURRESOURCENAME', sMessage);
res返回mwasAuthorized,mwasNotAuthorized或mwasConstrained 。
mwasConstained意味着授权可以在不同的情况下(不同的时间或类似的时间)给出。返回的sMessage可以更详细地解释访问被拒绝的原因。
在kbmMW智能服务(smart service)中,您可以获取登录令牌(logintoken)作为方法的参数,如下所示:
[kbmMW_Auth('role:[READER], grant:true')] [kbmMW_Rest('method:get, path:"someCall"')] function SomeCall([kbmMW_Arg(mwatToken)] const AToken:string):boolean;
调用SomeCall方法时,其AToken参数包含logintoken。
如果您不希望令牌成为方法调用的参数列表的一部分,您也可以在方法中访问ClientIdentity.Token属性。
现在,您的REST服务器受SSL保护,并通过登录调用其功能。
授权管理器中有许多功能,我在这里没有解释,但可以访问我们的网站http://www.components4developers.com,查找kbmMW白皮书相关部分。
原文地址:https://components4developers.blog/2017/05/26/rest-easy-with-kbmmw-4-access-management/
作者相关的博文: