#访问控制

0 关注者 · 6 帖子

此标签汇总了所有与角色(拥有访问 SQL 表的一个或多个权限的容器)、用户(连接到数据库时的登录身份)和授权(指定对资源的访问权限/特权的功能)相关的帖子。在“文档”中阅读有关角色、用户和授权的更多信息。

文章 sun yao · 十月 12, 2022 10m read

概述

现有Ensemble平台BS(服务)、BP(流程)、BO(操作)需对平台及开发语言有一定的了解才能实现,为简化用户操作,现对现有平台进行二次封装,通过API接口的形式进行前后端分离,通过前端界面操作实现BS(对外提供的服务)、BP、BO(逻辑处理或调用外部的服务)自动生成(通过%Dictionary实现),具体实现如下。

一、开发技术和工具

版本:Ensemble 2017.2.1

二、涉及公用类

2.1 %Dictionary.ClassDefinition(自定义类)

• property Super as %CacheString; Specifies one or more superclasses for the class. 定义一个或多个父类,继承父类

• property** ProcedureBlock** as %Boolean [ InitialExpression = 0 ]; Specifies that the class uses procedure block for method code. 设置类是否允许使用程序块,程序块强制实施变量作用域:方法无法看到由其调用方定义的变量,程序块中的任何变量都会自动成为私有变量

• relationship Parameters as %Dictionary.ParameterDefinition [ Inverse = parent,Cardinality = children ]; Parameter. 定义类参数,如全局变量、适配器等相关定义

• relationship Methods as %Dictionary.MethodDefinition [ Inverse = parent,Cardinality = children ]; Method. 定义类方法

参考链接:http://localhost:57772/csp/documatic/%25CSP.Documatic.cls?APP=1&LIBRARY=%25SYS&CLASSNAME=%25Dictionary.ClassDefinition

2.2 %Dictionary.ParameterDefinition(自定义类参数)

• property Name as %Dictionary.CacheIdentifier [ Required ]; The name of the parameter. 定义参数名

• property Default as %CacheString [ SqlFieldName = _Default ]; Specifies a default value for the parameter assuming the Expression keyword is blank. 定义参数默认值,不设置则为空

• property Description as %CacheString; Specifies a description of the parameter. 定义参数描述

参考链接:http://localhost:57772/csp/documatic/%25CSP.Documatic.cls?APP=1&LIBRARY=%25SYS&CLASSNAME=%25Dictionary.ParameterDefinition

###2.3 %Dictionary.MethodDefinition(自定义类方法) • property Name as %Dictionary.CacheIdentifier [ Required ]; The name of the method. 定义方法名

• property ClassMethod as %Boolean [ InitialExpression = 0 ]; Specifies that the method is a class method. Instance methods can only be invoked via an instantiated object while class methods can be directly invoked without an object instance. 指定该方法是类方法。实例方法只能通过实例化的对象调用,而类方法可以在没有对象实例的情况下直接调用。

• property FormalSpec as %CacheString; Specifies the list of arguments. Each argument is of the format [&|*][:][=] where & means pass-by-reference and * means output-only. 定义方法入参,每个入参格式为“参数名:参数类型=默认值”,如:code:%String=””

• property ReturnType as %Dictionary.CacheClassname; Specifies the data type of the value returned by a call to the method. Setting ReturnType to an empty string specifies that there is no return value. 定义方法返回值,设置为空则无返回值

• property WebMethod as %Boolean [ InitialExpression = 0 ]; Specifies that a method can be invoked as a web method using the SOAP protocol. 设置方法是否为web方法,适用于SOAP协议

• property Implementation as %Stream.TmpCharacter; The code that is executed when the method is invoked. In the case of an expression method, this is an expression. In the case of a call method, this is the name of a Cache routine to call. 调用方法时执行的代码。对于表达式方法,这是一个表达式。对于调用方法,这是要调用的缓存例程的名称

参考链接:http://localhost:57772/csp/documatic/%25CSP.Documatic.cls?APP=1&LIBRARY=%25SYS&CLASSNAME=%25Dictionary.MethodDefinition

###2.4 Ens.Config.Production • property Items as list of Ens.Config.Item(XMLNAME="Item",XMLPROJECTION="ELEMENT"); 定义Production下的BS、BP、BO,根据父类确认属于哪一类

• method SaveToClass(pItem As Ens.Config.Item = $$$NULLOREF) as %Status This method saves the production into the XData of the corresponding class

参考链接: http://localhost:57772/csp/documatic/%25CSP.Documatic.cls?APP=1&LIBRARY=ENSLIB&CLASSNAME=Ens.Config.Production

###2.5 Ens.Config.Item(BS服务、BP流程、BO操作) • property PoolSize as %Integer(MINVAL=0,XMLPROJECTION="ATTRIBUTE"); Number of jobs to start for this config item. Default value: 0 for Business Processes (i.e. use shared Actor Pool) 1 for FIFO message router Business Processes (i.e. use a dedicated job) 1 for Business Operations 0 for adapterless Business Services 1 for others For TCP based Services with JobPerConnection=1, this value is used to limit the number of connection jobs if its value is greater than 1. A value of 0 or 1 places no limit on the number of connection jobs. 设置缓冲池大小

• property Name as %String(MAXLEN=128,XMLPROJECTION="ATTRIBUTE") [ Required ]; The name of this config item. Default is the class name. 设置BS、BP、BO名称

• property ClassName as %String(MAXLEN=128,XMLPROJECTION="ATTRIBUTE") [ Required ]; Class name of this config item. 设置BS、BP、BO类名称

• property Category as %String(MAXLEN=2500,XMLPROJECTION="ATTRIBUTE"); Optional list of categories this item belongs to, comma-separated. This is only used for display purposes and does not affect the behavior of this item. 设置类别 • property Comment as %String(MAXLEN=512,XMLPROJECTION="ATTRIBUTE"); Optional comment text for this component. 设置注释

• property Enabled as %Boolean(XMLPROJECTION="ATTRIBUTE") [ InitialExpression = 1 ]; Whether this config item is enabled or not. 设置启用停用标志

参考链接:http://localhost:57772/csp/documatic/%25CSP.Documatic.cls?PAGE=CLASS&LIBRARY=ENSLIB&CLASSNAME=Ens.Config.Item ##三、实现方法 ###3.1 创建BS模板类 创建模板类,后续类生成方法体通过模板类获取

/// BS的SOAP模板
Class HIP.Platform.Template.BSSOAPTemplate Extends EnsLib.SOAP.Service
{

Parameter ADAPTER;

Parameter NAMESPACE = "http://tempuri.org";

Parameter SERVICENAME = "BSSOAPTemplate";

Method TemplateFun(code As %String, data As %GlobalCharacterStream) As %GlobalCharacterStream [ WebMethod ]
{
	set OutStream=##class(%GlobalCharacterStream).%New()
	try{
		s ..%ConfigName = $classname($this)
		set sourceCode=$p($classname($this),".",4) //PUB000X
		set methodCode=##safeexpression(""""_$get(%methodname)_"""") //SendDataFromHis
		
		s messageCode = $p(code,"^",1)
		s requestType= $select($p(code,"^",2)="REST":"REST", 1:"SOAP")
		set proc = ##class(%SYS.ProcessQuery).%OpenId($j) //当前进程 获取调用服务客户端的IP地址
		
		set sc = ##class(HIP.Service.PublishService).GetAllowedIP(sourceCode)
		if +sc=1 {
			s allowedIP = $p(sc,"^",2)
			if allowedIP '[ proc.ClientIPAddress {
				SET oref=##class(%Exception.General).%New("<401>","无权限",,"您的IP地址不允许访问,请联系管理员") 
         		THROW oref
			}
		}else{
			return sc
		}
		s request = ##class(HIP.Platform.Message.Request).%New()
		s request.sourceCode=sourceCode		//PUB0001
		s request.requestType=requestType	//REST SOAP
		s request.inputFlag="0"				//-1表示失败,0表示未处理,1表示成功
		s request.inputStream = data		//JSON流,或者XML流
		s request.messageCode=messageCode	//BOE0001
		
		Set tSC=..SendRequestSync("HIP.Platform.BP.ProcessCode",request,.pOutput)
		If $$$ISERR(tSC) Do ..ReturnMethodStatusFault(tSC)
		
		d OutStream.CopyFrom(pOutput.outStream)
		return OutStream
	}catch err {
		set OutStream=##class(%GlobalCharacterStream).%New()
		do OutStream.Write(err.DisplayString())
		return OutStream
	}
}

Storage Default
{
<Data name="BSSOAPTemplateDefaultData">
<Value name="1">
<Value>%%CLASSNAME</Value>
</Value>
</Data>
<DataLocation>^HIP.PlatforE240.BSSOAPTemplateD</DataLocation>
<DefaultData>BSSOAPTemplateDefaultData</DefaultData>
<IdLocation>^HIP.PlatforE240.BSSOAPTemplateD</IdLocation>
<IndexLocation>^HIP.PlatforE240.BSSOAPTemplateI</IndexLocation>
<StreamLocation>^HIP.PlatforE240.BSSOAPTemplateS</StreamLocation>
<Type>%Library.CacheStorage</Type>
}

}

###3.2 自动生成BS,并添加至Production中 通过模板类自动生成WebService方法,并添加到Production的BS中

/// 创建BS服务 PUB00XX服务,提供给第三方调用
/// d ##class(HIP.Util.SOAP).BSCreateSOAPInfo("PUB0001","提供给HIS访问平台")
ClassMethod BSCreateSOAPInfo(Code As %String, Desc As %String) As %Status
{
	///HIP.Platform.BS.PUB0001
	s src = "HIP.Platform.BS."_Code_".PublishWebService"
	s isExist = 0
	try {
		
		set isExist=##class(%Dictionary.ClassDefinition).%ExistsId(src)
		if isExist=1 { //类已存在则更新,先删除再插入
			set classObj = ##class(%Dictionary.ClassDefinition).%OpenId(src)
			d classObj.Parameters.Clear()
			d classObj.Properties.Clear()
			d classObj.Indices.Clear()
			d classObj.ForeignKeys.Clear()
			d classObj.Methods.Clear()
		}else { //类不存在则新建
			set classObj = ##class(%Dictionary.ClassDefinition).%New(src)
		}
		//设置父类
		s classObj.Super="EnsLib.SOAP.Service"
		//设置允许使用程序块,则可动态定义变量
		s classObj.ProcedureBlock=1

		///Parameter的值
		//设置适配器
		set ParDef = ##class(%Dictionary.ParameterDefinition).%New()
		set ParDef.Name="ADAPTER"
		d classObj.Parameters.Insert(ParDef)
		set ParDef = ##class(%Dictionary.ParameterDefinition).%New()
		//设置服务名
		set ParDef.Name="SERVICENAME"
		set ParDef.Default=Code
		set ParDef.Description=Desc
		d classObj.Parameters.Insert(ParDef)
		//设置命名空间
		set ParDef = ##class(%Dictionary.ParameterDefinition).%New()
		set ParDef.Name="NAMESPACE"
		set ParDef.Default="www.boe.com"
		d classObj.Parameters.Insert(ParDef)
		
		///函数模板代码,通过模板类获取
		s methodTemplate = ##class(%Dictionary.MethodDefinition).%OpenId("HIP.Platform.Template.BSSOAPTemplate||TemplateFun")
		Set methodObj=##class(%Dictionary.MethodDefinition).%OpenId(src_"||SendData")
		if methodObj="" Set methodObj=##class(%Dictionary.MethodDefinition).%New(src_".SendData")
		//设置方法名
		set methodObj.Name="SendData"
		set methodObj.ClassMethod=0
		//set methodObj.FormalSpec="code:%String,data:%GlobalCharacterStream,*pOutput:HIP.Platform.Message.Response"
		//设置方法入参
		set methodObj.FormalSpec="code:%String,data:%GlobalCharacterStream"
		//设置方法返回值
		set methodObj.ReturnType="%GlobalCharacterStream"
		//设置方法为WebService方法
		set methodObj.WebMethod=1
		//设置方法具体实现代码,通过模板类获取
		set methodObj.Implementation=methodTemplate.Implementation
		d classObj.Methods.Insert(methodObj)
		
		set sc=classObj.%Save()
		if $$$ISERR(sc) {
			return $system.Status.GetErrorText(sc)
		}else{
			d $system.OBJ.Compile(src,"ck/displaylog=0")
		}
		if isExist=0 {
			//存储到production中
			s prodObj = ##class(Ens.Config.Production).%OpenId("HIP.Platform.Production")
			if $IsObject($G(prodObj)){
				Set item = ##class(Ens.Config.Item).%New()
				Set item.PoolSize = 1 
				Set item.Name = src
				Set item.ClassName = src
				Set:item.Name="" item.Name = item.ClassName
				Set item.Category = ""
				Set item.Comment = Desc
				Set item.Enabled = 1
				Set tSC = prodObj.Items.Insert(item)
				
				If $$$ISOK(tSC) {
					// save production (and item)
					Set tSC = prodObj.%Save()
					set ^TempSy("tSC")=tSC
					If ($$$ISOK(tSC)) {
						// update production class
						Set tSC = prodObj.SaveToClass()
					}
					return tSC
				}
				If $$$ISERR(tSC) return $system.Status.GetErrorText(tSC)
			}
		}
		return $$$OK
	} catch(ex) {
		return ex.DisplayString()
	}
}

###四、 结果展示 运行 d ##class(HIP.Util.SOAP).BSCreateSOAPInfo("PUB0001","提供给HIS访问平台") 后,Studio中自动生成HIP.Platform.BS.PUB0001.PublishWebService.cls 类 如下: image 打开Portal管理界面,Production配置,可看到该服务已添加至Production中,如下: image 可直接通过soapUI调用,地址 http://localhost:57772/csp/hip/HIP.Platform.BS.PUB0001.PublishWebService.CLS?WSDL=1image InterSystems消息查看 imageimageimage ###五、 结论与猜想 同理,BO也可通过该方法实现自动生成,另可通过建立REST服务或WebService服务的方式通过前端调用该方法实现前端自动生成BS、BP、BO,以简化用户操作,但该方法存在问题点,如BP都为公用单个BP,消息并发量大时可能导致BP堵塞问题,可能实现的解决方法为前端先单独调用接口创建BP,后生成BS,再通过配置实现BS到BP的关联,大家感兴趣可自行尝试,以上,谢谢!

10
6 789
文章 Michael Lei · 六月 26, 2022 3m read

在这篇文章中,我将解释如何通过使用CSP Web应用程序以及启用/禁用和认证/取消认证任何Web应用程序的代码来进行认证、授权和审计。

应用层
 

0
0 181
文章 Nicky Zhu · 二月 4, 2021 7m read

上一篇: IRIS中的权限管理

在上一篇文章中,我们介绍了IRIS中的权限控制体系。在本文中我们将以一个常见的实施需求为例介绍如何使用IRIS的权限配置出一个只能使用SQL的用户。

需求的分解

和所有用户需求一样,当用户提出一个需求时,除其语义显式的含义之外,还需分析其是否具有没有明确说明的含义。 对于一个只能使用SQL的用户这样一个需求,即应当结合平台的特征分解成为功能需求: 具有一个合法,可通过用户名和密码使用IRIS的用户 该用户的数据库权限

  • 确认项:可以使用SQL访问所有数据库还是某几个特定的数据库? 该用户的SQL权限
  • 确认项:对于特定的数据库,是否可以执行所有的DDL?
  • 确认项:对于特定的数据库,是否对每一张表都可以执行Select、Update等所有的DML 该用户的程序权限
  • 确认项:用户是否可以通过Portal登录并管理IRIS?

如上所示,一个只能使用SQL的用户,这样一个看似简单的需求,如果需要与用户获得一致的理解并付诸实施,则需要将其分解,对于没有在用户需求中明确约定的部分,应作为待确认项与用户确认。 另外,需要注意的是,IRIS作为一个数据平台,除了提供底层的数据库之外,也提供了Portal等Web应用程序便于使用Sharding、HA、Interoperability等平台功能,通过平台对外提供的Webservice、REST等web接口也受Web应用程序控制。因此,当需要设计权限体系时,不但需要考虑用户使用的数据库相关的权限,也需要考虑是否需要控制Web应用程序的权限。

我们假设需要提供一个严格意义上的只能对某个库的某个Schema下的所有表具有只读权限的用户,即: 具有一个合法,可通过用户名和密码使用IRIS的用户 该用户的数据库权限

  • 该用户只能使用DemoSpace命名空间下的数据库 该用户的SQL权限
  • 该用户不能执行任何DDL
  • 该用户只能对DemoSchema下的所有表执行Select语句 该用户的程序权限
  • 该用户不能登录Portal,不能执行Portal中提供的任何管理功能

对数据库的配置

在IRIS中创建数据库时,默认的行为是引用%DB_%DEFAULT这个资源,并引用%DB_%DEFAULT角色(注意,平台中有一个叫做%DB_%DEFAULT的资源,同时还有一个叫做%DB_%DEFAULT的角色。%DB_%DEFAULT角色通过%DB_%DEFAULT资源获得默认的数据库访问权限)。如果直接使用%DB_%DEFAULT角色或%DB_%DEFAULT资源,都有可能影响到之前以默认配置创建的数据库,因此,在需要细粒度控制访问权限时,往往需要自定义资源和角色实现。

创建资源%DB_DemoDB_Res {#3.1}

资源可在创建数据库的同时创建,也可以在使用默认资源创建数据库之后,再给数据库指派其他资源。本例中,我们在创建命名空间DEMOSPACE的同时创建数据库DEMODB并创建资源。 image

创建角色DemoDB_Read_Role {#3.2}

image 在创建角色之后,即可为其分配资源。根据资源类型的不同,对资源的操作可以有读、写和使用三种权限。对于数据库引用的资源,是读权限和写权限。在本例中,我们需要创建的是只读用户,因此,资源权限分配读权限即可,不用赋写权限。 image

创建用户DemoUser并分配角色

通过Portal创建用户之后,即可给用户分配角色。在本例中,需要给这个用户分配之前创建的DemoDB_Read_Role角色。 image 在经过上述配置之后,用户DemoUser即已拥有对命名空间DEMOSPACE中数据库的访问权限。 image 此时,用户对数据库拥有读权限,但并没有对表执行查询或建表的权限,如果尝试create table,则会得到如下的权限错误信息: image 为继续实验,我们通过Portal执行这SQL语句先创建DemoSchema.Persons这张表 image

为角色DemoDB_Read_Role分配SQL权限

由于用户具有的权限不足,无法执行SQL操作,因此我们需要对该用户的角色赋予对应的权限(或直接给用户赋权,但平台用户较多时,考虑到用户管理的成本,并不推荐这样做)。可以采用如下手段进行SQL的授权

通过Portal授权

在Portal的用户管理和角色管理功能中,均可指定要授予的SQL权限。 SQL特权栏用于对DDL进行数据库级的授权,例如对DEMOSPACE命名空间下的数据库分配建表、修改表、建视图等DDL操作。在本例中,用户不具有这些权限,因此不对该用户对应的角色授予这些权限。 image 在SQL表,SQL视图和SQL过程栏中,则是分别对表、视图和存储过程授予查询、执行等权限。在本例中,用户需要对DemoSchema这个Schema下的表拥有select查询权限,即可通过对SQL表授权进行 image 授权后该角色的SQL表权限如下 image 此时通过SQL工具已可执行查询 image 使用Portal授权时是针对单个的表、视图或存储过程进行。在上例中,我们单独对表DemoSchema.Persons进行了授权,如果我们再建立一张DemoSchema.Employee表,当前的角色和用户并不能自动获得读取其数据的权限。

通过SQL授权

超级管理员或拥有SQL授权权限的用户可以通过SQL的GRANT语句对数据库对象(包括库,函数,表,视图和存储过程等)进行授权,SQL GRANT语句的语法和使用详见GRANT,此处不再赘述。 在上面的例子中我们建立了表DemoSchema.Persons,建表的同时建立的Schema DemoSchema。假如现在我们希望对授权进行简化,使角色DemoDB_Read_Role可以直接获得Schema下所有表的读权限,则可以用如下的SQL:

GRANT SELECT ON SCHEMA DemoSchema to DemoDB_Read_Role

执行成功后再查看DemoDB_Read_Role的SQL权限,会发现: image 即这个角色已经拥有了Schema级的授权,因此,对整个Schema下的所有表都拥有权限。之后在Schema中如果建立了新的表,则这个角色会自动拥有这些表的读权限。

限制用户登录和使用Portal

上例配置的用户可以用于登录IRIS的Portal,但由于没有任何系统功能的权限,不能执行操作。 image IRIS的Web应用程序在创建时默认并不需要额外的资源去访问,这意味着所有合法用户都能登入这个Web应用,但由于支撑应用的后台程序和数据是受到资源的保护的,能登入的用户不一定具有运行程序、访问数据的权限,正如我们建立的用户DemoUser可以登录Portal,但没有功能菜单可用。 如果需要进一步限制用户的行为,禁止其登录,则还需要对应用权限进行控制。 如我们在上一篇文章:IRIS中的权限管理 中所述,Portal是平台提供的Web应用程序,是通过Web应用的权限控制可访问性,因此,需要修改应用的资源要求。 通过菜单: 系统管理 > 安全 > 应用程序 > Web应用程序 可以访问当前系统提供的Web应用列表,其中的/csp/sys即为系统管理门户Portal image 其中必要的资源一栏即为该应用的资源需求,默认为空,即访问该应用不需要特定资源,只要是合法用户即可。我们可以为该应用指定所需资源,例如%Development,即只有具有%Development资源的角色及其对应的用户才能够访问该程序。保存设置后,再尝试以用户DemoUser登录Portal,结果是 image 除非我们为DemoUser引用的角色DemoDB_Read_Role分配资源%Development,该用户都不能登录。 当然,超级管理员由于拥有所有权限,不受这个设置的影响。

总结

通过上述实验,我们创建了一个用户,只能使用SQL连入并查询指定Schema下的表。希望通过这个实验,大家能够掌握IRIS权限管理的基本元素:

  • 用户,角色,权限,资源和许可构成的权限控制体系
  • 数据库、SQL和应用程序都是权限管理的对象

大家在实际项目中可以根据最终用户的实际需要,灵活应用这些概念,构建满足需求的权限配置。

上一篇: IRIS中的权限管理

3
1 448
文章 Nicky Zhu · 二月 3, 2021 6m read

下一篇: 案例: 建立只能使用SQL的用户

IRIS通过认证(Authentication)与授权(Authorization)两项机制控制外部用户对系统及应用、数据资源的可访问性。因此。如需要进行权限控制,则需要通过配置认证和授权进行。

IRIS中的认证 {#2}

认证可以验证任何试图连接到InterSystems IRIS®的用户的身份。一旦通过认证,用户就与IRIS建立了通信,从而可以使用其数据和工具。有许多不同的方法可以验证用户的身份;每种方法都称为验证机制。IRIS 通常被配置为只使用其中一种方式。 支持的认证方式

  • 实例认证:通过用户名/密码对登录平台,即密码认证
  • LDAP:通过第三方LDAP服务器(如Windows Active Directory )完成认证
  • 操作系统认证:建立操作系统用户-平台用户映射,使用操作系统用户登录平台
  • Kerberos:使用Kerberos协议进行认证
  • 代理认证:使用自定义的代码实现认证过程

系统服务与认证 {#2.1}

在安装时,IRIS会启动一系列系统级的服务用与控制与外部用户或系统的交互,这些服务都绑定了默认的认证机制 image 图中红框标出的即为系统安装后会自动启用并需经认证才可使用的系统服务,认证手段可配置。 例如,如果变更%Service_Console的身份验证方法,取消密码方法,用户就不能通过输入用户名密码登入Terminal。 通过Portal的菜单 系统管理 > 安全 > 服务 可访问该设置。

账户控制参数 {#2.2}

通过系统管理 > 安全 > 系统安全 > 系统范围的安全参数中的选项可对于用户名/密码认证手段的行为进行更多的约束。 image

  • 非活动限制 - 指定用户账户不活跃的最大天数,它被定义为成功登录之间的时间。当达到此限制时,该帐户将被禁用。值为0(0)表示对登录之间的天数没有限制。[对于最低安全级别的安装,默认为0,对于正常和锁定的安装,默认为90]。
  • 无效登录限制 (0-64) - 指定连续不成功的登录尝试的最大次数。在达到此限制后,要么禁用账户,要么对每次尝试进行递增的时间延迟;行动取决于如果达到登录限制字段则禁用账户的值。值为0(零)表示对无效登录的次数没有限制。[默认为5]
  • 如果达到登录限制,则禁用账户 - 如果选中,则指定达到无效登录次数(在前一字段中指定)将导致用户账户被禁用。
  • 密码有效期天数(0-99999) - 指定密码过期的频率以及用户更改密码的频率(天数)。当初始设置时,指定密码过期的天数。0(0)表示密码永远不会过期。不会影响已设置了下次登录时更改密码字段的用户。[默认为0]

需要特别注意的是,密码有效性、过期和禁用账户等设置会影响IRIS实例的所有账户,包括IRIS超级管理员账户。如触发了控制策略,则在更新这些帐户的信息之前,可能无法进行各种操作,这可能导致意外的结果。如超级管理员账户被锁定,则需要通过紧急模式启动实例再进行修改。

对于系统可用的认证手段的配置和其他可用的安全配置,请参见Security Administration Guide

IRIS中的授权 {#3}

授权模型 {#3.1}

InterSystems公司的授权模式采用基于角色的访问控制。

  • Users – 用户
  • Roles – 角色
  • Privileges – 权限
  • Resources – 资源 | Permissions – 许可

在这种模式下,用户拥有与分配给各自用户身份的角色相关的权限。 image

  • 一个角色是一个命名的特权集合
  • 一个用户可以拥有一个以上的角色
  • 权限分配给角色,角色分配给用户

其中,Roles就是权限的集合,而权限提供对资源的特定类型的访问的许可。

  • 可控资源: 数据库,服务,应用(包括Web应用 )和其他
  • 可选用的许可: Read, Write or Use,其中执行代码需要数据库的读权限

资源的定义 {#3.2}

资源是一项相对抽象的概念,用来指代IRIS中的数据库,服务,应用等可被访问的对象。例如,对于数据库,在建立时默认采用%DB_%DEFAULT指代,也可自定义资源(数据库资源必须以%DB_开头): image

对于Web应用,默认不需要通过资源控制,即所有可登录用户都可访问(但该用户进程不一定能访问到数据,还需参照是否具有对数据库的访问权限)。如通过分配资源进行控制,则登录用户还需具有资源才能访问这个Web应用: image

因此,一项权限实际上是指对某个资源的一些特定操作的集合。 例如,对于数据库UserDB具有读写操作许可的权限A,对于Web应用/csp/sys具有使用操作许可的权限B。如果我们将这两项权限都赋给角色RoleA,那么这个角色就同时拥有A权限和B权限,从而能够访问数据库UserDB和访问Web应用/csp/sys。

SQL授权 {#3.3}

除了对数据库进行授权外,IRIS作为一个数据平台,需要对外提供数据访问。因此,IRIS也提供了SQL授权对用户可执行的SQL进行细粒度的权限控制。 SQL的授权可以分配给角色或用户。但通常在企业环境中,用户数量会很多,仍然需要对SQL用户进行分组,根据分组规划角色,通过角色进行授权的控制,才能有效降低维护授权所需的工作量。 SQL的授权针对SQL类型,可分为库、表级授权。 对于Create table、drop view、truncate table这一类的DDL,使用库级授权,即用户可在特定的库中执行建表、删除视图等经过授权的操作。如下: image

对于Select、update等DML,则使用表级授权,使用户能够通过DML访问特定的表中的数据。如下: image

除通过Portal操作之外,对于SQL的授权,还可使用IRIS SQL中的额GRANT语句,例如:

GRANT * ON Schema Test TO TestRole

这个SQL即可以将当前操作数据库下Schema Test中的所有表的所有权限都赋给TestRole这个角色。 关于GRANT语句的用法,可参见GRANT指令

以上即为IRIS中进行权限控制所需掌握的概念和内容,在后续文章中,我们会结合实例向大家介绍其使用。

下一篇: 案例: 建立只能使用SQL的用户

推荐阅读

Security Administration Guide - https://docs.intersystems.com/irisforhealthlatest/csp/docbook/DocBook.UI.Page.cls?KEY=GCAS

0
0 651
文章 Qiao Peng · 一月 14, 2021 12m read

本文以及后面两篇该系列文章,是为需要在其基于 InterSystems 产品的应用程序中使用 OAuth 2.0 框架(下文简称为 OAUTH)的开发人员或系统管理员提供的指南。

作者:InterSystems 高级销售工程师 Daniel Kutac

发布后校正和更改历史记录

  • 2016 年 8 月 3 日 - 修正了 Google 客户端配置屏幕截图,更新了 Google API 屏幕截图以反映新版本的页面
  • 2016 年 8 月 28 日 - 更改了 JSON 相关代码,反映了对 Cache 2016.2 JSON 支持的更改
  • 2017 年 5 月 3 日 - 更新了文本和屏幕,以反映 Cache 2017.1 的新 UI 和功能
  • 2018 年 2 月 19 日 - 将 Caché 更改为 InterSystems IRIS 以反映最新的发展。 但是请记住,尽管产品名称发生更改,但文章涵盖所有的 InterSystems 产品——InterSystems IRIS 数据平台、Ensemble 和 Caché。
  • 2020 年 8 月 17 日 - 大面积更改,软件方面更改更大。 要获取 Google 的更新版 Oauth2 的网址,请咨询 Micholai Mitchko。

第 1 部分 客户端

简介

有关开放式授权框架 InterSystems 实现的相关内容,我们分 3 部分讲述,这是第 1 部分。

在第 1 部分中,我们对该主题进行了简短介绍,并提供了一个 InterSystems IRIS 应用程序担当授权服务器客户端并请求一些受保护资源的简单方案。

第 2 部分将讲述一个复杂一些的方案,在该方案中 InterSystems IRIS 本身通过 OpenID Connect 担当授权服务器和身份验证服务器。

本系列的最后一部分将描述 OAUTH 框架类的各个部分,它们由 InterSystems IRIS 实现。

什么是开放授权框架 [1]

许多人已经听说过有关开放授权框架及其用途的信息。 因此这里只做简单介绍,以备未听说过的人参考。

开放授权框架 (OAUTH) 当前为 2.0 版,其是一种协议,允许基于 Web 的主应用程序通过在客户端(应用程序请求数据)和资源所有者(应用程序保存请求的数据)之间建立间接信任来以安全的方式交换信息。 信任本身由客户端和资源服务器都认可并信任的主体提供。 该主体称为授权服务器。

简单举例如下:

假设 Jenny(使用 OAUTH 术语,就是资源所有者)在开展 JennyCorp 公司的一个工作项目。 她为一个潜在的大型业务创建了项目计划,并邀请 JohnInc 公司的业务伙伴 John(客户端用户)审阅此文档。 不过,她并不愿意让 John 访问自己公司的 VPN,因此她将文档放在 Google 云端硬盘(资源服务器)或其他类似的云存储中。 她这样做,已经在她和 Google(授权服务器)之间建立了信任。 她标记了要与 John 共享的文档(John 已经使用 Google 云端硬盘服务,Jenny 知道他的电子邮件)。

当 John 想要阅读该文档时,他进行了 Google 帐户身份验证,然后通过移动设备(平板电脑、笔记本电脑等)启动文档编辑器(客户端服务器)并加载 Jenny 的项目文件。

这听起来很简单,但是两个人与 Google 之间有很多通信。 所有交流均遵循 OAuth 2.0 规范,因此 John 的客户端(阅读器应用程序)必须首先向 Google 进行身份验证(OAUTH 不涵盖此步骤),然后 John 申请获取 Google 对提供表格的同意,经过授权后,Google 就会发出一个访问令牌,授权阅读器应用程序访问文档。 阅读器应用程序使用该访问令牌向 Google 云端硬盘服务发出请求,以检索 Jenny 的文件。

下图说明了各方之间的通信

请注意:虽然所有的 OAUTH 2.0 通信都使用 HTTP 请求,但服务器不必非得是 Web 应用程序。

让我们通过 InterSystems IRIS 来说明这一简单方案。

简单 Google 云端硬盘演示

在本演示中,我们将创建一个基于 CSP 的小型应用程序,该应用程序将使用我们自己的帐户(以及作为奖励的日历列表)来请求存储在 Google 云端硬盘服务中的资源(文件列表)。

基本要求

开始应用程序编码之前,我们需要准备环境。 这包括启用 SSL 的 Web 服务器和 Google 配置文件。

Web 服务器配置

如上所述,我们需要使用 SSL 与授权服务器进行通信,因为默认情况下 OAuth 2.0 要求如此。 我们需要确保数据安全,对吧?

解释如何配置 Web 服务器来支持 SSL 的内容超出了本文讨论的范围,因此,请以您喜欢的方式参阅相应 Web 服务器的用户手册。 为了您的好奇心(我们稍后可能会显示一些屏幕截图),在此特定示例中,我们将使用 Microsoft IIS 服务器。

Google 配置

为了向 Google 注册,我们需要使用 Google API Manager- https://console.developers.google.com/apis/library?project=globalsummit2016demo

为了进行演示,我们创建了一个帐户 GlobalSummit2016Demo。 确保我们已启用 Drive API

现在,该定义凭据了

请注意以下事项:

_Authorized JavaScript – _我们仅允许本地生成的脚本(相对于调用页面)

_Authorized redirect URIs – 从理论上讲,我们可以将客户端应用程序重定向到任何站点,但是当使用 InterSystems IRIS OAUTH 实现时,我们必须重定向到** https://localhost/csp/sys/oauth2/OAuth2.Response.cls**。您可以定义多个授权的重定向 URI,如屏幕截图所示,但是对于本演示,我们只需要两者中的第二个条目。

最后,我们需要将 InterSystems IRIS 配置为 Google 授权服务器的客户端

Caché /IRIS配置

InterSystems IRIS OAUTH2 客户端配置需要两步。 首先,我们需要创建服务器配置。

在 SMP 中,导航至系统管理 > 安全性 > OAuth 2.0 > 客户端配置

点击创建服务器配置按钮,填写表格并保存。

输入到表格的所有信息可以在 Google 开发者控制台网站上找到。 请注意,InterSystems IRIS 支持自动 Open ID 发现。 但是,由于我们没有使用它,因此我们手动输入所有信息

现在,点击新创建的 Issuer Endpoint
旁边的“客户端配置”链接。并点击创建客户端配置按钮。

将“客户端信息”和“JWT 设置”选项卡保留为空(默认值),并填写客户端凭据。

请注意:我们正在创建机密客户端(这比公共客户端更安全,这意味着客户端秘密永远不会离开客户端服务器应用程序(永远不会传输到浏览器)

此外,请确保选中**“使用 SSL/TLS**”,并提供主机名(本地主机,因为我们将本地重定向到客户端应用程序),最后提供端口和前缀(当同一台机器上有多个 InterSystems IRIS 实例时,这非常有用)。 根据输入的信息,会计算客户端重定向 URL 并显示在上一行中。

在上面的屏幕截图中,我们提供了一个名为 GOOGLE 的 SSL 配置。 该名称本身实际上仅用于帮助您确定此特定通信通道使用的可能是众多 SSL 配置中的哪个。 Caché 使用 SSL/TLS 配置存储所有必要的信息,以建立与服务器(在本例中,为 Google OAuth 2.0 URI)的安全流量。

有关详细信息,请参阅文档 。

Supply Client ID 和 Client Secret 值从 Google 凭据定义表中获得(使用手动配置时)。

现在,我们完成了所有的配置步骤,可以开始编写 CSP 应用程序代码。

客户端应用程序

客户端应用程序是基于 Web 的简单 CSP 应用程序。 因此,它包含由 Web 服务器定义和执行的服务器端源代码,以及由 Web 浏览器向用户公开的用户界面。 下文提供的示例代码期望客户端应用程序在 GOOGLE 名称空间中运行。 请将路径 /csp/google/ 修改为您的命名空间。

客户端服务器

客户端服务器是一个简单的两页应用程序。 在该应用程序内,我们将:

·        将 URL 重定向到 Google 授权服务器

·        执行向 Google Drive API 和 Google Calendar API 的请求并显示结果

第 1 页

这是应用程序的一页,我们决定在此处调用 Google 的资源。

以下是此页面上简单但功能齐全的代码。

<span style="margin: 0px; line-height: 115%; font-size: 9pt;"><font color="#000000" face="Arial">Class Web.OAUTH2.Google1N Extends %CSP.Page</font></span>
<span style="margin: 0px; line-height: 115%; font-size: 9pt;"><font color="#000000" face="Arial">{</font></span>
<span style="margin: 0px; line-height: 115%; font-size: 9pt;"><font color="#000000" face="Arial">Parameter OAUTH2CLIENTREDIRECTURI = "https://localhost/csp/google/Web.OAUTH2.Google2N.cls";</font></span>
<span style="margin: 0px; line-height: 115%; font-size: 9pt;"><font color="#000000" face="Arial">Parameter OAUTH2APPNAME = "Google";</font></span>
<span style="margin: 0px; line-height: 115%; font-size: 9pt;"><font color="#000000" face="Arial">ClassMethod OnPage() As %Status</font></span>
<span style="margin: 0px; line-height: 115%; font-size: 9pt;"><font color="#000000" face="Arial">{</font></span>
<span style="margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">  </span>&html<</font></font></span>
<span style="margin: 0px; line-height: 115%; font-size: 9pt;"><font color="#000000" face="Arial"></font></span>
<span style="margin: 0px; line-height: 115%; font-size: 9pt;"><font color="#000000" face="Arial"></font></span>
<span style="margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">        </span><!-- insert the page content here --></font></font></span>
<span style="margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">        </span><h1>Google OAuth2 API</h1></font></font></span>
<span style="margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">        </span><p>This page demo shows how to call Google API functions using OAuth2 authorization.</font></font></span>
<span style="margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">        </span><p>We are going to retrieve information about user and his/her Google Drive files as well as calendar entries.</font></font></span>
<span style="margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">        </span>></font></font></span>
<span style="margin: 0px; line-height: 115%; font-size: 9pt;"><span style="margin: 0px;"><font color="#000000" face="Arial">        </font></span></span>
<span style="margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">  </span>// we need to supply openid scope to authenticate to Google</font></font></span>
<span style="margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">  </span>set scope="openid https://www.googleapis.com/auth/userinfo.email "_</font></font></span>
<span style="margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">  </span>"https://www.googleapis.com/auth/userinfo.profile "_</font></font></span>
<span style="margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">  </span>"https://www.googleapis.com/auth/drive.metadata.readonly "_</font></font></span>
<span style="margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">  </span>"https://www.googleapis.com/auth/calendar.readonly"</font></font></span>
<span style="margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">  </span>set properties("approval_prompt")="force"</font></font></span>
<span style="margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">  </span>set properties("include_granted_scopes")="true"</font></font></span>
<span style="margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">  </span>set url=##class(%SYS.OAuth2.Authorization).GetAuthorizationCodeEndpoint(..#OAUTH2APPNAME,scope,</font></font></span>
<span style="margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">    </span>..#OAUTH2CLIENTREDIRECTURI,.properties,.isAuthorized,.sc) </font></font></span>
<span style="margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">  </span>w !,"<p><a href='"_url_"'><img border='0' alt='Google Sign In' src='images/google-signin-button.png' ></a>" </font></font></span>
<span style="margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">      </span>&html<</font></font></span>
<span style="margin: 0px; line-height: 115%; font-size: 9pt;"><font color="#000000" face="Arial">></font></span>
<span style="margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">  </span>Quit $$$OK</font></font></span>
<span style="margin: 0px; line-height: 115%; font-size: 9pt;"><font color="#000000" face="Arial">}</font></span>
<span style="margin: 0px; line-height: 115%; font-size: 9pt;"><font color="#000000" face="Arial">ClassMethod OnPreHTTP() As %Boolean [ ServerOnly = 1 ]</font></span>
<span style="margin: 0px; line-height: 115%; font-size: 9pt;"><font color="#000000" face="Arial">{</font></span>
<span style="margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">  </span>#dim %response as %CSP.Response</font></font></span>
<span style="margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">  </span>set scope="openid https://www.googleapis.com/auth/userinfo.email "_</font></font></span>
<span style="margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">    </span>"https://www.googleapis.com/auth/userinfo.profile "_</font></font></span>
<span style="margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">    </span>"https://www.googleapis.com/auth/drive.metadata.readonly "_</font></font></span>
<span style="margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">    </span>"https://www.googleapis.com/auth/calendar.readonly"</font></font></span>
<span style="margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">  </span>if ##class(%SYS.OAuth2.AccessToken).IsAuthorized(..#OAUTH2APPNAME,,scope,.accessToken,.idtoken,.responseProperties,.error) {</font></font></span>
<span style="margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">    </span>set %response.ServerSideRedirect="Web.OAUTH2.Google2N.cls"</font></font></span>
<span style="margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">  </span>}</font></font></span>
<span style="margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">  </span>quit 1</font></font></span>
<span style="margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">  </span>}</font></font></span>
<span style="margin: 0px; line-height: 115%; font-size: 9pt;"><font color="#000000" face="Arial">}</font></span>

代码的简要说明如下:

1.      OnPreHTTP 方法 - 首先,我们有机会时检查一下,我们是否由于 Google 授权而获得了有效的访问令牌——这种情况可能会发生,例如当我们只是刷新页面时。 如果没有,我们需要授权。 如果我们有令牌,我们只需将页面重定向到显示结果的页面

2.       OnPage 方法 - 只有在我们没有可用的有效访问令牌时,我们才到这里,因此我们需要开始通信——向 Google 进行身份验证和授权,以便它向我们授予访问令牌。

3.       我们定义了作用域字符串和属性数组,用于修改 Google 身份验证对话框的行为(我们需要先向 Google 进行身份验证,然后它才能根据我们的身份对我们进行授权)。

4.       最后,我们收到 Google 登录页面的 URL,然后将其提供给用户,接着提供同意页面。

还有一点注意事项:

我们在 OAUTH2CLIENTREDIRECTURI 参数的 https://www.localhost/csp/google/Web.OAUTH2.Google2N.cls 中指定真正的重定向页面。 但是,我们在 Google 凭据定义中使用了 InterSystems IRIS OAUTH 框架的系统页面! 重定向由我们的 OAUTH 处理程序类在内部处理。

第 2 页

此页面显示 Google 授权的结果,如果成功,我们将调用 Google API 调用以检索数据。 同样,此代码简单,但功能齐全。 相比读者的想象,我们以更结构化的方式来显示输入数据。

<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font color="#000000" face="Arial">Include %occInclude</font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font color="#000000" face="Arial">Class Web.OAUTH2.Google2N Extends %CSP.Page</font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font color="#000000" face="Arial">{</font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font color="#000000" face="Arial">Parameter OAUTH2APPNAME = "Google";</font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font color="#000000" face="Arial">Parameter OAUTH2ROOT = "https://www.googleapis.com";</font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font color="#000000" face="Arial">ClassMethod OnPage() As %Status</font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font color="#000000" face="Arial">{</font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">  </span>&html<</font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">   </span></font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">   </span>></font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">  </span>// Check if we have an access token</font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">  </span>set scope="openid https://www.googleapis.com/auth/userinfo.email "_</font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">    </span>"https://www.googleapis.com/auth/userinfo.profile "_</font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">    </span>"https://www.googleapis.com/auth/drive.metadata.readonly "_</font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">    </span>"https://www.googleapis.com/auth/calendar.readonly"</font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">  </span>set isAuthorized=##class(%SYS.OAuth2.AccessToken).IsAuthorized(..#OAUTH2APPNAME,,scope,.accessToken,.idtoken,.responseProperties,.error)</font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">  </span>if isAuthorized { </font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">    </span>// Google has no introspection endpoint - nothing to call - the introspection endpoint and display result -- see RFC 7662.<span style="margin: 0px;">  </span></font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">    </span>w "<h3>Data from <span style='color:red;'>GetUserInfo API</span></h3>"</font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">    </span>// userinfo has special API, but could be also retrieved by just calling Get() method with appropriate url<span style="margin: 0px;">    </span></font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">    </span>try {</font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">    </span>set tHttpRequest=##class(%Net.HttpRequest).%New()</font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">      </span>$$$THROWONERROR(sc,##class(%SYS.OAuth2.AccessToken).AddAccessToken(tHttpRequest,"query","GOOGLE",..#OAUTH2APPNAME))</font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">      </span>$$$THROWONERROR(sc,##class(%SYS.OAuth2.AccessToken).GetUserinfo(..#OAUTH2APPNAME,accessToken,,.jsonObject))</font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">      </span>w jsonObject.%ToJSON()</font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">    </span>} catch (e) {</font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">      </span>w "<h3><span style='color: red;'>ERROR: ",$zcvt(e.DisplayString(),"O","HTML")_"</span></h3>"<span style="margin: 0px;">    </span></font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">    </span>}</font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">    </span>/******************************************</font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">    </span>*<span style="margin: 0px;">                                         </span>*</font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">    </span>*<span style="margin: 0px;">      </span>Retrieve info from other APIs<span style="margin: 0px;">      </span>*</font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">    </span>*<span style="margin: 0px;">                                         </span>*</font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">    </span>******************************************/</font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">    </span>w "<hr>"</font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">    </span>do ..RetrieveAPIInfo("/drive/v3/files")</font></font></span>
<font color="#000000" face="Arial" size="2"> </font>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">    </span>do ..RetrieveAPIInfo("/calendar/v3/users/me/calendarList")</font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">  </span>} else {</font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">    </span>w "<h1>Not authorized!</h1>"<span style="margin: 0px;">  </span></font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">  </span>}</font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">  </span>&html<</font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">  </span>></font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">  </span>Quit $$$OK</font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font color="#000000" face="Arial">}</font></span>
<font color="#000000" face="Arial" size="2"> </font>

<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font color="#000000" face="Arial">ClassMethod RetrieveAPIInfo(api As %String)</font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font color="#000000" face="Arial">{</font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">  </span>w "<h3>Data from <span style='color:red;'>"_api_"</span></h3><p>"</font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">  </span>try {</font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">    </span>set tHttpRequest=##class(%Net.HttpRequest).%New()</font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">    </span>$$$THROWONERROR(sc,##class(%SYS.OAuth2.AccessToken).AddAccessToken(tHttpRequest,"query","GOOGLE",..#OAUTH2APPNAME))</font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">    </span>$$$THROWONERROR(sc,tHttpRequest.Get(..#OAUTH2ROOT_api))</font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">    </span>set tHttpResponse=tHttpRequest.HttpResponse</font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">    </span>s tJSONString=tHttpResponse.Data.Read()</font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">    </span>if $e(tJSONString)'="{" {</font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">      </span>// not a JSON</font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">      </span>d tHttpResponse.OutputToDevice()</font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">    </span>} else {<span style="margin: 0px;">      </span></font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">      </span>w tJSONString</font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">      </span>w "<hr/>"</font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">      </span>/*</font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">      </span>// new JSON API</font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">      </span>&html<<table border=1 style='border-collapse: collapse'>></font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">      </span>s tJSONObject={}.%FromJSON(tJSONString)</font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">      </span>set iterator=tJSONObject.%GetIterator()</font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">        </span>while iterator.%GetNext(.key,.value) {</font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">          </span>if $isobject(value) {</font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">      </span><span style="margin: 0px;">      </span>set iterator1=value.%GetIterator()</font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">            </span>w "<tr><td>",key,"</td><td><table border=1 style='border-collapse: collapse'>"</font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">            </span>while iterator1.%GetNext(.key1,.value1) {</font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">            </span>if $isobject(value1) {</font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">                </span>set iterator2=value1.%GetIterator()</font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">                </span>w "<tr><td>",key1,"</td><td><table border=0 style='border-collapse: collapse'>"</font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">                </span>while iterator2.%GetNext(.key2,.value2) {</font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">                    </span>write !, "<tr><td>",key2, "</td><td>",value2,"</td></tr>"<span style="margin: 0px;">                   </span></font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">                </span>}</font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">                </span>// this way we can go on and on into the embedded objects/arrays</font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">              </span>w "</table></td></tr>"</font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">            </span>} else {</font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">  </span><span style="margin: 0px;">                </span>write !, "<tr><td>",key1, "</td><td>",value1,"</td></tr>"<span style="margin: 0px;">       </span></font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">            </span>}</font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">            </span>}</font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">          </span>w "</table></td></tr>"</font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">          </span>} else {</font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">              </span>write !, "<tr><td>",key, "</td><td>",value,"</td></tr>"</font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">          </span>}</font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">        </span>}<span style="margin: 0px;">    </span><span style="margin: 0px;">   </span></font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">    </span>&html<</table><hr/></font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">    </span>></font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">    </span>*/</font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">    </span>}</font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">  </span>} catch (e) {</font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">    </span>w "<h3><span style='color: red;'>ERROR: ",$zcvt(e.DisplayString(),"O","HTML")_"</span></h3>"</font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font face="Arial"><font color="#000000"><span style="margin: 0px;">  </span>}</font></font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font color="#000000" face="Arial">}</font></span>
<span style="background: rgb(242, 242, 242); margin: 0px; line-height: 115%; font-size: 9pt;"><font color="#000000" face="Arial">}</font></span>

 

让我们快速看一下代码:

1.       首先,我们需要检查一下我们是否具有有效的访问令牌(即我们是否被授权)

2.       如果是,我们可以向 Google 提供且由已发布访问令牌覆盖的 API 发出请求

3.       为此,我们使用标准的 %Net.HttpRequest 类,但根据 API 规范,我们将访问令牌添加到 GET 或 POST 方法中

4.       如您所见,为了方便您,OAUTH 框架已实现 GetUserInfo()方法,但是您可以使用 Google API 规范直接检索用户信息,就像我们在 RetrieveAPIInfo()助手方法中所做的一样。

5.       由于在 OAUTH 世界中以 JSON 格式交换数据司空见惯,因此我们只读取传入的数据,然后简单地将其转储到浏览器。 应用程序开发人员可以解析和格式化接收到的数据,以便用户可以看明白。 但这超出了本演示的范围。 (尽管一些代码有注释,显示了如何完成解析。)

下图是一个输出屏幕截图,显示了原始 JSON 数据。

继续阅读第 2 部分,该部分讲述 InterSystems IRIS 担当授权服务器和 OpenID Connect 提供程序相关的内容。

0
0 399