Java web服务:理解WS-Policy

日期: 2011-02-24 作者:Dennis Sosnoski 来源:TechTarget中国 英文

  在文本中,我首先为您提供一点WS-Policy的背景知识,弥补您在这方面的不足。然后,您将看到如何在WSDL文档中附加策略到服务。最后,我将针对Axis2、Metro和CXF尝试一些WS-Policy配置示例,然后告诉您它们在实践中如何工作。

  WS-Policy基础

  WS-Policy定义了一个简单XML结构,由4个不同元素和一对属性组成。根据WS-Policy解释,这些元素和属性提供一种方法来组织和合并任意复杂度的策略断言(policy assertions)。为了定义构成策略的真实断言,您需要使用特定扩展,比如WS-SecurityPolicy,而不是WS-Policy本身。

  为了方便起见,WS-Policy定义了一个标准形式的策略表达式和一组可以创建更紧凑的策略表达式的规则。标准形式可能有点繁琐,尽管WS-Policy推荐 “应该将策略表达式的标准形式用于实践”,但是,多数策略文件撰写者倾向于使用(至少部分使用)紧凑表达式规则,使文档更人性化。策略文档的解释是基于标准形式的,因此我首先对它做一介绍。

  标准形式表达式

  标准形式策略表达式最多使用 3 个元素,必须按照特定顺序嵌套。最外层的一般是<wsp:Policy>,它必须且只能包含一个<wsp:ExactlyOne>子元素。而嵌套的<wsp:ExactlyOne>元素,可以包含任意多(可以为 0)的<wsp:All>子元素。因此最简单的标准形式策略表达式是<wsp:Policy><wsp:ExactlyOne/></wsp:Policy>。

  任何标准形式的策略断言必须被嵌套在<wsp:All>元素中。这些策略断言自身可以包含策略表达式。在本系列之前的文章中,您可能见过WS-SecurityPolicy断言包含嵌套的策略表达式。在一个标准形式策略表达式中,所有这些嵌套的策略断言必须是标准形式。(实际上,它们必须在一个更为严格的标准形式子集中,子集中每个<wsp:ExactlyOne>元素,除了最外层的那个,都有且仅有一个<wsp:All>子元素。) 清单 1 是嵌套策略表达式示例的一个片段,以标准形式表示:

  清单 1. WS-SecurityPolicy嵌套策略表达式摘录
    

以下是引用片段:
<wsp:Policy>
  <wsp:ExactlyOne>
    <wsp:All>
      <sp:AsymmetricBinding
          >
        <wsp:Policy>
          <wsp:ExactlyOne>
            <wsp:All>
              <sp:InitiatorToken>
                <wsp:Policy>
                  <wsp:ExactlyOne>
                    <wsp:All>
                      <sp:X509Token
                          sp:IncludeToken=”…/IncludeToken/AlwaysToRecipient”>
                        <wsp:Policy>
                          <wsp:ExactlyOne>
                            <wsp:All>
                              <sp:RequireThumbprintReference/>
                            </wsp:All>
                          </wsp:ExactlyOne>
                        </wsp:Policy>
                      </sp:X509Token>
                    </wsp:All>
                  </wsp:ExactlyOne>
                </wsp:Policy>
              </sp:InitiatorToken>
              …

  尽管有多层嵌套元素,但语法结构是比较简单的。在一个策略表达式的标准形式中,<wsp:Policy>只是策略表达式的一个包装,而顶层策略表达式才是附加名称或标识符之处。嵌套的<wsp:ExactlyOne>元素表示嵌套的<wsp:All>元素代表的替代项的一个or组合(因此,如果嵌套的替代项其中的一个符合标准,那整个策略表达式就是符合标准的)。每一个<wsp:All>元素表示嵌套策略断言的一个and组合(因此,只有所有断言符合标准,替代项才符合标准)。

  非标准化策略

  策略表达式不需要都是标准形式的,非标准策略表达式可以包含嵌套替代项(一个<wsp:ExactlyOne>元素的多个<wsp:All>子元素),也可以使用下一节将要讨论的紧凑策略表达式。

  如果您学习过逻辑原理,您就会知道标准表示形式(没有嵌套替代项)等同于逻辑表达式的析取范式(disjunctive normal form)。实际上,策略表达式只是使用一个尖括号格式的逻辑表达式,断言是子句。在逻辑理论中,任何逻辑表达式都可以被转换成析取范式 ,而同样的原则适用于用嵌套替代项编写的策略表达式 — 仅在顶层可以被扩展成一个含有替代项的标准格式表示。标准形式策略表达式的一个主要优势是,从程序语法上检查两个策略的兼容性变得很容易 — 如果两个标准策略是兼容的,它们将有一个或多个顶层 <wsp:All> 元素,包含相同的断言集。

  紧凑策略表达式

  标准形式策略表达式是冗长的,特别是如果它包含嵌套替代项。WS-Policy定义了一些选项,您可以用来创建比标准形式更简明的策略表达式,使策略更易理解。当以紧凑形式 本身使用这些选项时,WS-Policy文档有时候会变得混乱。实际上,一个策略的多个紧凑表达式等价于一个标准表达式。在本文中,我只是用紧凑表达式 指代使用一个或多个选项的策略。

  紧凑表达式选项的一个特性是,拥有通过基本策略元素(在策略术语中称为操作,因为每个元素暗含嵌套断言的一个具体解释)任意次序嵌套来表达策略的能力。嵌套规则也定义一种直接使用的<wsp:Policy>元素(不需要标准形式中必须有的<wsp:ExactlyOne> 子元素)的解释等价于一个 <wsp:All>,这可能是最广泛使用的紧凑表达式特性。

  清单 2 显示了(清单 1 中)同一个策略的紧凑表达式,使用<wsp:Policy>表示。这个比第一个长度减少了一半,而且对大多数人来说都很容易理解 。

  清单 2. 紧凑形式的简单策略
    

以下是引用片段:
<wsp:Policy>
  <sp:AsymmetricBinding
      >
    <wsp:Policy>
      <sp:InitiatorToken>
        <wsp:Policy>
          <sp:X509Token
              sp:IncludeToken=”…/IncludeToken/AlwaysToRecipient”>
            <wsp:Policy>
              <sp:RequireThumbprintReference/>
            </wsp:Policy>
          </sp:X509Token>
        </wsp:Policy>
      </sp:InitiatorToken>
      …

 
  WS-Policy定义了一个变换集,您可以用来将使用紧凑选项的策略表达式转换成标准形式,因此没有理由直接使用标准形式。使用计算机将紧凑表达式转换成标准表达式比起人工解析标准表达式容易得多。

  策略包含

  除了简化元素嵌入,紧凑表达式也提供一种引用和重用策略表达式的方法。您可以使用第 4 个WS-Policy元素<wsp:PolicyReference>来实现。<wsp:PolicyReference>元素可以在任何策略断言出现的地方出现。引用的策略表达式被策略引用(Policy reference)有效替代(技术上是使用<wsp:All>元素替代引用的<wsp:Policy>元素。)

  清单 3 说明了策略包含的使用,显示使用一个独立的策略表达式和一个策略引用重构 清单 2 中的策略表达式:

  清单 3. 策略引用
    

以下是引用片段:
<!– Client X.509 token policy assertion. –>
<wsp:Policy wsu:Id=”ClientX509″
    >
  <sp:InitiatorToken>
    <wsp:Policy>
      <sp:X509Token
          sp:IncludeToken=”…/IncludeToken/AlwaysToRecipient”>
        <wsp:Policy>
          <sp:RequireThumbprintReference/>
        </wsp:Policy>
      </sp:X509Token>
    </wsp:Policy>
  </sp:InitiatorToken>
</wsp:Policy>
<wsp:Policy>
  <sp:AsymmetricBinding
      >
    <wsp:Policy>
      <wsp:PolicyReference URI=”#ClientX509″/>
      …

  策略引用可用于本地策略表达式,如 清单 3 所示,也可用于外部策略表达式。对于外部策略表达式,引用的URI属性通常提供外部策略的真实 URL。

  正如您在本文后面提供的策略测试示例中所看到的,策略包含目前不是普遍支持的,这限制了这个原本很出色的特性的使用。

  策略替代项

  正如您之前看到的,WS-Policy结构支持选择替代项作为策略的一部分,使用<wsp:ExactlyOne>元素。有了紧凑表达式,您就可以(至少理论上)使用特定属性创建选项。根据WS-Policy建议,您可以向任何策略断言添加wsp:Optional=”true”属性,使断言成为一个选择而不是需求,即使断言是<wsp:All>或<wsp:Policy>元素的子元素。

  策略替代项似乎是一个很有用的特性,但是要给出一个非凡的运行示例也比较困难。特性用于实践的情况通常能够对安全性的一个特定组件进行处理,比如一个UsernameToken,可选。较复杂的替代项,比如允许客户以UsernameToken或X.509证书格式提供验证,当前WS-SecurityPolicy实现功能超出了本书范围。

  策略附加

  在WSDL 1.1(有些过时,但仍然是最广泛使用的服务定义格式),服务定义使用层次结构,第一层(底层)由 <wsdl:message> 元素构成,定义消息的 XML 结构到服务,或者从服务定义。第二层是<wsdl:portType>元素,定义操作集,每一个操作由输入、输出或默认消息指定。第 3 层是<wsdl:binding>元素,使用<wsdl:portType>将一个特定消息协议和访问方法联系到一起。第 4 层 <wsdl:portType> 元素格式的服务器端定义,这指定<wsdl:binding>的访问路径。

  WS-Policy允许您在几个点上向WSDL服务定义附加策略,这几个点不能精确匹配定义的层。然而,层代表的是服务定义的一个逻辑结构,WS-Policy更关注消息和消息分组。WS-Policy使用的 4 个消息分组级别是:

  消息:策略适用于一个特定消息(如果策略是通过 <wsdl:message> 元素附加的,消息可用于任何地方;或者,如果在<wsdl:portType>或 <wsdl:binding> 元素中策略是通过操作的 input/output/fault 定义附加的,当消息被一个特定操作所用时)。

  操作:策略适用于一个特定操作的所有消息交换(在<wsdl:binding>或<wsdl:portType>中,通过<wsdl:operation>元素附加的策略)。

  端点:策略适用于一个特定服务绑定的所有消息交换(通过<wsdl:port>或<wsdl:binding>附加的策略),或者适用于基于一个特定端口类型的所有服务绑定的消息交换(附加到<wsdl:portType>的策略)。

  服务:策略适用于所有端点和所有与服务关联的操作。

  WSDL所用的主要策略附加机制同另一个策略—在 策略包含 部分描述的<wsp:PolicyReference>元素—中引用策略的机制是一样的。WS-Policy 元素可以被添加作为任何之前列出的WSDL元素的一个子元素,指定在消息分组层应用的策略。您也可以使用恰当内容直接嵌入一个策略作为<wsp:Policy>元素,但是通常最好使用策略引用,便于WSDL结构保持整洁。

  在消息分组一层中应用的策略,可被下一层继承,在<wsp:All>元素中合并。这使得实际(在WS-Policy术语中是有效)适用于每个消息的策略成为所有策略(适用于消息、操作、端点和服务层)的连接点。因此,策略不是由消息本身决定的,而是由使用消息的上下文决定的。
 
  清单 4 说明了这些操作如何进行,显示了使用策略引用定义WSDL的框架:

  清单 4. 策略附加示例
    

以下是引用片段:
<wsdl:binding name=”LibrarySoapBinding” type=”wns:Library”>
  <wsp:PolicyReference  URI=”#UsernameToken”/>
  <wsdlsoap:binding style=”document” transport=”http://schemas.xmlsoap.org/soap/http”/>
  
  <wsdl:operation name=”addBook”>
  
    <wsp:PolicyReference  URI=”#AsymmEncr”/>
    …
  </wsdl:operation>
</wsdl:binding>

  在这种情况下,需要UsernameToken的策略被附加在<wsdl:binding层,一个需要不对称消息加密的附加策略被附加到addBook操作,定义为绑定的一部分。假设这个示例显示了全套WSDL策略引用,UsernameToken永远需要,但是消息签名仅用于addBook操作。

  作为使用<wsp:PolicyReference>直接从WSDL元素引用策略的一种替代方法,您也可以使用wsp:PolicyURIs属性。您可以将该属性添加到策略被添加到的任何WSDL元素。本质上,这同使用<wsp:PolicyReference>子元素功能一样。

  WS-SecurityPolicy附加

  WS-SecurityPolicy指定消息分组级别,其中不同类型的策略断言可以被附加到一个服务描述中。例如,<sp:TransportBinding>断言通常用来指定安全性,只能被添加在端点层,而<sp:AsymmetricBinding>和<sp:SymmetricBinding>断言通常用来指定消息加密和签名,只用于端点或操作层。

  尽管<sp:AsymmetricBinding>或<sp:SymmetricBinding>不能在消息层指定,但您可以 在消息层指定加密或者签名的组件。这意味至少从理论上可以在消息令牌基础上指定加密或签名。

  策略示例

  现在,您已经学习了WS-Policy的基本原理,以及它如何同WSDL协作,是时候使用这些原理尝试一些策略示例了。

  清单 5 是一个示例(effective1.wsdl 在样例代码 下载 部分),在一个WSDL服务定义中使用 3 层策略附件。所用的 3 个策略是:

  UsernameToken:需要一个带有散列密码的UsernameToken。

  SymmEncr:需要使用一个客户端生成您的密钥进行对称加密。

  EncrBody:需要对消息体进行加密。

  清单 5. 有效策略示例
    

以下是引用片段:
<wsdl:definitions targetNamespace=”http://ws.sosnoski.com/library/wsdl”…>
  
  <wsp:Policy wsu:Id=”UsernameToken” …>
    <sp:SupportingTokens>
      <wsp:Policy>
        <sp:UsernameToken sp:IncludeToken=”…/IncludeToken/AlwaysToRecipient”>
          <wsp:Policy>
            <sp:HashPassword/>
          </wsp:Policy>
        </sp:UsernameToken>
      </wsp:Policy>
    </sp:SupportingTokens>
  </wsp:Policy>
  
  <wsp:Policy wsu:Id=”SymmEncr” …>
    <sp:SymmetricBinding>
      <wsp:Policy>
        <sp:ProtectionToken>
          <wsp:Policy>
            <sp:X509Token sp:IncludeToken=”…/IncludeToken/Never”>
              …
            </sp:X509Token>
          </wsp:Policy>
        </sp:ProtectionToken>
        …
      </wsp:Policy>
    </sp:SymmetricBinding>
    …
  </wsp:Policy>
  
  <wsp:Policy wsu:Id=”EncrBody” …>
    <sp:EncryptedParts>
      <sp:Body/>
    </sp:EncryptedParts>
  </wsp:Policy>
  …
  <wsdl:binding name=”LibrarySoapBinding” type=”wns:Library”>
    <wsp:PolicyReference 
        URI=”#UsernameToken”/>
    …
    <wsdl:operation name=”getBook”>
      <wsp:PolicyReference 
          URI=”#SymmEncr”/>
      <wsdlsoap:operation soapAction=”urn:getBook”/>
      <wsdl:input name=”getBookRequest”>
        <wsdlsoap:body use=”literal”/>
      </wsdl:input>
      <wsdl:output name=”getBookResponse”>
        <wsp:PolicyReference 
            URI=”#EncrBody”/>
        <wsdlsoap:body use=”literal”/>
      </wsdl:output>
    </wsdl:operation>
    <wsdl:operation name=”getBooksByType”>
      <wsp:PolicyReference 
          URI=”#SymmEncr”/>
      <wsdlsoap:operation soapAction=”urn:getBooksByType”/>
      <wsdl:input name=”getBooksByTypeRequest”>
        <wsdlsoap:body use=”literal”/>
      </wsdl:input>
      <wsdl:output name=”getBooksByTypeResponse”>
        <wsp:PolicyReference 
            URI=”#EncrBody”/>
        <wsdlsoap:body use=”literal”/>
      </wsdl:output>
    </wsdl:operation>
    <wsdl:operation name=”getTypes”>
    …
    </wsdl:operation>
    <wsdl:operation name=”addBook”>
      <wsp:PolicyReference 
          URI=”#SymmEncr”/>
      <wsdlsoap:operation soapAction=”urn:addBook”/>
      <wsdl:input name=”addBookRequest”>
        <wsp:PolicyReference 
            URI=”#EncrBody”/>
        <wsdlsoap:body use=”literal”/>
      </wsdl:input>
      <wsdl:output name=”addBookResponse”>
        <wsdlsoap:body use=”literal”/>
      </wsdl:output>
      <wsdl:fault name=”addDuplicateFault”>
        <wsp:PolicyReference 
            URI=”#EncrBody”/>
        <wsdlsoap:fault name=”addDuplicateFault” use=”literal”/>
      </wsdl:fault>
    </wsdl:operation>
  </wsdl:binding>
  …
</wsdl:definitions> 

  清单 5 的WSDL文件中,策略引用(黑体部分)将UsernameToken策略附加到<wsdl:binding>,以至于UsernameToken对所有操作都是必须的。SymmEncr策略被附加到交换图书信息的所有操作的一个 <wsdl:operation> 中,而EncrBody策略被附加到包含图书信息的消息中 — 以至于图书信息总是被加密发送的。

  这是一个很麻烦的策略,因为它需要客户生成一个密钥,并使用请求消息将这个密钥发送到客户端,尽管密钥仅作加密用。Axis2 1.5.2和Metro 2.0.1都不能完全处理这种情况。Axis2使用WS-Policy 1.5策略名称空间完全忽略策略组件。当我改为使用提交名称空间时,Axis2在客户端代码中生成一个合理的配置 — 但是在运行时,在客户端和服务器端同时忽略策略时,运行不安全。Metro使用一个UsernameToken生成一个请求消息,但是没有密钥信息。

  CXF 2.3.0则表现得很好,可以正确处理策略,但以下两个案例除外。addBook操作成功时,响应消息不使用加密。CXF服务器可以正确处理这种情况,但是在处理响应时客户端会抛出一个异常。另一个CXF错误也出现在使用addBook操作时,但这次是在请求失败时,返回的应用程序错误响应应该使用加密,但是CXF服务器发送它时不使用加密,而且客户端也毫无异议地接受了。

  清单 5 中的策略演示了选择性使用消息加密,仅加密那些包含订单信息的消息。然而,这个策略使用对称加密。能够使用非对称加密处理同类型的事情就太好了,其中客户端有自己的证书(特别是当您想要签署发送者证书消息时)。清单 6 是专为此设计的示例。该示例只使用WSDL的两个策略:

  AsymmBinding:需要非对称加密,使用双重证书。

  SignBody:需要消息体签名。

  清单 6. 非对称签署示例
    

以下是引用片段:
<wsdl:definitions targetNamespace=”http://ws.sosnoski.com/library/wsdl”…>
  <wsp:Policy wsu:Id=”AsymmBinding”  …>
    <sp:AsymmetricBinding>
      <wsp:Policy>
        <sp:InitiatorToken>
          <wsp:Policy>
            <sp:X509Token sp:IncludeToken=”…/IncludeToken/AlwaysToRecipient”>
              …
            </sp:X509Token>
          </wsp:Policy>
        </sp:InitiatorToken>
        <sp:RecipientToken>
          <wsp:Policy>
            <sp:X509Token sp:IncludeToken=”…/IncludeToken/Never”>
              …
            </sp:X509Token>
          </wsp:Policy>
        </sp:RecipientToken>
        …
      </wsp:Policy>
    </sp:AsymmetricBinding>
  </wsp:Policy>
  
  <wsp:Policy wsu:Id=”SignBody”  …>
    <sp:SignedParts>
      <sp:Body/>
    </sp:SignedParts>
  </wsp:Policy>
  …
  <wsdl:binding name=”LibrarySoapBinding” type=”wns:Library”>
    …
    <wsdl:operation name=”getBook”>
      <wsp:PolicyReference 
          URI=”#AsymmBinding”/>
      <wsdlsoap:operation soapAction=”urn:getBook”/>
      <wsdl:input name=”getBookRequest”>
        <wsdlsoap:body use=”literal”/>
      </wsdl:input>
      <wsdl:output name=”getBookResponse”>
        <wsp:PolicyReference 
            URI=”#SignBody”/>
        <wsdlsoap:body use=”literal”/>
      </wsdl:output>
    </wsdl:operation>
    <wsdl:operation name=”getBooksByType”>
      <wsp:PolicyReference 
          URI=”#AsymmBinding”/>
      <wsdlsoap:operation soapAction=”urn:getBooksByType”/>
      <wsdl:input name=”getBooksByTypeRequest”>
        <wsdlsoap:body use=”literal”/>
      </wsdl:input>
      <wsdl:output name=”getBooksByTypeResponse”>
        <wsp:PolicyReference 
            URI=”#SignBody”/>
        <wsdlsoap:body use=”literal”/>
      </wsdl:output>
    </wsdl:operation>
    <wsdl:operation name=”getTypes”>
      …
    </wsdl:operation>
    <wsdl:operation name=”addBook”>
      <wsp:PolicyReference 
          URI=”#AsymmBinding”/>
      <wsdlsoap:operation soapAction=”urn:addBook”/>
      <wsdl:input name=”addBookRequest”>
        <wsp:PolicyReference 
            URI=”#SignBody”/>
        <wsdlsoap:body use=”literal”/>
      </wsdl:input>
      <wsdl:output name=”addBookResponse”>
        <wsdlsoap:body use=”literal”/>
      </wsdl:output>
      <wsdl:fault name=”addDuplicateFault”>
        <wsdlsoap:fault name=”addDuplicateFault” use=”literal”/>
      </wsdl:fault>
    </wsdl:operation>
  </wsdl:binding>
  …
</wsdl:definitions> 

  清单 6 中的示例使用非对称加密技术签署所有消息,提供图书信息。这意味着getBook和getBooksByType响应消息应该由服务器签署,而addBook请求消息应该由客户端签署。因为这是使用非对象加密技术,而每一端有自己的证书和私有密钥,比起 清单 5 对称加密技术示例,它处理起来更为简单。

  外部策略引用外部策略引用不能很好地融入提供的示例结构,但是我想试试看。对于我的测试,我使用两个关系引用,<wsp:PolicyReference http://www.w3.org/ns/ws-policy”>http://www.w3.org/ns/ws-policy” URI=”./asymm-binding-policy.xml”/>形式的关系引用和位于web服务器的绝对URL。Axis2和Metro都不能解决这类策略引用,但它们在CXF上可以正确运行。

  在早期示例中Axis2同样失败,当我修改提交名称空间时,除了在两端都忽略策略组件,还完全忽略使用WS-Policy 1.5策略名称空间的策略组件,并生成客户端策略。

  Metro不能运行此策略配置,在客户端使用第一个请求抛出一个NullPointerException。经过一番调查,我发现,如果不仅仅在操作和消息层附加策略,在端点层也附加一个策略,那么问题将会变得更糟糕。Metro 示例包括一个effective3.wsdl,其中有一个来自 <wsdl:binding> 的 #AsymmBinding 策略引用,该示例运行没有问题(尽管,在对称案例中像 CXF,但是Metro不能应用安全性到应用程序错误响应)。

  使用 清单 6 策略配置,CXF也可能失败,但是在目前代码中没有轻松的工作环境。在发送getBook请求消息时,CXF客户端代码不能正确生成一个签名,而服务器端代码也在处理请求消息时失败。因此,当AsymmetricBinding在作用域中时,它和当前CXF代码一样坚持生成签名,即使没有什么需要签署。

  策略包装

  WS-Policy定义一个灵活且功能强大的结构,用于表达任何形式的约束。不幸的是,web 服务栈使用的WS-Policy和WS-SecurityPolicy处理的实施不能实现较大的灵活性。因为缺乏实现支持,WS-Policy 的其他有用特性不能用于想要同全方位的web服务栈交互的web服务。

  基本高效策略处理,在WSDL服务定义中策略依此添加到不同端点,是一个策略和WSDL设计的核心特性,应该用于适合您的服务。但是这类配置的一个潜在有用特性—可以根据情况选择性地签署和加密消息—不能可靠地进行工作。考虑到这一限制,如果您想要最好的互操作性,目前最好同时在<wsdl:binding>和<wsdl:operation>层附加策略。

  外部策略对于SOA类环境是特别有用的,在这类环境中,可以构建一组通用策略,便于整个组织使用,且每个服务可以引用满足其需求的策略。尽管,目前这一特性并不被所有开源Java web服务栈所支持(只有Apache CXF可以正确处理),大型组织可能想要使用这一特性,且限制web服务实现来使用一个支持它的栈(不论开源还是商业)。通过其他方法您也可能达到同样的效果,比如通过使用WSDL。

我们一直都在努力坚持原创.......请不要一声不吭,就悄悄拿走。

我原创,你原创,我们的内容世界才会更加精彩!

【所有原创内容版权均属TechTarget,欢迎大家转发分享。但未经授权,严禁任何媒体(平面媒体、网络媒体、自媒体等)以及微信公众号复制、转载、摘编或以其他方式进行使用。】

微信公众号

TechTarget微信公众号二维码

TechTarget

官方微博

TechTarget中国官方微博二维码

TechTarget中国

相关推荐