16.4. セキュリティーコンテキスト ID の変更
概要
デフォルトでは、アプリケーションサーバーにデプロイされた EJB にリモートコールを行う場合は、サーバーへの接続が認証され、この接続を介して受信されたすべての要求が、接続を認証した ID として実行されます。これは、クライアントとサーバー間のコールとサーバー間のコールの両方に適用されます。同じクライアントから異なる ID を使用する必要がある場合は、通常、サーバーに対して複数の接続を開き、各接続が異なる ID として認証されるようにする必要があります。複数のクライアント接続を開く代わりに、認証済みユーザーに別のユーザーとして要求を実行するパーミッションを与えることができます。
このトピックでは、既存のクライアント接続の ID を切り替える方法について説明します。完全な実例については、
ejb-security-interceptors
クイックスタートを参照してください。以下のコード例は、クイックスタートのコードを抜粋したものです。
手順16.2 セキュリティーコンテキストの ID の変更
セキュアな接続の ID を変更するには、以下の 3 つのコンポーネントを作成する必要があります。
クライアントサイドインターセプターの作成
このインターセプターは、org.jboss.ejb.client.EJBClientInterceptor
を実装する必要があります。インターセプターは、コンテキストデータマップを介して要求された ID を渡すことが期待されます。このコンテキストデータマップは、EJBClientInvocationContext.getContextData()
への呼び出しを介して取得できます。クライアントサイドインターセプターの例は、以下のとおりです。public class ClientSecurityInterceptor implements EJBClientInterceptor { public void handleInvocation(EJBClientInvocationContext context) throws Exception { Principal currentPrincipal = SecurityActions.securityContextGetPrincipal(); if (currentPrincipal != null) { Map<String, Object> contextData = context.getContextData(); contextData.put(ServerSecurityInterceptor.DELEGATED_USER_KEY, currentPrincipal.getName()); } context.sendRequest(); } public Object handleInvocationResult(EJBClientInvocationContext context) throws Exception { return context.getResult(); } }
ユーザーアプリケーションは、以下のいずれかの方法でEJBClientContext
のインターセプターに接続できます。プログラミング
この方法では、org.jboss.ejb.client.EJBClientContext.registerInterceptor(int order, EJBClientInterceptor interceptor)
API を呼び出し、order
およびinterceptor
インスタンスを渡します。order
は、このinterceptor
が置かれるクライアントインターセプターチェーンの位置を決定するために使用されます。ServiceLoader メカニズム
この方法では、META-INF/services/org.jboss.ejb.client.EJBClientInterceptor
ファイルを作成し、クライアントアプリケーションのクラスパスに配置またはパッケージ化する必要があります。ファイルのルールは、Java ServiceLoader メカニズムにより決まります。このファイルでは、EJB クライアントインターセプター実装の完全修飾名が各行に含まれることが期待されます。EJB クライアントインターセプタークラスがクラスパスで利用可能である必要があります。ServiceLoader
メカニズムを使用して追加された EJB クライアントインターセプターは、クライアントインターセプターチェーンの最後に、クラスパスに指定された順序で追加されます。ejb-security-interceptors
クイックスタートでは、この方法が使用されます。
サーバーサイドコンテナーインターセプターの作成および設定
コンテナーインターセプタークラスは、単純な Plain Old Java Object (POJO) です。@javax.annotation.AroundInvoke
を使用して、Bean での呼び出し中に呼び出されるメソッドを指定します。コンテナーインターセプターの詳細については、 「コンテナーインターセプターについて」を参照してください。コンテナーインターセプターの作成
このインターセプターは、ID でInvocationContext
を受け取り、切り替えを要求します。実際のコード例を抜き出したものは以下のとおりです。public class ServerSecurityInterceptor { private static final Logger logger = Logger.getLogger(ServerSecurityInterceptor.class); static final String DELEGATED_USER_KEY = ServerSecurityInterceptor.class.getName() + ".DelegationUser"; @AroundInvoke public Object aroundInvoke(final InvocationContext invocationContext) throws Exception { Principal desiredUser = null; RealmUser connectionUser = null; Map<String, Object> contextData = invocationContext.getContextData(); if (contextData.containsKey(DELEGATED_USER_KEY)) { desiredUser = new SimplePrincipal((String) contextData.get(DELEGATED_USER_KEY)); Connection con = SecurityActions.remotingContextGetConnection(); if (con != null) { UserInfo userInfo = con.getUserInfo(); if (userInfo instanceof SubjectUserInfo) { SubjectUserInfo sinfo = (SubjectUserInfo) userInfo; for (Principal current : sinfo.getPrincipals()) { if (current instanceof RealmUser) { connectionUser = (RealmUser) current; break; } } } } else { throw new IllegalStateException("Delegation user requested but no user on connection found."); } } SecurityContext cachedSecurityContext = null; boolean contextSet = false; try { if (desiredUser != null && connectionUser != null && (desiredUser.getName().equals(connectionUser.getName()) == false)) { // The final part of this check is to verify that the change does actually indicate a change in user. try { // We have been requested to switch user and have successfully identified the user from the connection // so now we attempt the switch. cachedSecurityContext = SecurityActions.securityContextSetPrincipalInfo(desiredUser, new OuterUserCredential(connectionUser)); // keep track that we switched the security context contextSet = true; SecurityActions.remotingContextClear(); } catch (Exception e) { logger.error("Failed to switch security context for user", e); // Don't propagate the exception stacktrace back to the client for security reasons throw new EJBAccessException("Unable to attempt switching of user."); } } return invocationContext.proceed(); } finally { // switch back to original security context if (contextSet) { SecurityActions.securityContextSet(cachedSecurityContext); } } } }
コンテナーインターセプターの設定
サーバーサイドコンテナーインターセプターの設定方法については、 「コンテナーインターセプターの設定」を参照してください。
JAAS LoginModule の作成
このコンポーネントは、ユーザが要求された ID として要求を実行することが許可されていることを確認します。以下のコード例は、ログインと検証を実行するメソッドを示しています。@SuppressWarnings("unchecked") @Override public boolean login() throws LoginException { if (super.login() == true) { log.debug("super.login()==true"); return true; } // Time to see if this is a delegation request. NameCallback ncb = new NameCallback("Username:"); ObjectCallback ocb = new ObjectCallback("Password:"); try { callbackHandler.handle(new Callback[] { ncb, ocb }); } catch (Exception e) { if (e instanceof RuntimeException) { throw (RuntimeException) e; } return false; // If the CallbackHandler can not handle the required callbacks then no chance. } String name = ncb.getName(); Object credential = ocb.getCredential(); if (credential instanceof OuterUserCredential) { // This credential type will only be seen for a delegation request, if not seen then the request is not for us. if (delegationAcceptable(name, (OuterUserCredential) credential)) { identity = new SimplePrincipal(name); if (getUseFirstPass()) { String userName = identity.getName(); if (log.isDebugEnabled()) log.debug("Storing username '" + userName + "' and empty password"); // Add the username and an empty password to the shared state map sharedState.put("javax.security.auth.login.name", identity); sharedState.put("javax.security.auth.login.password", ""); } loginOk = true; return true; } } return false; // Attempted login but not successful. }
protected boolean delegationAcceptable(String requestedUser, OuterUserCredential connectionUser) { if (delegationMappings == null) { return false; } String[] allowedMappings = loadPropertyValue(connectionUser.getName(), connectionUser.getRealm()); if (allowedMappings.length == 1 && "*".equals(allowedMappings[1])) { // A wild card mapping was found. return true; } for (String current : allowedMappings) { if (requestedUser.equals(current)) { return true; } } return false; }
完全な指示とコードの詳細については、
README
ファイルを参照してください。