Menerapkan keamanan WS-Security secara nyata: menyusun WS-SecurityPolicy yang terikat ke WSDL dan mengimplementasikannya dengan Apache WSS4J 4.0.1 dan interceptor CXF untuk signing, encryption, dan UsernameToken password digest beserta alur sertifikat X.509.

Setelah di episode 14 kita memahami konsep WS-Security, sekarang saatnya mengimplementasikannya. Memakai WS-Security secara manual (menyusun header Security dengan tangan) itu rawan dan tidak konsisten — praktik profesional adalah mendeklarasikan WS-SecurityPolicy dan membiarkan framework menerapkannya.
Mengapa penting? Policy adalah kontrak keamanan yang machine-readable: ia memberi tahu tool client apa yang harus dilakukan (token apa, elemen apa yang di-sign, apa yang di-encrypt) tanpa menebak-nebak. Apache WSS4J — pustaka WS-Security untuk Java — dan interceptor CXF adalah cara standar industri menerapkannya. Di episode ini kita membangun keduanya dari nol.
WS-SecurityPolicy memperluas WS-Policy (episode 10) dengan assertion keamanan: token apa yang dipakai, apa yang dilindungi, dan bagaimana proteksinya. Contoh policy untuk service yang menuntut UsernameToken + sign body + encrypt body:
<wsp:Policy wsu:Id="BankSecurityPolicy"
xmlns:wsp="http://www.w3.org/ns/ws-policy"
xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702"
xmlns:sp13="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/1.3">
<wsp:ExactlyOne>
<wsp:All>
<sp:TransportBinding>
<sp:TransportToken>
<sp:HttpsToken/>
</sp:TransportToken>
</sp:TransportBinding>
<sp:SignedEncryptedSupportingTokens>
<sp:Policy>
<sp:UsernameToken sp:IncludeToken=
"http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702/IncludeToken/AlwaysToRecipient">
<wsp:Policy>
<sp:WssUsernameToken11/>
</wsp:Policy>
</sp:UsernameToken>
</sp:Policy>
</sp:SignedEncryptedSupportingTokens>
<sp:SignedParts>
<sp:Body/>
</sp:SignedParts>
<sp:EncryptedParts>
<sp:Body/>
</sp:EncryptedParts>
</wsp:All>
</wsp:ExactlyOne>
</wsp:Policy>Maknanya: transport wajib HTTPS; client mengirim UsernameToken (WSS 1.1); body harus di-sign dan di-encrypt. Client yang menghasilkan request dari policy ini otomatis melakukan semuanya.
Note
Mulailah dari policy yang paling sederhana yang memenuhi kebutuhan, lalu perkuat bertahap: (1) HTTPS saja, (2) + UsernameToken, (3) + sign body, (4) + encrypt body. Policy yang terlalu kompleks dari awal membuat debugging keamanan terasa menyakitkan.
Apache WSS4J 4.0.1 adalah pustaka WS-Security untuk Java — dan ia terintegrasi erat dengan CXF lewat interceptor: WSS4JOutInterceptor di sisi pengirim dan WSS4JInInterceptor di sisi penerima.
<dependencies>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-ws-security</artifactId>
<version>4.2.2</version>
</dependency>
</dependencies>WSS4J datang sebagai bagian dari modul cxf-rt-ws-security (versi WSS4J 4.0.1 dibundel di dalamnya).
Client dikonfigurasi dengan actions: timestamp, UsernameToken, sign, dan encrypt:
<bean id="clientWss4j" class="org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor">
<property name="properties">
<map>
<entry key="action" value="Timestamp UsernameToken Signature Encrypt"/>
<entry key="user" value="integrasi-mitra"/>
<entry key="passwordType" value="PasswordDigest"/>
<entry key="passwordCallbackClass"
value="id.devnull.soap.ClientPasswordCallback"/>
<entry key="signatureUser" value="myclientkey"/>
<entry key="signaturePropFile" value="client-sign.properties"/>
<entry key="encryptionUser" value="myservicekey"/>
<entry key="encryptionPropFile" value="client-enc.properties"/>
<entry key="signatureKeyIdentifier" value="DirectReference"/>
<entry key="encryptionKeyIdentifier" value="X509KeyIdentifier"/>
</map>
</property>
</bean>Alurnya: client membuat timestamp, menambah UsernameToken (digest), menandatangani body dengan kunci privat myclientkey, lalu mengenkripsi body dengan kunci publik myservicekey.
WSS4J tidak pernah menyimpan password — ia memintanya lewat callback:
package id.devnull.soap;
import java.io.IOException;
import javax.security.auth.callback.*;
import org.apache.wss4j.common.ext.WSPasswordCallback;
public class ClientPasswordCallback implements CallbackHandler {
@Override
public void handle(Callback[] callbacks) throws IOException {
for (Callback cb : callbacks) {
WSPasswordCallback pc = (WSPasswordCallback) cb;
if ("integrasi-mitra".equals(pc.getIdentifier())) {
pc.setPassword(System.getenv("MITRA_PASSWORD"));
}
}
}
}Perhatikan: password diambil dari environment variable, bukan hardcode — pola yang akan kita pertegas di episode 16.
Server memakai WSS4JInInterceptor untuk memvalidasi token, signature, dan dekripsi:
<bean id="serverWss4j" class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor">
<property name="properties">
<map>
<entry key="action" value="Timestamp UsernameToken Signature Encrypt"/>
<entry key="passwordCallbackClass"
value="id.devnull.soap.ServerPasswordCallback"/>
<entry key="signaturePropFile" value="server-sign.properties"/>
<entry key="decryptionPropFile" value="server-dec.properties"/>
</map>
</property>
</bean>Server perlu (a) memverifikasi password digest terhadap store mitra, (b) memverifikasi signature dengan sertifikat publik client, dan (c) mendekripsi body dengan kunci privat service. Jika salah satu gagal, CXF mengembalikan SOAP Fault — pesan tidak pernah sampai ke kode bisnis.
Untuk signing/encryption, siapkan pasangan keystore. Alur minimal untuk lab:
# Keystore service (kunci privat + sertifikat service)
keytool -genkeypair -alias servicekey -keyalg RSA -keysize 2048 \
-keystore service.jks -storepass changeit
# Keystore client (kunci privat + sertifikat client)
keytool -genkeypair -alias clientkey -keyalg RSA -keysize 2048 \
-keystore client.jks -storepass changeit
# Ekspor dan tukar sertifikat
keytool -exportcert -alias servicekey -keystore service.jks \
-rfc -file service.crt
keytool -exportcert -alias clientkey -keystore client.jks \
-rfc -file client.crtWarning
Keystore yang dibuat keytool hanya untuk lab. Di produksi, kunci harus diterbitkan oleh CA yang dipercaya kedua pihak, dikelola dengan kontrol akses (hanya service yang bisa membaca kunci privat), dan dirotasi berkala — kunci yang sama dipakai selamanya adalah kewajiban yang menunggu kebocoran.
EncryptedParts vs SignedParts — kalau policy butuh keduanya tapi config hanya sign, request tetap lolos atau gagal tergantung validasi; pastikan action konsisten di client dan server.Inti yang harus dibawa pulang:
WSS4JOutInterceptor (client) dan WSS4JInInterceptor (server) di CXF.Di episode 16 selanjutnya kita akan membahas authentication & authorization — perbandingan HTTP Basic/Digest, UsernameToken, mutual TLS, dan integrasi SSO dengan SAML/WS-Trust, plus RBAC di level operasi dan pengelolaan credentials dengan vault. Sampai jumpa di episode 16!