How do you add WS-Security username/password authentication to a Spring WS client or endpoint with Wss4jSecurityInterceptor?
answer
- wss4j2 package (not deprecated wss4j)
- two directions: securement (out) / validation (in)
- securementActions='UsernameToken' + username/password/passwordType
- PW_DIGEST hashes nonce+created+password; PW_TEXT needs TLS
- validationCallbackHandler supplies known password
basics
~20 sAdd a Wss4jSecurityInterceptor to the client's or endpoint's interceptor chain. For outgoing messages set securementActions='UsernameToken' with a username, password, and password type (Digest or Text); for incoming ones set validationActions='UsernameToken' plus a callback handler that checks the credentials.
solid answer
~40 sWS-Security puts security tokens inside the SOAP header rather than at the transport layer. Wss4jSecurityInterceptor is Spring WS's bridge to Apache WSS4J. It has two directions: securement (outgoing) and validation (incoming). For UsernameToken auth on a client you set setSecurementActions("UsernameToken"), setSecurementUsername, setSecurementPassword, and setSecurementPasswordType (PW_DIGEST — a hashed nonce+created+password digest — or PW_TEXT — plaintext, only safe over TLS). On the server you set setValidationActions("UsernameToken") and a setValidationCallbackHandler (e.g. a SimplePasswordValidationCallbackHandler or SpringSecurityPasswordValidationCallbackHandler) that resolves/verifies the password. You register the interceptor via WebServiceTemplate.setInterceptors on the client or by implementing WsConfigurerAdapter.addInterceptors / EndpointInterceptor on the server. Multiple actions can be chained in one space-separated string.
code
java · 31 lines// CLIENT: send a UsernameToken with digest password
@Bean
public Wss4jSecurityInterceptor securityInterceptor() {
Wss4jSecurityInterceptor interceptor = new Wss4jSecurityInterceptor();
interceptor.setSecurementActions("UsernameToken Timestamp");
interceptor.setSecurementUsername("alice");
interceptor.setSecurementPassword("s3cret");
interceptor.setSecurementPasswordType(WSConstants.PW_DIGEST); // nonce+created+pwd digest
return interceptor;
}
@Bean
public WebServiceTemplate webServiceTemplate(Jaxb2Marshaller m, Wss4jSecurityInterceptor sec) {
WebServiceTemplate t = new WebServiceTemplate();
t.setMarshaller(m);
t.setUnmarshaller(m);
t.setDefaultUri("https://partner.example.com/service");
t.setInterceptors(new ClientInterceptor[]{ sec });
return t;
}
// SERVER: validate the incoming UsernameToken
@Bean
public Wss4jSecurityInterceptor serverSecurity() {
Wss4jSecurityInterceptor i = new Wss4jSecurityInterceptor();
i.setValidationActions("UsernameToken Timestamp");
SimplePasswordValidationCallbackHandler handler = new SimplePasswordValidationCallbackHandler();
handler.setUsersMap(Map.of("alice", "s3cret")); // known plaintext for digest recompute
i.setValidationCallbackHandler(handler);
return i;
}go deeper
Know WS-Security adds credentials inside the SOAP header and that Wss4jSecurityInterceptor handles it.
Explain securement vs validation directions and the basic UsernameToken client/server config.
Discuss PW_DIGEST vs PW_TEXT trade-offs, callback handlers, Timestamp/replay, and interceptor registration on client vs endpoint.
Weigh message-level vs transport security, digest's recoverable-password tension, WSS4J version/package migration, and policy-driven action chaining.
**Why WS-Security exists.** Transport security (TLS/HTTPS) protects a message only *while in transit between two hops*. If a SOAP message passes through intermediaries, or you need the security to travel *with* the message (end-to-end, message-level), you use **WS-Security (WSS)** — an OASIS standard that places security tokens, signatures, and encrypted data inside the SOAP `<Header>` under a `<wsse:Security>` element. This leaf covers only the *wiring* of WS-Security in Spring; the underlying crypto/TLS concepts belong to the Security category. **The interceptor.** `org.springframework.ws.soap.security.wss4j2.Wss4jSecurityInterceptor` (the `wss4j2` package is the current WSS4J 2.x version; the older `wss4j` one is deprecated) is an `EndpointInterceptor`/`ClientInterceptor` that delegates to **Apache WSS4J**, the reference library for WS-Security. It operates in **two independent directions**, and you configure each: - **Securement** = what it does to *outgoing* messages (add tokens/signatures/encryption). Configured with `setSecurement*` properties. - **Validation** = what it checks on *incoming* messages. Configured with `setValidation*` properties. On a **client**, outgoing = requests (securement) and incoming = responses (validation). On a **server endpoint** it's the reverse: incoming = requests (validation), outgoing = responses (securement). Same class, symmetric config. **UsernameToken authentication (the most common WSS need).** - *Client / securement side:* - `setSecurementActions("UsernameToken")` — the space-separated list of actions to perform. - `setSecurementUsername("alice")`, `setSecurementPassword("secret")`. - `setSecurementPasswordType(WSConstants.PW_DIGEST)` or `PW_TEXT`. - **PW_DIGEST**: the wire carries `Base64(SHA-1(nonce + created + password))` plus the nonce and created timestamp — the plaintext password never travels, and the nonce+timestamp guard against replay. Requires the server to know the plaintext password to recompute the digest. - **PW_TEXT**: the password is sent in clear text inside the header — only acceptable over TLS. - *Server / validation side:* - `setValidationActions("UsernameToken")`. - `setValidationCallbackHandler(handler)` — a JAAS-style `CallbackHandler` that WSS4J invokes with a `WSPasswordCallback`. For digest, the handler must **supply the known plaintext password** so WSS4J can recompute and compare the digest; for text, it can compare directly. Spring ships `SimplePasswordValidationCallbackHandler` (in-memory user->password map) and `SpringSecurityPasswordValidationCallbackHandler` (delegates to a Spring Security `UserDetailsService`/`AuthenticationManager`). **Registering it.** - Client: `webServiceTemplate.setInterceptors(new ClientInterceptor[]{ wss4jInterceptor });`. - Server: implement `WsConfigurerAdapter` and override `addInterceptors(List<EndpointInterceptor>)` to add it (optionally with a `PayloadRootSmartSoapEndpointInterceptor` to scope it to certain endpoints). **Chaining actions.** The action strings are space-separated and *ordered*, e.g. `"UsernameToken Timestamp Signature Encrypt"`. WSS4J applies them in that order on securement and expects them on validation. This is how you combine authentication with a timestamp (for freshness/replay defense) or with signing/encryption. **Gotchas / edge cases:** - **Digest requires plaintext on the server.** Because the server must recompute the digest, it cannot store only a salted hash of the password — a real design tension. PW_TEXT + TLS is often chosen instead. - **Timestamp/replay.** UsernameToken alone (text) has no replay protection; add `Timestamp` and consider WSS4J's nonce/timestamp caching. `setSecurementUsernameTokenElements`/nonce options control created+nonce inclusion. - **Clock skew.** With `Timestamp` validation, `setValidationTimeToLive` and server/client clock differences can cause 'message expired' faults. - **Wrong package.** Using the deprecated `...security.wss4j.Wss4jSecurityInterceptor` instead of `...wss4j2...` pulls in WSS4J 1.x — mismatches are a classpath headache. - **Validation failures** produce a WS-Security SOAP fault, surfaced client-side as a `SoapFaultClientException` / `Wss4jSecurityValidationException`. - **Order of interceptors** matters when combined with logging/other interceptors. **When to use.** Choose WS-Security UsernameToken when the contract/WSDL policy requires message-level auth, when messages traverse intermediaries, or when a partner mandates WSS headers. If you merely need point-to-point auth over HTTPS, HTTP Basic + TLS is simpler — but many enterprise SOAP contracts mandate WSS.
- Why can't the server store only a salted hash of the password when using PW_DIGEST?Because the digest on the wire is SHA-1(nonce + created + plaintextPassword). To validate it the server must recompute that digest, which requires the actual plaintext password. A one-way salted hash can't reproduce it, so digest auth forces recoverable password storage — a common reason teams pick PW_TEXT over TLS instead.
- What extra action defends UsernameToken against replay attacks and what pitfall does it introduce?Add a Timestamp action (and rely on WSS4J's nonce/created values). The pitfall is clock skew: with validationTimeToLive, differing client/server clocks can cause 'message expired' or 'created in the future' faults, so you must sync clocks and allow a tolerance.
saying these in an interview costs you the question
- Saying WS-Security is just HTTPS/TLS — it's message-level security in the SOAP header, independent of transport
- Claiming PW_DIGEST lets the server store only a hashed password
- Sending PW_TEXT without TLS
- Using the deprecated ...security.wss4j.* (1.x) package instead of ...wss4j2.*
- Thinking one interceptor direction covers both request and response — securement and validation are separate