spring-projects / spring-projects/spring-ws

its not clear how to use Spring Security with Wss4jSecurityInterceptor for UsernameToken authentication

Open
#1,745 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

status: waiting-for-triage
Dominant language
Java
Stars
346
Forks
323
Avg merge
3d 2h
Merged PRs (30d)
7

Description

basically, the Wss4jSecurityInterceptor delegates to a SpringSecurityPasswordValidationCallbackHandler which in turn uses Spring Security to look up a User and get its password. That passowrd will be (hopefully) encoded using a PasswordEncoder.

In regular Spring Security HTTP Basic authentication on any other web scenario, Spring Security takes the password from the User#getPassword, finds the prefix (eg: {bcrypt}), and uses it to find the PasswordEncoder that matches the algorithm and then uses that PasswordEncoder to encode the plaintext passwrd sent by the client and then uses the PasswordEncoder to compare the two encoded passwords (the one encoded from the client and the one from the User.).

What happens in Spring WS is different. It compares the encoded password with the plaintext password in the SOAP message. I suppose we could try to encode the password in the SOAP message, but the comparison would still fail because, for example, BCrypt encodings have randomness and seeds so even given the same input, you might get different outputs. This is why we'd need the PasswordEncoder#matches, not just the call to MessageDigest.isEqual in UsernameTokenValidator#verifyDigestPassword, which does a straight byte for byte equality check and doesn't know about Spring Security's encodings.

AFAICT, the only way to make this work is to store passwords with the PasswordEncoder using plaintext, but this is insecure and - for most systems - not an option because the passwords will already be encoded.

i mention all this thinking that i am missing something, somehow.

i have a Spring WS app using Spring Boot 4 and the spring-boot-starter-web-services starter and org.springframework.ws : spring-ws-security

here's the relevant Spring Security + Spring WS integration configuration:


@Configuration
class SecurityConfiguration {

    @Configuration
    static class SecurityWsConfigurer implements WsConfigurer {

        private final Wss4jSecurityInterceptor securityInterceptor;

        SecurityWsConfigurer(Wss4jSecurityInterceptor securityInterceptor) {
            this.securityInterceptor = securityInterceptor;
        }

        @Override
        public void addInterceptors(List<EndpointInterceptor> interceptors) {
            interceptors.add(this.securityInterceptor);
        }
    }


    @Bean
    PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

    @Bean
    InMemoryUserDetailsManager inMemoryUserDetailsManager(PasswordEncoder passwordEncoder) {
        var users = Set.of("stephane", "rob", "josh")
                .stream()
                .map(username -> User //
                        .withUsername(username)//
                        .password(passwordEncoder.encode("pw")) //
                        .roles("USER") //
                        .build() //
                )
                .toList();
        return new InMemoryUserDetailsManager(users);
    }

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http
                .csrf(AbstractHttpConfigurer::disable)
                .authorizeHttpRequests(a -> a
                        .requestMatchers("/ws/**").permitAll()
                        .anyRequest().authenticated()
                )
                .httpBasic(Customizer.withDefaults())
                .build();
    }

    @Bean
    SpringSecurityPasswordValidationCallbackHandler springSecurityPasswordValidationCallbackHandler(UserDetailsService service) {
        var security = new SpringSecurityPasswordValidationCallbackHandler();
        security.setUserDetailsService(service);
        return security;
    }

    @Bean
    Wss4jSecurityInterceptor wss4jSecurityInterceptor(
                   SpringSecurityPasswordValidationCallbackHandler handler) {
        var ws4jsi = new Wss4jSecurityInterceptor();
        ws4jsi.setValidationActions("UsernameToken");
        ws4jsi.setValidationCallbackHandler(handler);
        return ws4jsi;
    }
}

Here's the request I'm sending to the endpoint

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
                  xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
                  xmlns:gs="http://example.com/ws">
    <soapenv:Header>
        <wsse:Security soapenv:mustUnderstand="1">
            <wsse:UsernameToken>
                <wsse:Username>josh</wsse:Username>
                <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">pw</wsse:Password>
            </wsse:UsernameToken>
        </wsse:Security>
    </soapenv:Header>
    <soapenv:Body>
        <gs:getCountryRequest>
            <gs:name>Spain</gs:name>
        </gs:getCountryRequest>
    </soapenv:Body>
</soapenv:Envelope>

this way

#!/usr/bin/env bash
curl -v --header "content-type: text/xml" -d @request-secure.xml http://localhost:8080/ws

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with SpringSecurityPasswordValidationCallbackHandler and UsernameTokenValidator, then reproduce the supplied Spring Security and Wss4jSecurityInterceptor configuration with request-secure.xml. Compare the UsernameToken password validation path with the configured PasswordEncoder behavior; done means either encoded users authenticate successfully or the supported limitation and required configuration are documented.

Written by the indexing model from the issue text.

Assessment

Tech stack
java, spring
Domain
authentication, backend, security
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Needs clarification
Newbie friendliness
38/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.