3.2. 使用 Elytron 进行身份传播和转发

3.2.1. 为远程调用传播安全识别信息

JBoss EAP 7.1 引入了轻松配置服务器和应用的功能,以将安全身份从客户端传播到服务器以重新移动调用。您还可以配置服务器组件,以便在给定用户的安全身份内运行。

本节中的示例演示了如何转发安全身份凭证。它将客户端的安全身份和 Jakarta Enterprise Beans 传播到远程 Jakarta Enterprise Bean。它返回一个字符串,其中包含名为远程 Jakarta Enterprise Beans 的 Principal 名称以及用户的授权角色信息。示例由以下组件组成。

  • 安全的 Jakarta Enterprise Beans 包含单一方法,可供所有用户访问,后者返回有关调用者的授权信息。
  • 含有单一方法的中间 Jakarta Enterprise Beans。它利用远程连接,并调用受保护 Jakarta Enterprise Beans 的方法。
  • 调用中间 Jakarta Enterprise Beans 的远程独立客户端应用。
  • META-INF/wildfly-config.xml 文件,其中包含用于身份验证的身份信息。

您必须首先通过 配置服务器来启用安全身份传播。接下来 查看使用 WildFlyInitialContext onnectionFactoryy 查找并调用远程 Jakarta Enterprise Bean 的示例应用程序代码

为安全传播配置服务器
  1. ejb3 子系统配置为使用 Elytron ApplicationDomain

    /subsystem=ejb3/application-security-domain=quickstart-domain:add(security-domain=ApplicationDomain)

    这会将以下 application-security-domain 配置添加到 ejb3 子系统:

    <subsystem xmlns="urn:jboss:domain:ejb3:5.0">
        ....
        <application-security-domains>
            <application-security-domain name="quickstart-domain" security-domain="ApplicationDomain"/>
        </application-security-domains>
    </subsystem>
  2. 添加 PLAIN 身份验证配置,以发送纯文本用户名和密码,以及用于出站连接的验证上下文。有关支持 身份传播的机制列表,请参阅支持安全 身份传播的机制列表。

    /subsystem=elytron/authentication-configuration=ejb-outbound-configuration:add(security-domain=ApplicationDomain,sasl-mechanism-selector="PLAIN")
    /subsystem=elytron/authentication-context=ejb-outbound-context:add(match-rules=[{authentication-configuration=ejb-outbound-configuration}])

    这会在 elytron 子系统中添加以下 authentication-client 配置。

    <subsystem xmlns="urn:wildfly:elytron:4.0" final-providers="combined-providers" disallowed-providers="OracleUcrypto">
        <authentication-client>
            <authentication-configuration name="ejb-outbound-configuration" security-domain="ApplicationDomain" sasl-mechanism-selector="PLAIN"/>
            <authentication-context name="ejb-outbound-context">
                <match-rule authentication-configuration="ejb-outbound-configuration"/>
            </authentication-context>
        </authentication-client>
        ....
    </subsystem>
  3. 将远程目的地出站套接字绑定添加到 standard-sockets 套接字绑定组。

    /socket-binding-group=standard-sockets/remote-destination-outbound-socket-binding=ejb-outbound:add(host=localhost,port=8080)

    这会添加以下 ejb-outbound 出站套接字绑定到 standard-sockets 套接字绑定组。

    <socket-binding-group name="standard-sockets" default-interface="public" port-offset="${jboss.socket.binding.port-offset:0}">
        ....
        <outbound-socket-binding name="ejb-outbound">
            <remote-destination host="localhost" port="8080"/>
        </outbound-socket-binding>
    </socket-binding-group>
  4. 添加远程出站连接,并在 HTTP 连接器中设置 SASL 身份验证工厂。

    /subsystem=remoting/remote-outbound-connection=ejb-outbound-connection:add(outbound-socket-binding-ref=ejb-outbound, authentication-context=ejb-outbound-context)
    /subsystem=remoting/http-connector=http-remoting-connector:write-attribute(name=sasl-authentication-factory,value=application-sasl-authentication)

    这会将以下 http-remoting-connectorejb-outbound-connection 配置添加到 remoting 子系统。

    <subsystem xmlns="urn:jboss:domain:remoting:4.0">
        ....
        <http-connector name="http-remoting-connector" connector-ref="default" security-realm="ApplicationRealm" sasl-authentication-factory="application-sasl-authentication"/>
        <outbound-connections>
            <remote-outbound-connection name="ejb-outbound-connection" outbound-socket-binding-ref="ejb-outbound" authentication-context="ejb-outbound-context"/>
        </outbound-connections>
    </subsystem>
  5. 将 Elytron SASL 身份验证配置为使用 PLAIN 机制。

    /subsystem=elytron/sasl-authentication-factory=application-sasl-authentication:write-attribute(name=mechanism-configurations,value=[{mechanism-name=PLAIN},{mechanism-name=JBOSS-LOCAL-USER,realm-mapper=local},{mechanism-name=DIGEST-MD5,mechanism-realm-configurations=[{realm-name=ApplicationRealm}]}])

    这会将以下 application-sasl-authentication 配置添加到 elytron 子系统:

    <subsystem xmlns="urn:wildfly:elytron:4.0" final-providers="combined-providers" disallowed-providers="OracleUcrypto">
        ....
        <sasl>
          ....
          <sasl-authentication-factory name="application-sasl-authentication" sasl-server-factory="configured" security-domain="ApplicationDomain">
              <mechanism-configuration>
                  <mechanism mechanism-name="PLAIN"/>
                  <mechanism mechanism-name="JBOSS-LOCAL-USER" realm-mapper="local"/>
                  <mechanism mechanism-name="DIGEST-MD5">
                      <mechanism-realm realm-name="ApplicationRealm"/>
                  </mechanism>
              </mechanism-configuration>
          </sasl-authentication-factory>
      </sasl>
      ....
    </subsystem>

服务器现在已配置为启用以下示例应用程序的安全性传播。

查看应用程序代码示例(pagates a Security Identity)

在服务器配置中启用了安全身份传播后,Jakarta Enterprise Beans 客户端应用可以使用 WildFlyInitialContextonnectionFactory y 查找并调用 Jakarta Enterprise Beans 代理。Jakarta Enterprise Beans 调用为在客户端示例中身份验证的用户,如下所示。以下缩写代码示例来自 JBoss EAP 7.4 附带的 ejb-security-context-propagation Quickstart。有关安全身份传播的完整工作示例,请参阅快速入门。

要将 Jakarta Enterprise Beans 作为其他用户调用,您可以在上下文属性中设置 Context.SECURITY_PRINCIPALContext.SECURITY_CREDENTIALS

示例:远程客户端

public class RemoteClient {

    public static void main(String[] args) throws Exception {
        // invoke the intermediate bean using the identity configured in wildfly-config.xml
        invokeIntermediateBean();

        // now lets programmatically setup an authentication context to switch users before invoking the intermediate bean
        AuthenticationConfiguration superUser = AuthenticationConfiguration.empty().setSaslMechanismSelector(SaslMechanismSelector.NONE.addMechanism("PLAIN")).
                useName("superUser").usePassword("superPwd1!");
        final AuthenticationContext authCtx = AuthenticationContext.empty().
                with(MatchRule.ALL, superUser);

        AuthenticationContext.getContextManager().setThreadDefault(authCtx);
        invokeIntermediateBean();
    }

    private static void invokeIntermediateBean() throws Exception {
        final Hashtable<String, String> jndiProperties = new Hashtable<>();
        jndiProperties.put(Context.INITIAL_CONTEXT_FACTORY, "org.wildfly.naming.client.WildFlyInitialContextFactory");
        jndiProperties.put(Context.PROVIDER_URL, "remote+http://localhost:8080");
        final Context context = new InitialContext(jndiProperties);
        IntermediateEJBRemote intermediate = (IntermediateEJBRemote) context.lookup("ejb:/ejb-security-context-propagation/IntermediateEJB!"
                + IntermediateEJBRemote.class.getName());
        // Call the intermediate EJB
        System.out.println(intermediate.makeRemoteCalls());
    }
}

示例:Intermediate Jakarta Enterprise Beans

@Stateless
@Remote(IntermediateEJBRemote.class)
@SecurityDomain("quickstart-domain")
@PermitAll
public class IntermediateEJB implements IntermediateEJBRemote {

    @EJB(lookup="ejb:/ejb-security-context-propagation/SecuredEJB!org.jboss.as.quickstarts.ejb_security_context_propagation.SecuredEJBRemote")
    private SecuredEJBRemote remote;

    @Resource
    private EJBContext context;

    public String makeRemoteCalls() {
        try {
            StringBuilder sb = new StringBuilder("** ").
                    append(context.getCallerPrincipal()).
                    append(" * * \n\n");
            sb.append("Remote Security Information: ").
                    append(remote.getSecurityInformation()).
                    append("\n");

            return sb.toString();
        } catch (Exception e) {
            if (e instanceof RuntimeException) {
                throw (RuntimeException) e;
            }
            throw new RuntimeException("Teasting failed.", e);
        }
    }
}

示例:Example: Secured Jakarta Enterprise Beans

@Stateless
@Remote(SecuredEJBRemote.class)
@SecurityDomain("quickstart-domain")
public class SecuredEJB implements SecuredEJBRemote {

    @Resource
    private SessionContext context;

    @PermitAll
    public String getSecurityInformation() {
        StringBuilder sb = new StringBuilder("[");
        sb.append("Principal=[").
                append(context.getCallerPrincipal().getName()).
                append("], ");
        userInRole("guest", sb).append(", ");
        userInRole("user", sb).append(", ");
        userInRole("admin", sb).append("]");
        return sb.toString();
    }
}

示例: wildfly-config.xml 文件

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <authentication-client xmlns="urn:elytron:client:1.2">
        <authentication-rules>
            <rule use-configuration="default"/>
        </authentication-rules>
        <authentication-configurations>
            <configuration name="default">
                <set-user-name name="quickstartUser"/>
                <credentials>
                    <clear-password password="quickstartPwd1!"/>
                </credentials>
                <sasl-mechanism-selector selector="PLAIN"/>
                <providers>
                    <use-service-loader />
                </providers>
            </configuration>
        </authentication-configurations>
    </authentication-client>
</configuration>

3.2.2. 使用授权转发模式

除了凭证转发外,Elytron 还支持在对等点之间使用身份。在以下情况下很有用。

  • 要求是,您无法通过线路来发送密码。
  • 身份验证类型不支持凭证转发
  • 环境需要限制哪些系统可以接收传播的请求。

要使用授权转发,您首先在 转发服务器上配置身份验证客户端,然后将 接收服务器配置为接受和处理授权

在转发服务器上配置身份验证客户端

要启用授权转发,您必须在转发服务器配置中配置身份验证客户端配置。

以下管理 CLI 命令创建了默认的身份验证客户端配置,以启用身份验证转发。如果需要,您可以配置更加高级的基于规则的选择。

示例:管理 CLI 命令以创建身份验证客户端配置

/subsystem=elytron/authentication-configuration=forwardit:add(authentication-name=theserver1,security-domain=ApplicationDomain,realm=ApplicationRealm,forwarding-mode=authorization,credential-reference={clear-text=thereallysecretpassword})
/subsystem=elytron/authentication-context=forwardctx:add(match-rules=[{authentication-configuration=forwardit,match-no-user=true}])

这些命令将以下 authentication-configurationauthentication-context 配置添加到 elytron 子系统中。

示例:身份验证客户端配置

<authentication-client>
    <authentication-configuration name="forwardit" authentication-name="theserver1" security-domain="ApplicationDomain" forwarding-mode="authorization" realm="ApplicationRealm">
        <credential-reference clear-text="thereallysecretpassword"/>
    </authentication-configuration>
    <authentication-context name="forwardctx">
        <match-rule match-no-user="true" authentication-configuration="forwardit"/>
    </authentication-context>
</authentication-client>

当转发服务器联系接收服务器时,不使用默认的基于身份验证的用户名和凭证,它使用预定义的服务器登录名 theserver1 来建立信任关系。

在接收服务器上配置授权转发

要使转发成功完成,需要使用与转发服务器传递的用户身份配置接收服务器配置。在这种情况下,您必须在接收服务器上配置名为 theserver1 的用户并使用正确的凭据。

您还必须在 elytron 子系统中配置"RunAs"权限映射,以允许从转发服务器传递的 server1 身份的身份切换。有关权限映射的更多信息,请参阅如何为 JBoss EAP 配置服务器安全性中的创建一个 Elytron 权限映射程序

下面的命令添加一个名为 auth-forwarding-permission-mappersimple-permission-mapper,它包括以下配置。

  • 用户 anonymous 的权限映射。此用户没有权限,这可以防止匿名用户可以登录。
  • 用户 theserver1 的权限映射。此用户被分配了 *RunAsPrincipalPermission 权限,这可让此用户全局权限作为任何身份运行。如果您愿意,您可以将权限限制为特定的身份。
  • 所有其他用户的权限映射。

示例:管理 CLI 命令创建 Simple Permission Mapper

/subsystem=elytron/permission-set=run-as-principal-permission:add(permissions=[{class-name="org.wildfly.security.auth.permission.RunAsPrincipalPermission",target-name="*"}])

/subsystem=elytron/simple-permission-mapper=auth-forwarding-permission-mapper:add(permission-mappings=[{principals=["anonymous"]},{principals=["theserver1"],permission-sets=[{permission-set=login-permission},{permission-set=default-permissions},{permission-set=run-as-principal-permission}]},{match-all=true,permission-sets=[{permission-set=login-permission},{permission-set=default-permissions}]}]

此命令将以下 simple-permission-mapper 配置添加到 elytron 子系统:

示例:简单权限映射配置

<mappers>
    <simple-permission-mapper name="auth-forwarding-permission-mapper">
        <permission-mapping>
            <principal name="anonymous"/>
            <!-- No permissions: Deny any permission to anonymous! -->
        </permission-mapping>
        <permission-mapping>
            <principal name="theserver1"/>
            <permission-set name="login-permission"/>
            <permission-set name="default-permissions"/>
            <permission-set name="run-as-principal-permission"/>
        </permission-mapping>
        <permission-mapping match-all="true">
            <permission-set name="login-permission"/>
            <permission-set name="default-permissions"/>
        </permission-mapping>
    </simple-permission-mapper>
</mappers>
<permission-sets>
    <permission-set name="login-permission">
        <permission class-name="org.wildfly.security.auth.permission.LoginPermission"/>
    </permission-set>
    <permission-set name="default-permissions">
        <permission class-name="org.wildfly.extension.batch.jberet.deployment.BatchPermission" module="org.wildfly.extension.batch.jberet" target-name="*"/>
        <permission class-name="org.wildfly.transaction.client.RemoteTransactionPermission" module="org.wildfly.transaction.client"/>
        <permission class-name="org.jboss.ejb.client.RemoteEJBPermission" module="org.jboss.ejb-client"/>
    </permission-set>
    <permission-set name="run-as-principal-permission">
        <permission class-name="org.wildfly.security.auth.permission.RunAsPrincipalPermission" target-name="*"/>
    </permission-set>
</permission-sets>

注意

默认配置中已存在 login-permissiondefault-permissions 权限集。

在转发授权后使用主体转换器时,这些转换程序都会在验证和授权主体中应用。

3.2.3. 创建 case-principal-transformer 以更改主体用户名的问题单字符

elytron 子系统包括 case-principal-transformer 主体转换程序。您可以使用这个主体转换器将主体的用户名更改为大写或小写字符。

case-principal-transformer 主体转换器包括默认设置为 trueupper-case 属性。

要演示 case-principal-transformer 的用例,请考虑您使用身份验证机制将主体映射到安全域。realm mapper 操控映射的主体,以识别安全域并加载其身份之一。身份验证机制会将身份传递到 post-realm 映射阶段和最终主体转换阶段。随后,验证机制会验证身份以进行身份验证。您可以使用 case-principal-transformer 主体转换器来转换映射主体的字符大小写格式。

该流程示例中在安全域上下文中使用 case-principal-transformer。您还可以在以下验证策略中使用内联主体转换器:

  • http-authentication-factory
  • sasl-authentication-factory
  • ssl-context
  • aggregate-realm

流程

  1. case-principal-transformer 添加到 elytron 子系统,然后选择用户名的字符大小写。

    1. 要将转换器的用户名更改为大写字符,不要更改默认的 大写 属性值。

      显示添加到 elytron 子系统中的 <transformer_name > 示例,并定义了默认的 大写 属性设置:

      /subsystem=elytron/case-principal-transformer=<transformer_name>:add(upper-case="true")

      或者,您可以截断命令语法以使用默认的 大写 属性值:

      /subsystem=elytron/case-principal-transformer=<transformer_name>:add()
    2. 要将转换器的用户名更改为小写字符,请将 upper-case 属性设置为 false

      显示添加到 elytron 子系统中的 <transformer_name> 示例,并将 upper-case 属性设置为 false

      /subsystem=elytron/case-principal-transformer=<transformer_name>:add(upper-case="false")

  2. 可选:使用 elytron 子系统配置主体转换器。以下示例配置了一个主体转换器到由 elytron 子系统提供的默认 ApplicationDomain 配置。Elytron 将默认的 ApplicationDomain 配置应用到 pre-realm-principal-transformer

    /subsystem=elytron/security-domain=ApplicationDomain:write-attribute(name=pre-realm-principal-transformer,value=<transformer_name>)
    注意

    您可以将 post-realm-principal-transformer 配置为在域映射程序标识了安全域后使用 ApplicationDomain 配置。

其他资源

3.2.4. 检索安全身份凭证

在某些情况下,您可能需要检索用于传出调用的身份凭据,例如,由 HTTP 客户端使用。以下示例演示了如何以编程方式检索安全凭证。

import org.wildfly.security.auth.server.IdentityCredentials;
import org.wildfly.security.auth.server.SecurityDomain;
import org.wildfly.security.auth.server.SecurityIdentity;
import org.wildfly.security.credential.PasswordCredential;
import org.wildfly.security.password.interfaces.ClearPassword;

SecurityIdentity securityIdentity = null;
ClearPassword password = null;

// Obtain the SecurityDomain for the current deployment.
// The calling code requires the
//     org.wildfly.security.permission.ElytronPermission("getSecurityDomain") permission
//     if running with a security manager.
SecurityDomain securityDomain = SecurityDomain.getCurrent();
if (securityDomain != null) {
    // Obtain the current security identity from the security domain.
    // This always returns an identity, but it could be the representation
    //     of the anonymous identity if no authenticated identity is available.
    securityIdentity = securityDomain.getCurrentSecurityIdentity();
    // The private credentials can be accessed to obtain any credentials delegated to the identity.
    // The calling code requires the
    //     org.wildfly.security.permission.ElytronPermission("getPrivateCredentials")
    //     permission if running with a security manager.
    IdentityCredentials credentials = securityIdentity.getPrivateCredentials();
    if (credentials.contains(PasswordCredential.class)) {
        password = credentials.getCredential(PasswordCredential.class).getPassword(ClearPassword.class);
    }
}

3.2.5. 支持安全身份传播机制

以下 SASL 机制支持传播安全身份:

  • PLAIN
  • OAUTHBEARER
  • GSSAPI
  • GS2-KRB5

以下 HTTP 机制支持传播安全身份:

  • FORM 1
  • BASIC
  • BEARER_TOKEN
  • SPNEGO

1 FORM 身份验证不会由 Web 浏览器自动处理。因此,无法在 HA 集群中运行的使用 FORM 身份验证的 Web 应用程序传播。其他机制,如 BASICSPNEGO,在 HA 集群环境中支持身份传播。