インストールおよびアップグレード > 高度な展開の検討事項 > 認証 > Windchill における代替認証の設定 > シングルサインオン認証 > OAuth 委任認証の設定
  
OAuth 委任認証の設定
Windchill は、ThingWorx プラットフォーム上に構築されたアプリケーションとマッシュアップのリソースプロバイダとしてシングルサインオン (SSO) に参加できます。このユースケースでは、ユーザーは SSO が有効な ThingWorx ベースのアプリケーションにサインインし、使用しているアプリケーションを認証してそれらの Windchill データにアクセスできます。このような SSO の実装を委任認証と呼びます。これは、ユーザーがサービスプロバイダ (アプリケーション) に自分の代理としてリソースプロバイダ (Windchill) から情報を読み込む権限を与え、ユーザーは 1 回ログインするだけでよいからです。サポートされる SSO のユースケース、および PTC 製品の SSO フェデレーションのセットアップに必要な設定ステップの詳細については、PTC Product Platform Single Sign-on Guide を参照してください。
PTC 製品プラットフォームの SSO ソリューションは、製品アプリケーション間の委任認証の管理を行うために、OAuth 規格に基づいたアクセストークンの交換を利用しています。Windchill で OAuth を有効にするには、ファイル securityContext.propertiesWeb.xml の 2 つの 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 トークンが提示される必要があります。このためには、com.ptc.eauth.identity.oauth2.rs=/oauth/** という文字列の後ろに、ant スタイルのパターンで各リソースを記述します。
たとえば、Windchill ドキュメントを保護するには、文字列を以下のように設定します。
com.ptc.eauth.identity.oauth2.rs=/oauth/documents/**
単一パターンだけなので (つまり、プロパティファイルには行を 1 つだけ指定できる)、OAuth 2 トークンによって保護するすべてのリソースを完全に網羅するトップレベル URL を定義する必要があります。ステップ 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 リソースをペアにする 1 つ以上のプロパティを追加します。
スコープ WINDCHILL_READ によって公開されるリソースを設定するには、このアクセスを定義するプロパティを securityContext.properties ファイルに追加する必要があります。Windchill では、単一スコープの設定がサポートされています。テストは WINDCHILL_READ という名前のスコープを使用して実施されました。このファイルで登録されているスコープを、SSO ネットワークでのアクセストークンの交換を管理する中央認証サーバーと、データをリクエストするサービスプロバイダアプリケーションでも登録する必要があります。
各スコープのプロパティは以下の部分から構成されます。
OAuth によって保護され、有効なアクセストークンが提示された場合にアクセスが許可されるリソースを示す URL プレフィックス。次に例を示します。
com.ptc.eauth.identity.oauth2.rs.InMemoryResourceScopeService.resourceScopes.
公開されるリソースへのディレクトリパス。例: /Windchill/oauth/documents
ant スタイルのパスパターン。たとえば、/** では、このタスクの前に指定されているパスの下のすべてのディレクトリからリソースが公開されます。
スコープの値 (名前)。例: =WINDCHILL_READスコープの値にスペースが含まれていてはなりませんスペースは複数のスコープの値を区切るときに使用しますが、複数のスコープを指定するシナリオは現在のところサポートされていません。
以下に示すスコーププロパティのサンプルでは、/Windchill/oauth/documents リソースのデータを渡すことを許可するためには、OAuth アクセストークンにスコープ WINDCHILL_READ を添付することが要求されます。
com.ptc.eauth.identity.oauth2.rs.InMemoryResourceScopeService.resourceScopes.
/Windchill/oauth/documents=WINDCHILL_READ
指定されたリソースにアクセスするためにプロパティで定義されている必要なスコープを持つアクセストークンが、受信リクエストで指定されている必要があります。これらのプロパティは付加的なものであり、同じリソースに追加のスコープを定義した場合、保護されているデータに対する受信リクエストには、そのリソースに定義されているすべてのスコープが含まれている必要があります。たとえば、上記の例に加えて、/Windchill/oauth/documents リソース用にスコープ "ABC" を必要とする 2 つ目のプロパティを作成した場合、そのリソースにアクセスするためには、受信アクセストークンにスコープ "WINDCHILL_READ" と "ABC" が含まれている必要があります。
JWT トークンの OAuth 認証コンフィギュレーション
JWT トークンを使用して Azure Active Directory の OAuth 認証を設定するには、以下で説明する手順に従います。
1. site.xconf を介して wt.properties ファイルに以下のプロパティを追加します。
wt.jwt.oauth2.token.issueAtTime
JWT アクセストークンが発行された時間を指定します。これを基に JWT アクセストークンの有効期間を判断できます。デフォルト値は 900000 ミリ秒/15 分です。
wt.jwt.oauth2.token.azure.tenantId
組織名やドメインとは異なるグローバル一意識別子 (GUID) である Tenant ID を指定します。
wt.jwt.oauth2.token.audience
クライアント ID を指定します。
wt.jwt.oauth2.token.tokenIssuer
値として https://sts.windows.net/<テナント_id>/ を指定します。
wt.jwt.oauth2.token.algorithm
JWT アクセストークンの署名の検証に使用するアルゴリズムを指定します。デフォルト値は RS256 です。
wt.jwt.oauth2.token.kidUrl
値として https://login.microsoftonline.com/<テナント_id>/discovery/v2.0/keys を指定します。
2. ファイル securityContext.properties 内のプロパティ wt.jwt.oauth2.token.jwtIdpType の値を更新して、JWT トークンを送信する、サポートされているサードパーティ IDP 識別子に更新します (例: wt.jwt.oauth2.token.jwtIdpType = azure)。Azure 固有の検証が必要でない場合、このプロパティには値を指定しなくても構いません。この場合、標準の JWT 要求検証だけが行われます。
Web.xml ファイルの編集
Web.xml ファイルに対して以下の 2 つの編集を行います。
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 を追加します。
Web.xml ファイルで 1 つ目の <filter-mapping> タグを見つけて、そのタグの前に以下のコードを追加します。
<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>
Web.xml ファイルで 1 つ目の <servlet-mapping> タグを見つけて、そのタグの前に以下のコードを追加します。
<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.properties および Web.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;
}
}