skip to content

How do you add WS-Security username/password authentication to a Spring WS client or endpoint with Wss4jSecurityInterceptor?

level: seniorimportance: should knowfreq 35%

answer

  1. wss4j2 package (not deprecated wss4j)
  2. two directions: securement (out) / validation (in)
  3. securementActions='UsernameToken' + username/password/passwordType
  4. PW_DIGEST hashes nonce+created+password; PW_TEXT needs TLS
  5. validationCallbackHandler supplies known password

basics

~20 s

Add 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 s

WS-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
java
// 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

for a junior

Know WS-Security adds credentials inside the SOAP header and that Wss4jSecurityInterceptor handles it.

for a middle

Explain securement vs validation directions and the basic UsernameToken client/server config.

for a senior

Discuss PW_DIGEST vs PW_TEXT trade-offs, callback handlers, Timestamp/replay, and interceptor registration on client vs endpoint.

for a principal

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

context