Red Hat Training

A Red Hat training course is available for Red Hat JBoss Enterprise Application Platform

8.6.4. セキュリティーコンテキスト ID を変更する

概要

デフォルトでは、アプリケーションサーバーにデプロイされた EJB にリモート呼び出しを行うと、サーバーへの接続が認証され、この接続を介して受信した要求はすべて、接続を認証した ID として実行されます。これは、クライアントとサーバー間の呼び出しの両方に適用されます。同じクライアントから異なるアイデンティティーを使用する必要がある場合は、通常は、異なるアイデンティティーとして認証されるように、サーバーへの接続を複数開く必要があります。複数のクライアント接続を開くのではなく、認証されたユーザーに別のユーザーとしてリクエストを実行する権限を与えることができます。

このトピックでは、既存のクライアント接続で ID を切り替える方法について説明します。コード例は、クイックスタートのコードの要約版です。完全な実例については、ejb-security-interceptors クイックスタートを参照してください。

手順8.12 セキュリティーコンテキストの ID を変更する

保護された接続の ID を変更するには、次の 3 つのコンポーネントを作成する必要があります。
  1. クライアント側インターセプターを作成する

    クライアント側インターセプターは、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)メソッドとパスorderそしてそのinterceptor実例。Theorderこのクライアントインターセプターがインターセプターチェーンのどこに配置されるかを決定します。
    • ServiceLoader メカニズム

      このアプローチでは、META-INF/services/org.jboss.ejb.client.EJBClientInterceptor ファイルを作成し、クライアントアプリケーションのクラスパスに配置またはパッケージ化します。ファイルのルールは、Java ServiceLoader Mechanism によって指示されます。このファイルには、EJB クライアントインターセプター実装の完全修飾クラス名ごとに個別の行が含まれることが予想されます。EJB クライアントインターセプタークラスはクラスパスで利用できる必要があります。ServiceLoader メカニズムを使用して追加された EJB クライアントインターセプターは、クラスパスで検出された順序で、クライアントインターセプターチェーンの最後に追加されます。ejb-security-interceptors クイックスタートはこのアプローチを使用します。
  2. サーバー側コンテナーインターセプターを作成して設定する

    コンテナーインターセプタクラスは、単純な Plain Old Java Object (POJO) です。彼らは使用します@javax.annotation.AroundInvokeBean での呼び出し中に呼び出されるメソッドをマークします。コンテナーインターセプターの詳細については、以下を参照してください。「コンテナーインターセプターについて」
    1. コンテナーインターセプターを作成する

      このインターセプターは、ID を含む InvocationContext を受信し、その新しい ID への切り替えを要求します。以下は、実際のコード例の要約版です。
          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;
              UserPrincipal connectionUser = null;
      
              Map<String, Object> contextData = invocationContext.getContextData();
              if (contextData.containsKey(DELEGATED_USER_KEY)) {
                  desiredUser = new SimplePrincipal((String) contextData.get(DELEGATED_USER_KEY));
      
                  Collection<Principal> connectionPrincipals = SecurityActions.getConnectionPrincipals();
      
                  if (connectionPrincipals != null) {
                      for (Principal current : connectionPrincipals) {
                          if (current instanceof UserPrincipal) {
                              connectionUser = (UserPrincipal) current;
                              break;
                          }
                      }
      
                  } else {
                      throw new IllegalStateException("Delegation user requested but no user on connection found.");
                  }
              }
      
      
              ContextStateCache stateCache = null;
              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 use an authentication token
                          // so now we attempt the switch.
                          stateCache = SecurityActions.pushIdentity(desiredUser, new OuterUserCredential(connectionUser));
                      } 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 context
                  if (stateCache != null) {
                      SecurityActions.popIdentity(stateCache);;
                  }
              }
          }
      
    2. コンテナーインターセプターを設定する

      サーバー側のコンテナーインターセプターを設定する方法については、以下を参照してください。「コンテナーインターセプターの設定」
  3. JAASLoginModule を作成します

    このコンポーネントは、ユーザーが要求された 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;
    }
    
コードの完全な手順と詳細については、ejb-security-interceptors クイックスタート README.html ファイルを参照してください。