第 1 章 Quarkus CXF 安全指南
本章提供有关使用 Quarkus CXF 扩展时的安全性的信息。
1.1. 安全指南 复制链接链接已复制到粘贴板!
安全指南记录了 Quarkus CXF 的各种与安全方面:
1.1.1. SSL、TLS 和 HTTPS 复制链接链接已复制到粘贴板!
本节记录了与 SSL、TLS 和 HTTPS 相关的各种用例。
本节中使用的代码片段示例来自 Quarkus CXF 的源树中的 WS-SecurityPolicy 集成测试
1.1.1.1. 客户端 SSL 配置 复制链接链接已复制到粘贴板!
如果您的客户端要与客户端的操作系统不受信任的 SSL 证书的服务器通信,则需要为您的客户端设置自定义信任存储。
openssl 或 Java keytool 等工具通常用于创建和维护信任存储。
我们在两个工具在 Quarkus CXF 源树中都有示例:
准备好信任存储后,您需要将客户端配置为使用它。
1.1.1.1.1. 在 application.properties中设置客户端信任存储 复制链接链接已复制到粘贴板!
这是设置客户端信任存储的最简单方法。关键角色由以下属性扮演:
下面是一个示例:
application.properties
# Client side SSL
quarkus.cxf.client.hello.client-endpoint-url = https://localhost:${quarkus.http.test-ssl-port}/services/hello
quarkus.cxf.client.hello.service-interface = io.quarkiverse.cxf.it.security.policy.HelloService
quarkus.cxf.client.hello.trust-store-type = pkcs12
quarkus.cxf.client.hello.trust-store = client-truststore.pkcs12
quarkus.cxf.client.hello.trust-store-password = client-truststore-password
- 1
pkcs12和jks是两种常用的密钥存储格式。PKCS12 是默认的 Java 密钥存储格式,自 Java 9 起。我们建议使用 PKCS12 而不是 JKS,因为它提供更强大的加密算法,因此可扩展、标准化、语言中和广泛支持。- 2
- 引用的
client-truststore.pkcs12文件必须在 classpath 或文件系统中可用。
1.1.1.2. 服务器 SSL 配置 复制链接链接已复制到粘贴板!
要通过 HTTPS 协议将您的服务可用,您需要在第一个位置设置服务器密钥存储。服务器 SSL 配置由 Vert.x ( Quarkus 的 HTTP 层)驱动。{link-quarkus-docs-base}/http-reference#ssl[Quarkus HTTP 指南] 提供了关于配置选项的信息。
下面是一个基本示例:
application.properties
# Server side SSL
quarkus.tls.key-store.p12.path = localhost-keystore.pkcs12
quarkus.tls.key-store.p12.password = localhost-keystore-password
quarkus.tls.key-store.p12.alias = localhost
quarkus.tls.key-store.p12.alias-password = localhost-keystore-password
1.1.1.3. Mutual TLS (mTLS)身份验证 复制链接链接已复制到粘贴板!
到目前为止,我们解释了一个简单的或单端情况,其中只有服务器通过 SSL 证书证明其身份,客户端必须设置为信任该证书。通过让客户端使用相同的公钥加密方式证明其身份,从而进行双向 TLS 身份验证。
因此,对于 Mutual TLS (mTLS)身份验证,除了设置服务器密钥存储和客户端信任存储外,还需要在客户端上设置密钥存储和服务器端的信任存储。
创建和维护存储的工具相同,要使用的配置属性与简单 TLS 案例中使用的配置属性非常相似。
Quarkus CXF 源树中的 mTLS 集成测试 可以作为良好起点。
keystores 和 truststores 使用 openssl (或使用 Java keytool)创建。
以下是 application.properties 文件:
application.properties
# Server keystore for Simple TLS
quarkus.tls.localhost-pkcs12.key-store.p12.path = localhost-keystore.pkcs12
quarkus.tls.localhost-pkcs12.key-store.p12.password = localhost-keystore-password
quarkus.tls.localhost-pkcs12.key-store.p12.alias = localhost
quarkus.tls.localhost-pkcs12.key-store.p12.alias-password = localhost-keystore-password
# Server truststore for Mutual TLS
quarkus.tls.localhost-pkcs12.trust-store.p12.path = localhost-truststore.pkcs12
quarkus.tls.localhost-pkcs12.trust-store.p12.password = localhost-truststore-password
# Select localhost-pkcs12 as the TLS configuration for the HTTP server
quarkus.http.tls-configuration-name = localhost-pkcs12
# Do not allow any clients which do not prove their indentity through an SSL certificate
quarkus.http.ssl.client-auth = required
# CXF service
quarkus.cxf.endpoint."/mTls".implementor = io.quarkiverse.cxf.it.auth.mtls.MTlsHelloServiceImpl
# CXF client with a properly set certificate for mTLS
quarkus.cxf.client.mTls.client-endpoint-url = https://localhost:${quarkus.http.test-ssl-port}/services/mTls
quarkus.cxf.client.mTls.service-interface = io.quarkiverse.cxf.it.security.policy.HelloService
quarkus.cxf.client.mTls.key-store = target/classes/client-keystore.pkcs12
quarkus.cxf.client.mTls.key-store-type = pkcs12
quarkus.cxf.client.mTls.key-store-password = client-keystore-password
quarkus.cxf.client.mTls.key-password = client-keystore-password
quarkus.cxf.client.mTls.trust-store = target/classes/client-truststore.pkcs12
quarkus.cxf.client.mTls.trust-store-type = pkcs12
quarkus.cxf.client.mTls.trust-store-password = client-truststore-password
# Include the keystores in the native executable
quarkus.native.resources.includes = *.pkcs12,*.jks
1.1.1.4. 通过 WS-SecurityPolicy 强制 SSL 复制链接链接已复制到粘贴板!
客户端通过 HTTPS 连接的要求可以在策略中定义。
功能由 quarkus-cxf-rt-ws-security 扩展提供。
以下是策略文件的示例:
https-policy.xml
<?xml version="1.0" encoding="UTF-8"?>
<wsp:Policy wsp:Id="HttpsSecurityServicePolicy"
xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy"
xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<wsp:ExactlyOne>
<wsp:All>
<sp:TransportBinding>
<wsp:Policy>
<sp:TransportToken>
<wsp:Policy>
<sp:HttpsToken RequireClientCertificate="false" />
</wsp:Policy>
</sp:TransportToken>
<sp:IncludeTimestamp />
<sp:AlgorithmSuite>
<wsp:Policy>
<sp:Basic128 />
</wsp:Policy>
</sp:AlgorithmSuite>
</wsp:Policy>
</sp:TransportBinding>
</wsp:All>
</wsp:ExactlyOne>
</wsp:Policy>
该策略必须从服务端点接口(SEI)引用:
HttpsPolicyHelloService.java
package io.quarkiverse.cxf.it.security.policy;
import jakarta.jws.WebMethod;
import jakarta.jws.WebService;
import org.apache.cxf.annotations.Policy;
/**
* A service implementation with a transport policy set
*/
@WebService(serviceName = "HttpsPolicyHelloService")
@Policy(placement = Policy.Placement.BINDING, uri = "https-policy.xml")
public interface HttpsPolicyHelloService extends AbstractHelloService {
@WebMethod
@Override
public String hello(String text);
}
在这个版本中,通过 HTTP 发送的任何请求都会被 PolicyVerificationInInterceptor 拒绝:
ERROR [org.apa.cxf.ws.pol.PolicyVerificationInInterceptor] Inbound policy verification failed: These policy alternatives can not be satisfied:
{http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702}TransportBinding: TLS is not enabled
...
1.1.2. 认证和授权 复制链接链接已复制到粘贴板!
本节中显示的代码片段示例来自 Quarkus CXF 的源树中的客户端和服务器 集成测试。您可能需要将其用作可运行的示例。
1.1.2.1. 客户端 HTTP 基本身份验证 复制链接链接已复制到粘贴板!
使用以下由 quarkus-cxf 扩展提供的客户端配置选项,为 HTTP 基本身份验证传递用户名和密码:
下面是一个示例:
application.properties
quarkus.cxf.client.basicAuth.wsdl = http://localhost:${quarkus.http.test-port}/soap/basicAuth?wsdl
quarkus.cxf.client.basicAuth.client-endpoint-url = http://localhost:${quarkus.http.test-port}/soap/basicAuth
quarkus.cxf.client.basicAuth.username = bob
quarkus.cxf.client.basicAuth.password = bob234
1.1.2.1.1. 访问由基本身份验证保护的 WSDL 复制链接链接已复制到粘贴板!
默认情况下,由 Quarkus CXF 创建的客户端不会发送 Authorization 标头,除非您将 quarkus.cxf.client."client-name".secure-wsdl-access 设置为 true :
application.properties
quarkus.cxf.client.basicAuthSecureWsdl.wsdl = http://localhost:${quarkus.http.test-port}/soap/basicAuth?wsdl
quarkus.cxf.client.basicAuthSecureWsdl.client-endpoint-url = http://localhost:${quarkus.http.test-port}/soap/basicAuthSecureWsdl
quarkus.cxf.client.basicAuthSecureWsdl.username = bob
quarkus.cxf.client.basicAuthSecureWsdl.password = ${client-server.bob.password}
quarkus.cxf.client.basicAuthSecureWsdl.secure-wsdl-access = true
1.1.2.2. Mutual TLS (mTLS)身份验证 复制链接链接已复制到粘贴板!
请参阅 SSL、TLS 和 HTTPS 指南中的 Mutual TLS (mTLS)身份验证 部分。
1.1.2.3. 保护服务端点 复制链接链接已复制到粘贴板!
服务器端身份验证和授权由 {link-quarkus-docs-base}/security-overview[Quarkus Security] 驱动,特别是在涉及时
- {link-quarkus-docs-base}/security-authentication-mechanisms[Authentication mechanisms]
- {link-quarkus-docs-base}/security-identity-providers[Identity provider]
- {link-quarkus-docs-base}/security-authorize-web-endpoints-reference[Role-based access control (RBAC)]
我们的 客户端和服务器集成测试 中有一个基本示例。其关键部分是:
-
io.quarkus:quarkus-elytron-security-properties-file依赖项作为身份提供程序 启用基本身份验证,并在
application.properties中配置其角色:application.properties
quarkus.http.auth.basic = true quarkus.security.users.embedded.enabled = true quarkus.security.users.embedded.plain-text = true quarkus.security.users.embedded.users.alice = alice123 quarkus.security.users.embedded.roles.alice = admin quarkus.security.users.embedded.users.bob = bob234 quarkus.security.users.embedded.roles.bob = app-user-
基于角色的访问控制通过
@RolesAllowed注释提供:
BasicAuthHelloServiceImpl.java
package io.quarkiverse.cxf.it.auth.basic;
import jakarta.annotation.security.RolesAllowed;
import jakarta.jws.WebService;
import io.quarkiverse.cxf.it.HelloService;
@WebService(serviceName = "HelloService", targetNamespace = HelloService.NS)
@RolesAllowed("app-user")
public class BasicAuthHelloServiceImpl implements HelloService {
@Override
public String hello(String person) {
return "Hello " + person + "!";
}
}
1.1.3. WS-SecurityPolicy 强制的身份验证 复制链接链接已复制到粘贴板!
您可以通过 WS-SecurityPolicy 强制身份验证,而不是为客户端 和服务 进行 Mutual TLS 和基本身份验证。???
要通过 WS-SecurityPolicy 强制身份验证,请按照以下步骤操作:
- 将支持的令牌策略添加到 WSDL 合同中的端点。
-
在服务器端,实施身份验证回调处理程序,并将其与
application.properties或环境变量中的端点关联。从客户端接收的凭证由回调处理程序进行身份验证。 -
在客户端,通过
application.properties或环境变量中的配置来提供凭证。或者,您可以实施身份验证回调处理程序来传递凭证。
1.1.3.1. 指定身份验证策略 复制链接链接已复制到粘贴板!
如果要在服务端点中强制实施身份验证,请将 支持的令牌 策略断言与相关端点绑定相关联,并在其下指定一个或多个 令牌断言。
有几种不同类型的支持令牌策略断言,其 XML 元素以 SupportingTokens 结尾(例如: SupportingTokens、SignedSupportingTokens 等)。有关完整的列表,请参阅 WS-SecurityPolicy 规范的 Supporting Tokens 部分。
1.1.3.2. UsernameToken 策略断言示例 复制链接链接已复制到粘贴板!
本节中使用的代码片段示例来自 Quarkus CXF 的源树中 WS-SecurityPolicy 集成测试。您可能需要将其用作可运行的示例。
下表显示了一个策略示例,它要求包含在安全标头中包含 WS-Security UsernameToken (包含用户名/密码凭据)。
username-token-policy.xml
<?xml version="1.0" encoding="UTF-8"?>
<wsp:Policy
wsp:Id="UsernameTokenSecurityServicePolicy"
xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy"
xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702"
xmlns:sp13="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200802"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<wsp:ExactlyOne>
<wsp:All>
<sp:SupportingTokens>
<wsp:Policy>
<sp:UsernameToken
sp:IncludeToken="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702/IncludeToken/AlwaysToRecipient">
<wsp:Policy>
<sp:WssUsernameToken11 />
<sp13:Created />
<sp13:Nonce />
</wsp:Policy>
</sp:UsernameToken>
</wsp:Policy>
</sp:SupportingTokens>
</wsp:All>
</wsp:ExactlyOne>
</wsp:Policy>
您可以通过两种方式将此策略文件与服务端点关联:
引用服务端点接口(SEI)上的策略,如下所示:
UsernameTokenPolicyHelloService.java
@WebService(serviceName = "UsernameTokenPolicyHelloService") @Policy(placement = Policy.Placement.BINDING, uri = "username-token-policy.xml") public interface UsernameTokenPolicyHelloService extends AbstractHelloService { ... }-
在您的 WSDL 合同中包含 策略,并通过
PolicyReference元素引用该策略。
当您有策略就位时,在服务端点和客户端上配置凭证:
application.properties
# A service with a UsernameToken policy assertion
quarkus.cxf.endpoint."/helloUsernameToken".implementor = io.quarkiverse.cxf.it.security.policy.UsernameTokenPolicyHelloServiceImpl
quarkus.cxf.endpoint."/helloUsernameToken".security.callback-handler = #usernameTokenPasswordCallback
# These properties are used in UsernameTokenPasswordCallback
# and in the configuration of the helloUsernameToken below
wss.user = cxf-user
wss.password = secret
# A client with a UsernameToken policy assertion
quarkus.cxf.client.helloUsernameToken.client-endpoint-url = https://localhost:${quarkus.http.test-ssl-port}/services/helloUsernameToken
quarkus.cxf.client.helloUsernameToken.service-interface = io.quarkiverse.cxf.it.security.policy.UsernameTokenPolicyHelloService
quarkus.cxf.client.helloUsernameToken.security.username = ${wss.user}
quarkus.cxf.client.helloUsernameToken.security.password = ${wss.password}
在上面的列表中,usernameTokenPasswordCallback 是实施 javax.security.auth.callback.CallbackHandler 的 @jakarta.inject.Named bean 的名称。Quarkus CXF 将在 CDI 容器中 使用此名称 查找 bean。
以下是 bean 的示例实施:
UsernameTokenPasswordCallback.java
package io.quarkiverse.cxf.it.security.policy;
import java.io.IOException;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Named;
import org.apache.wss4j.common.ext.WSPasswordCallback;
import org.eclipse.microprofile.config.inject.ConfigProperty;
@ApplicationScoped
@Named("usernameTokenPasswordCallback") /* We refer to this bean by this name from application.properties */
public class UsernameTokenPasswordCallback implements CallbackHandler {
/* These two configuration properties are set in application.properties */
@ConfigProperty(name = "wss.password")
String password;
@ConfigProperty(name = "wss.user")
String user;
@Override
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
if (callbacks.length < 1) {
throw new IllegalStateException("Expected a " + WSPasswordCallback.class.getName()
+ " at possition 0 of callbacks. Got array of length " + callbacks.length);
}
if (!(callbacks[0] instanceof WSPasswordCallback)) {
throw new IllegalStateException(
"Expected a " + WSPasswordCallback.class.getName() + " at possition 0 of callbacks. Got an instance of "
+ callbacks[0].getClass().getName() + " at possition 0");
}
final WSPasswordCallback pc = (WSPasswordCallback) callbacks[0];
if (user.equals(pc.getIdentifier())) {
pc.setPassword(password);
} else {
throw new IllegalStateException("Unexpected user " + user);
}
}
}
要测试整个设置,您可以创建一个简单的 {link-quarkus-docs-base}/getting-started-testing[@QuarkusTest] :
UsernameTokenTest.java
package io.quarkiverse.cxf.it.security.policy;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import io.quarkiverse.cxf.annotation.CXFClient;
import io.quarkus.test.junit.QuarkusTest;
@QuarkusTest
public class UsernameTokenTest {
@CXFClient("helloUsernameToken")
UsernameTokenPolicyHelloService helloUsernameToken;
@Test
void helloUsernameToken() {
Assertions.assertThat(helloUsernameToken.hello("CXF")).isEqualTo("Hello CXF from UsernameToken!");
}
}
通过 mvn test -Dtest=UsernameTokenTest 运行测试时,您应该会看到一个 SOAP 消息,其中包含 Username 和 Password :
UsernameTokenTest 的日志输出
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Header>
<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" soap:mustUnderstand="1">
<wsse:UsernameToken xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="UsernameToken-bac4f255-147e-42a4-aeec-e0a3f5cd3587">
<wsse:Username>cxf-user</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">secret</wsse:Password>
<wsse:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">3uX15dZT08jRWFWxyWmfhg==</wsse:Nonce>
<wsu:Created>2024-10-02T17:32:10.497Z</wsu:Created>
</wsse:UsernameToken>
</wsse:Security>
</soap:Header>
<soap:Body>
<ns2:hello xmlns:ns2="http://policy.security.it.cxf.quarkiverse.io/">
<arg0>CXF</arg0>
</ns2:hello>
</soap:Body>
</soap:Envelope>
1.1.3.3. SAML v1 和 v2 策略断言示例 复制链接链接已复制到粘贴板!
WS-SecurityPolicy 集成测试 还包含与 SAML v1 和 SAML v2 断言示例类似。