配置 OAuth 委派授权
Windchill 可作为 ThingWorx 平台上构建的应用程序和混搭的资源提供者参与单一登录 (SSO)。在此用例中,用户可以登录到一个启用 SSO 的 应用程序,并授权正在与其交互的应用程序来访问其 Windchill 数据。这种 SSO 实施即称为委派授权,因为用户会授权服务提供者 (应用程序) 代表他们从资源提供者 (Windchill) 那里检索其信息,并且用户只需登录一次即可。有关受支持的 SSO 用例以及在 PTC 产品之间设置 SSO 联合所需配置步骤的完整说明,请参阅 PTC Single Sign-on Architecture and Configuration Overview Guide (《PTC 单一登录架构和配置概述指南》)。
PTC 产品平台 SSO 解决方案依赖基于 OAuth 标准的访问令牌交换来管理产品应用程序之间的委派授权。要在 Windchill 中启用 OAuth,必须对 securityContext.propertiesWeb.xml 这两个 Windchill 文件进行编辑。
OAuth 委派授权的通用配置
wt.oauth2.token.userNameAttribute 特性的值更新为 IDP 配置中指定的用户属性名称。例如,如果 PingFederate 为 IDP,则其值为 Username
更新 wt.oauth2.token.tokenType 特性以在 OPAQUE 与 JWT 之间切换令牌验证。默认值为 OPAQUE。
更新 wt.oauth2.token.scopeAttribute 特性以配置 OPAQUE 和 JWT 令牌的范围属性。默认值为 scope
OPAQUE 令牌的 OAuth 委派授权配置
要配置与中央授权服务器的连接,并指定由访问令牌和范围保护的系统资源,请按如下所述编辑 securityContext.properties 文件。此特性文件的路径为 <Windchill>\codebase\WEB-INF\security\config\securityContext.properties
1. 标识哪些资源应受 OAuth 2 令牌保护。受保护资源需从服务提供者处获取有效的 OAuth 2 令牌才能对其授予访问权限。要执行此操作,请在以下字符串后以 Ant 样式的模式列出资源:com.ptc.eauth.identity.oauth2.rs=/oauth/**
例如,要保护 Windchill 文档,请将按如下方式配置该字符串:
com.ptc.eauth.identity.oauth2.rs=/oauth/documents/**
在特性文件中只能指定单个阵列,即只能指定一行。因此,需要定义顶层 URL,以充分涵盖想要由 OAuth 2 令牌保护的所有资源。在步骤 4 中,指定应附加到 OAuth 令牌的范围,以便更好地控制对资源的访问。
2. 标识令牌验证端点。此值应设置为将验证访问令牌的 CAS 服务器。
org.springframework.security.oauth2.provider.token.RemoteTokenServices.checkTokenEndpointUrl=https://{cas-server}
.ptc.com:9031/as/introspect.oauth2
3. 提供为资源提供者创建的 PingFederate (CAS) OAuth 客户端中所指定的客户端 ID 和客户端密码。有关 Windchill OAuth 客户端的信息,请与 CAS 管理员联系。
org.springframework.security.oauth2.provider.token.RemoteTokenServices.clientId=rs_client
org.springframework.security.oauth2.provider.token.RemoteTokenServices.clientSecret={client secret}
4. 至少添加一个特性,以将 WINDCHILL_READ 范围与您想要提供给服务提供者的 Windchill 资源进行配对。
要配置由 WINDCHILL_READ 范围所公开的资源,必须添加相应的特性来定义对 securityContext.properties 文件的这种访问。在 Windchill 中,支持配置单一范围。测试是通过一个名为 WINDCHILL_READ 的范围执行的。在此文件中注册的范围也必须在用于管理 SSO 网络中访问令牌交换的中央授权服务器中以及在将要请求数据的服务提供者应用程序中进行注册。
每个范围特性均由以下几部分组成:
URL 前缀,用于指示受 OAuth 保护的资源,且在存在有效访问令牌的情况下可供访问。例如:
com.ptc.eauth.identity.oauth2.rs.InMemoryResourceScopeService.resourceScopes.
指向将公开的资源的目录路径。例如,/Windchill/oauth/documents
Ant 样式的路径模式。例如,/** 公开在任务之前所提供的路径下所有目录中的资源。
范围值 (名称)。例如,=WINDCHILL_READ。范围值不能包含空格。(空格用于分隔多个范围值,但当前不支持多个范围方案。)
以下是一个示例范围特性,要求 OAuth 访问令牌必须具有 WINDCHILL_READ 范围,才允许传递 /Windchill/oauth/documents 资源数据:
com.ptc.eauth.identity.oauth2.rs.InMemoryResourceScopeService.resourceScopes.
/Windchill/oauth/documents=WINDCHILL_READ
传入的请求必须具有一个访问令牌,且该令牌的必需范围是在给定资源的特性中定义的。这些特性为附加特性,也就是说,如果为同一资源定义了其他范围,则受保护数据的传入请求必须包含已为该资源定义的所有范围。例如,如果除了上述示例范围特性之外,您还创建了另一个特性,且该特性需要 /Windchill/oauth/documents 资源的 "ABC" 范围,则传入的访问令牌将需要 "WINDCHILL_READ" 和 "ABC" 范围才能访问资源。
JWT 令牌的 OAuth 授权配置
要使用 JWT 令牌为 Azure Active Directory 配置 OAuth 授权,请按以下步骤操作:
1. 通过 site.xconfwt.properties 文件中添加下列特性:
wt.jwt.oauth2.token.keytype
该值可以是 symmetric 或 asymmetric。默认值为 asymmetric。
wt.jwt.oauth2.token.certificateLocation
用于对 JWT 令牌进行签名的证书的位置。如果未指定 wt.jwt.oauth2.token.kidUrl 特性且 wt.jwt.oauth2.token.keytype=asymmetric,则此特性为必需特性。
wt.jwt.id.token.extraClaimsToBeValidated
其值可以是下列其中一个或多个值:iss、aud、exp、sub 和/或 typ。此特性会将默认强制声明的列表作为值。此特性用于验证 JWT 令牌中的额外声明,比如 iatnbf 等。
对于 PingFederate JWT,此特性不是必需特性。
wt.jwt.oauth2.token.issueAtTime
JWT 访问令牌的发行时间。由此可以确定 JWT 访问令牌的期限。默认值为 900000 millisecond/15min。
wt.jwt.oauth2.token.azure.tenantId
Tenant ID,这是与您的组织名称或域不同的全局唯一标识符 (GUID)。
wt.jwt.oauth2.token.audience
客户端 ID。
wt.jwt.oauth2.token.tokenIssuer
值为 https://sts.windows.net/<租户_ID>/。
wt.jwt.oauth2.token.algorithm
支持的算法:
对于对称密钥:HS256、HS384、HS512。默认值为 HS256。
对于非对称 (证书) 密钥:RS256、RS384、RS512。默认值为 RS256。
wt.jwt.oauth2.token.kidUrl
值为 https://login.microsoftonline.com/<租户_ID>/discovery/v2.0/keys。
对于 PingFederate,将其值设置为 https://<Ping Federate 主机>:9031/ext/oauth/jwks。
2. wt.jwt.oauth2.token.jwtIdpType 特性的默认值为 DEFAULT_JWT_IDP。将此特性的值更新为 securityContext.properties 文件中受支持的第三方标识符。这仅验证标准 JWT 声明。如果 AzureAD 为 IDP,请将此特性设置为 wt.jwt.oauth2.token.jwtIdpType = azure
3. 在 Windchill 中添加对称密钥配置。有关详细信息,请参阅 addValueToKeyStore 目标部分。在 WTKeystore 中使用 Windchill 提供的 EncryptPasswords.xml 实用程序添加对称密钥,如下面的示例所述:
ant -f EncryptPasswords.xml addValueToKeyStore
-DpropertyName=<property> -Dpassword=<password_value>
-Dwt.home=<Windchill_location>

ant -f EncryptPasswords.xml addValueToKeyStore -DpropertyName=wvs -Dpassword=hwmLrHtgOBSS0qiEeiGVtsRS1hy7IA7ovSWdlu9YVPk=
-Dwt.home="/opt/ptc/Windchill"
propertyName - PingFederate 的 kid 声明值。对于其他 IDP,此值会发生变化。
password - 用于 JWT 令牌签名的对称密钥的值。此值应为非加密值。
编辑 Web.xml 文件
Web.xml 文件执行下面两个编辑操作:
1. securityContext.properties 文件的位置添加到 WEB-INF/web.xml 文件中。
WEB-INF/web.xml 文件必须引用 securityContext.properties 文件。Web.xml 文件位于 <Windchill 安装位置>/codebase/WEB-INF/Web.xml 下。
将此位置添加到 Spring root Web 应用程序上下文位置的参数值中。如果已将 securityContext.properties 文件保存在其默认目录中,则以下示例就是有效的。将路径 WEB-INF/security/config/securityContext.xml 添加到 <param-value>config/mvc/applicationContext.xml</param-value> 中。上下文参数的结构应如下所示:
<context-param>
<description>Location of Spring root web application context</description>
<param-name>contextConfigLocation</param-name>
<param-value>config/mvc/applicationContext.xml
WEB-INF/security/config/securityContext.xml
</param-value>
</context-param>
* 
securityContext.properties 文件的位置也在 securityContext.xml 中被引用。如果已更改 securityContext.properties 的目录位置,则必须在 Web.xmlsecurityContext.xml 中更新对新位置的引用。
2. 添加 SpringSecurityFilterChain 代码段。
找到 <filter-mapping> 文件中的第一个 Web.xml 标记,并在该标记之前添加以下代码。
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/oauth/*</url-pattern>
</filter-mapping>
找到 <servlet-mapping> 文件中的第一个 Web.xml 标记,并在该标记之前添加以下代码。
<servlet>
<description>Bridge Servlet for Oauth Access</description>
<servlet-name>OauthAuthBridgeServlet</servlet-name>
<servlet-class>wt.servlet.AuthBridgeServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>OauthAuthBridgeServlet</servlet-name>
<url-pattern>/oauth/*</url-pattern>
</servlet-mapping>
重新启动 Windchill 和 Apache 服务器
编辑 securityContext.propertiesWeb.xml 文件后,重新启动 Windchill 和 Apache 服务器并执行测试,以验证 Windchill 是否通过 SSO 网络实现授权委派。
自定义 UserAuthenticationConverter 接口
根据自省端点的响应,需要实现 UserAuthenticationConverter 接口才能转换和提取要在访问令牌中使用的用户信息。例如:
如果响应格式为如下所示的嵌套用户名声明,请使用 securityContext.xml 文件中的 com.ptc.eauth.identity.oauth2.rs.PingFederateUserAuthenticationConverter
{
"access_token":
{
"USERNAME":"wcadmin" }
"scope":"WINDCHILL_READ"
"token_type":"urn:pingidentity.com:oauth2:validated_token",
"expires_in";5289,
"client_id";"TestJWTClient",
}
如果响应格式为如下所示的扁平结构,则使用预设 com.ptc.eauth.identity.oauth2.rs.IntrospectionUserAuthenticationConverter,而不需要对此文件进行任何更新。
{ "aud":"TEST_AUD",
"scope":"WINDCHILL_READ",
"iss":"TEST_ISS",
"active":true,
"USERNAME":"wcadmin",
"token_type":"Bearer",
"exp":1640609559,
"client_id":"TestJWTClient"
}
对于任何其他格式,都需要根据上述示例中的指导思路,正确读取响应中的用户名声明。您必须更改 extractAuthentication 方法中的代码来提取正确的声明详细信息。必须更新 <WT_HOME>/codebase/WEB-INF/security/config/ securityContext.xml 文件中的自定义 UserAuthenticationConverter,位置如下:

<!--Customize this depending on the exact nature of the Authorization server's "check token" endpoint -->
<bean_id="userTokenConverter" class="com.ptc.eauth.identity.oauth2.rs.PingFederateUserAuthenticationConverter">
<property name="userNameAttribute" value="${wt.oauth2.token.userNameAttribute}"/>
</bean>
使用下面的示例代码:
import java.util.HashMap;
import java.util.Map;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.provider.token.UserAuthenticationConverter;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
public class IntrospectionUserAuthenticationConverter implements UserAuthenticationConverter {
public static final String DEFAULT_USERNAME_ATTR = "username";
private String userNameAttribute = DEFAULT_USERNAME_ATTR;
public Map<String, ?> convertUserAuthentication(Authentication userAuthentication) { Map<String, String> map = new HashMap<>(); map.put(this.userNameAttribute, userAuthentication.getName()); return map; }
/** * This method extracts user defined claim name from the token * @param map - token returned from introspection end point * @return - user details extracted from token */
public Authentication extractAuthentication(Map<String, ?> map){
Object principal = null;
for (String key : map.keySet()) {
if (key.equalsIgnoreCase(this.userNameAttribute)) { principal = map.get(key);
break;
}
}
if (principal != null)
{
PreAuthenticatedAuthenticationToken response = new PreAuthenticatedAuthenticationToken(principal, null);
response.setDetails(map);
return (Authentication)response; }
return null; }
public void setUserNameAttribute(String userNameAttribute) {
this.userNameAttribute = userNameAttribute;
}
}
这对您有帮助吗?