0

We have a three fold Security Configuration for an WS-SOAP Endpoint (part of a WSTEP server implementation). It supports Kerberos, X509-Certificate and UsernameToken authentication. We had it configured using individual WebSecurityConfigurerAdapter extended configuration classes. In addition we use a Wss4jSecurityInterceptor implementing the shouldIntercept method for doing the UsernameToken authentication stuff while no previous authentication (by any of the other methods) took place. This works well while using WebSecurityConfigurationAdapter, but not any more while migrating. It seems, that the Wss4jSecurityInterceptor will be engaged much earlier in the processing than before and no authentication had taken place so far.

The environment is Spring-Boot 2.4.4

@Configuration
@EnableWebSecurity
@Order(1)
public class SecurityKerberosConfig {

    private static final Logger LOGGER = LoggerFactory.getLogger(SecurityKerberosConfig.class);

    private final UserAuthenticationService userAuthenticationService;

    @Value("${wstepservice.kerberos.service-principal:HTTP/localhost}")
    private String servicePrincipal;

    @Value("${wstepservice.kerberos.keytab-location}")
    private String keytabLocation;

    @Value("${wstepservice.urls.ces.baseUrl}")
    private String cesBaseUrl;

    @Value("${wstepservice.urls.cep.baseUrl}")
    private String cepBaseUrl;

    public SecurityKerberosConfig(UserAuthenticationService userAuthenticationService) {
        this.userAuthenticationService = userAuthenticationService;
    }

    @SuppressFBWarnings(value = "SPRING_CSRF_PROTECTION_DISABLED",
        justification = "This resource is stateless.")
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http, SpnegoFilterDsl spnegoFilterDsl,
        KerberosProviderDsl kerberosProviderDsl) throws Exception {
        http
            .requestMatchers((requestMatchers) ->
                requestMatchers
                    .antMatchers(cepBaseUrl + "/kerberos/**")
                    .antMatchers(cesBaseUrl + "/kerberos/**")
            )
            .authorizeRequests().anyRequest()
            .authenticated()
            .and()
            .exceptionHandling()
            .authenticationEntryPoint(spnegoEntryPoint())
            .and()
            .csrf().disable()
            .apply(spnegoFilterDsl)
            .and()
            .apply(kerberosProviderDsl);
        return http.build();
    }

@Configuration
@EnableWebSecurity
@Order(2)
public class SecurityTLSCertificateConfig {

    @Value("${wstepservice.urls.ces.baseUrl}")
    private String cesBaseUrl;

    @Value("${wstepservice.urls.cep.baseUrl}")
    private String cepBaseUrl;

    private final UserAuthenticationService userAuthenticationService;

    public SecurityTLSCertificateConfig(UserAuthenticationService userAuthenticationService) {
        this.userAuthenticationService = userAuthenticationService;
    }

    @SuppressFBWarnings(value = "SPRING_CSRF_PROTECTION_DISABLED",
        justification = "This resource is stateless.")
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .requestMatchers((requestMatchers) ->
                requestMatchers
                    .antMatchers(cepBaseUrl+"/certificate/**")
                    .antMatchers(cesBaseUrl+"/certificate/**")
            )
            .authorizeRequests().anyRequest()
            .authenticated()
            .and()
            .x509()
            .subjectPrincipalRegex("CN=(.*?)(?:,|$)")
            .userDetailsService(getUserDetailsService())
            .and()
            .csrf().disable();
        return http.build();
    }

@Configuration
@EnableWebSecurity
@Order(3)
public class SecurityUsernameTokenConfig {

    @Value("${wstepservice.urls.ces.baseUrl}")
    private String cesBaseUrl;

    @Value("${wstepservice.urls.cep.baseUrl}")
    private String cepBaseUrl;

    @SuppressFBWarnings(value = "SPRING_CSRF_PROTECTION_DISABLED",
        justification = "This resource is stateless.")
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .requestMatchers((requestMatchers) ->
                requestMatchers
                    .antMatchers(cepBaseUrl + "/userpass/**")
                    .antMatchers(cesBaseUrl + "/userpass/**")
            )
            .requiresChannel()
            .anyRequest()
            .requiresSecure()
            .and()
            .anonymous()
            .and()
            .csrf().disable();
        return http.build();
    }
}

@Configuration
public class WebServiceInterceptorConfig extends WsConfigurationSupport {

    public final static Logger LOGGER = LoggerFactory.getLogger(WebServiceInterceptorConfig.class);

    @Bean
    public SmartWss4jInterceptor smartWss4jInterceptor(UserAuthenticationService userAuthenticationService) {
        CallbackHandler callbackHandler1 = new AbstractCallbackHandler() {
            @Override
            protected void handleInternal(Callback callback)
                throws IOException, UnsupportedCallbackException {
                if (callback instanceof WSPasswordCallback) {
                    WSPasswordCallback wsPasswordCallback = (WSPasswordCallback) callback;
                    userAuthenticationService.authenticate(wsPasswordCallback.getIdentifier(),
                        wsPasswordCallback.getPassword(), CredentialSource.USERNAME_TOKEN);
                }
            }
        };

        SmartWss4jInterceptor interceptor = new SmartWss4jInterceptor();
        interceptor.setValidationActions(WSHandlerConstants.USERNAME_TOKEN);
        interceptor.setValidationCallbackHandler(callbackHandler1);
        interceptor.setSecurementActions(WSHandlerConstants.TIMESTAMP);
        interceptor.setTimestampStrict(true);

        WSSConfig wssConfig = WSSConfig.getNewInstance();
        QName qName = new QName("http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", "UsernameToken");
        wssConfig.setValidator(qName, new CustomUsernameTokenValidator());
        interceptor.setWssConfig(wssConfig);
        return interceptor;
    }


    public static class SmartWss4jInterceptor extends Wss4jSecurityInterceptor implements
        SmartEndpointInterceptor {
        @Override
        public boolean shouldIntercept(MessageContext messageContext, Object endpoint) {
            if (endpoint instanceof MethodEndpoint) {
                Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
----> The Authentication object is null, after the migration to SecurityFilterChain
                if (authentication.getAuthorities()
                    .stream()
                    .anyMatch(auth -> ((SimpleGrantedAuthority)auth).getAuthority().equals("ROLE_USER"))
                ) {
                    LOGGER.debug("Principal {} already authenticated.", authentication.getName());
                    return false;
                }
                else {
                    LOGGER.debug("Unauthenticated request. Trying to authenticate with UsernameToken.");
                    return true;
                }
            }
            else {
                return false;
            }
        }
    }

    public static class CustomUsernameTokenValidator extends UsernameTokenValidator {
        @Override
        protected void verifyPlaintextPassword(UsernameToken usernameToken, RequestData data)
            throws WSSecurityException {
            CallbackHandler callbackHandler = data.getCallbackHandler();
            if (callbackHandler == null) {
                throw new WSSecurityException(ErrorCode.FAILURE, "noCallback");
            }
            String user = usernameToken.getName();
            String password = usernameToken.getPassword();
            String pwType = usernameToken.getPasswordType();
            WSPasswordCallback pwCb = new WSPasswordCallback(user, password, pwType, 2);
            try {
                data.getCallbackHandler().handle(new Callback[]{pwCb});
            } catch (UnsupportedCallbackException | IOException e) {
                throw new WSSecurityException(ErrorCode.FAILED_AUTHENTICATION, e);
            }
        }
    }
}

After a lot of try & error I've no glue how to proceed and solve the problem. Any hints are greatly appreciated. Many thanks in advance.

Ruediger
  • 1
  • 1

0 Answers0