16.4. セキュリティーコンテキスト ID の変更

概要

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

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

手順16.2 セキュリティーコンテキストの 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) API を呼び出し、order および interceptor インスタンスを渡します。order は、この interceptor が置かれるクライアントインターセプターチェーンの位置を決定するために使用されます。
    • ServiceLoader メカニズム

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

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

      このインターセプターは、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);
                      }
                  }
              }
          }
      
    2. コンテナーインターセプターの設定

      サーバーサイドコンテナーインターセプターの設定方法については、 「コンテナーインターセプターの設定」を参照してください。
  3. 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 ファイルを参照してください。

このページには機械翻訳が使用されている場合があります (詳細はこちら)。