I have multiple working SOAP Web Services on a Spring application, using httpBasic authentication, and I need to use WS-Security instead on one of them to allow authentication with the following Soap Header.
<soap:Header><wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" soap:mustUnderstand="1">
<wsse:UsernameToken xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="UsernameToken-1">
<wsse:Username>username</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">password</wsse:Password>
</wsse:UsernameToken>
</wsse:Security></soap:Header>
Current WSConfiguration was done according to https://github.com/spring-projects/spring-boot/blob/master/spring-boot-samples/spring-boot-sample-ws/ giving something like
#EnableWs
#Configuration
public class WebServiceConfig extends WsConfigurerAdapter {
#Bean
public ServletRegistrationBean dispatcherServlet(ApplicationContext applicationContext) {
MessageDispatcherServlet servlet = new MessageDispatcherServlet();
servlet.setApplicationContext(applicationContext);
return new ServletRegistrationBean(servlet, "/services/*");
}
#Bean(name = "SOAP1")
public DefaultWsdl11Definition defaultWsdl11Definition(XsdSchema soap1) {
DefaultWsdl11Definition wsdl11Definition = new DefaultWsdl11Definition();
wsdl11Definition.setPortTypeName("Soap1");
wsdl11Definition.setLocationUri("/soap1/");
wsdl11Definition.setTargetNamespace("http://mycompany.com/hr/definitions");
wsdl11Definition.setSchema(soap1);
return wsdl11Definition;
}
#Bean
public XsdSchema soap1() {
return new SimpleXsdSchema(new ClassPathResource("META-INF/schemas/hr.xsd"));
}
}
and Web Security according to http://spring.io/blog/2013/07/03/spring-security-java-config-preview-web-security/ looks like this
#EnableWebSecurity
#Configuration
public class CustomWebSecurityConfigurerAdapter extends
WebSecurityConfigurerAdapter {
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
auth
.inMemoryAuthentication()
.withUser("user1")
.password("password")
.roles("SOAP1")
.and()
.withUser("user2")
.password("password")
.roles("SOAP2");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeUrls()
.antMatchers("/soap/soap1").hasRole("SOAP1")
.antMatchers("/soap/soap2").hasRole("SOAP2")
.anyRequest().authenticated()
.and().httpBasic();
}
}
After some searches, I found that Wss4J provides a UsernameToken authentication, but can't figure out how to use it. What I'm trying to do is the following
https://sites.google.com/site/ddmwsst/ws-security-impl/ws-security-with-usernametoken
but without XML files with bean definitions.
What I plan to do:
Create the Callback Handler.
Create a Wss4jSecurityInterceptor, setting "setValidationActions" to "UsernameToken", "setValidationCallbackHandler" to my callback handler, and then add it by overriding addInterceptors on my WebServiceConfig.
(I tried something like that, but I just realised my callback was using a deprecated method)
Problem : Even if it works, it would then apply to all my webservices on "WebServiceConfig".
Update :
The implementation does work, but as expected it is applied to all my Web Services. How could I add my interceptor only to 1 Web Service ?
Following, the code I added in WebServiceConfig
#Bean
public Wss4jSecurityInterceptor wss4jSecurityInterceptor() throws IOException, Exception{
Wss4jSecurityInterceptor interceptor = new Wss4jSecurityInterceptor();
interceptor.setValidationActions("UsernameToken");
interceptor.setValidationCallbackHandler(new Wss4jSecurityCallbackImpl());
return interceptor;
}
#Override
public void addInterceptors(List<EndpointInterceptor> interceptors) {
try {
interceptors.add(wss4jSecurityInterceptor());
} catch (Exception e) {
e.printStackTrace();
}
}
Sorry, I totally forgot to answer this, but in case it helps someone :
We got it working by creating a new SmartEndpointInterceptor, and applying it only to our endpoint:
public class CustomSmartEndpointInterceptor extends Wss4jSecurityInterceptor implements SmartEndpointInterceptor {
//CustomEndpoint is your #Endpoint class
#Override
public boolean shouldIntercept(MessageContext messageContext, Object endpoint) {
if (endpoint instanceof MethodEndpoint) {
MethodEndpoint methodEndpoint = (MethodEndpoint)endpoint;
return methodEndpoint.getMethod().getDeclaringClass() == CustomEndpoint.class;
}
return false;
}
}
instead of adding a wss4j bean to the WebServiceConfig, we added our SmartEndpointInterceptor :
#Configuration
public class SoapWebServiceConfig extends WsConfigurationSupport {
//Wss4jSecurityCallbackImpl refers to an implementation of https://sites.google.com/site/ddmwsst/ws-security-impl/ws-security-with-usernametoken
#Bean
public CustomSmartEndpointInterceptor customSmartEndpointInterceptor() {
CustomSmartEndpointInterceptor customSmartEndpointInterceptor = new CustomSmartEndpointInterceptor();
customSmartEndpointInterceptor.setValidationActions("UsernameToken");
customSmartEndpointInterceptor.setValidationCallbackHandler(new Wss4jSecurityCallbackImpl(login, pwd));
return customSmartEndpointInterceptor;
}
[...]
}
Hope this is clear enough :)
It is worthworthy to note that whether is the result of the method shouldIntercept, the program would execute anyways the handleRequest method.
This can be dangerous, for example, in the login process.
In a project that I'm developing, we have only two endpoints:
UserLoginEndpoint
SomeGeneralEndpoint
The login would be invoked only for logging in purposes and will produce a token that I'll have to parse somehow from the request (this is done via an interceptor, the only one that we need in the application).
Suppose we have the following interceptor, just like Christophe Douy proposed and that our class of interest would be the UserLoginEndpoint.class
public class CustomSmartEndpointInterceptor extends Wss4jSecurityInterceptor implements SmartEndpointInterceptor {
//CustomEndpoint is your #Endpoint class
#Override
public boolean shouldIntercept(MessageContext messageContext, Object endpoint) {
if (endpoint instanceof MethodEndpoint) {
MethodEndpoint methodEndpoint = (MethodEndpoint)endpoint;
return methodEndpoint.getMethod().getDeclaringClass() == UserLoginEndpoint.class;
}
return false;
}
If this returns true, by all means, that's good and the logic defined in the handleRequest method will be executed.
But where's my issue?
For my specific problem, I'm writing an interceptor that should get in the way only if the user has already logged in. This means that the previous snippet code should be the following
public class CustomSmartEndpointInterceptor extends Wss4jSecurityInterceptor implements SmartEndpointInterceptor {
//CustomEndpoint is your #Endpoint class
#Override
public boolean shouldIntercept(MessageContext messageContext, Object endpoint) {
if (endpoint instanceof MethodEndpoint) {
MethodEndpoint methodEndpoint = (MethodEndpoint)endpoint;
return methodEndpoint.getMethod().getDeclaringClass() != UserLoginEndpoint.class;
}
return false;
}
And if that would be true, the handleRequest method would be executed (my implementation is below)
#Override
public boolean handleRequest(MessageContext messageContext, Object endpoint) throws Exception {
System.out.println("### SOAP REQUEST ###");
InputStream is = null;
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
Document doc = null;
try {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
messageContext.getRequest().writeTo(buffer);
String payload = buffer.toString(java.nio.charset.StandardCharsets.UTF_8.name());
System.out.println(payload);
is = new ByteArrayInputStream(payload.getBytes());
DocumentBuilder builder = factory.newDocumentBuilder();
doc = builder.parse(is);
} catch (IOException ex) {
ex.printStackTrace();
return false;
}
XPath xpath = XPathFactory.newInstance().newXPath();
xpath.setNamespaceContext(new NamespaceContext() {
#Override
public String getNamespaceURI(String prefix) {
switch(prefix) {
case "soapenv":
return "http://schemas.xmlsoap.org/soap/envelope/";
case "it":
return "some.fancy.ws";
default:
return null;
}
}
#Override
public String getPrefix(String namespaceURI) {
return null;
}
#Override
public Iterator getPrefixes(String namespaceURI) {
return null;
}
});
XPathExpression expr = xpath.compile("//*//it:accessToken//text()");
Object result = expr.evaluate(doc, XPathConstants.NODE);
Node node = (Node) result;
String token = node.getNodeValue();
return authUtility.checkTokenIsValid(token);
}
But what happens if shouldIntercept returns false? If the handleRequest method, which is mandatory to implement if you "implements" SmartPointEndPointInterceptor, returns true, the invocation chain will keep on; but if it returns false, it will stop there: I'm in the second case, but the handleRequest still gets executed.
The only workaround that I found is to add a property in the MessageContext which has an arbitrary key and a corresponding value which is the one returned from the shouldIntercept method.
Then negate that value in the very first lines of your handleRequest's implementation to force the return true and have the invocation chain
if (!(Boolean)messageContext.getProperty("shouldFollow")) {
return true;
}
Of course, this will work in projects where only one interceptor is needed (i.e., in my case just to verify if the user is really logged in) and there are many other factors that might influence everything but I felt it was worthy to share in this topic.
I apologize in advance if I made a mistake in answering here instead of opening a new question
Related
We have a Spring Boot microservice that does the SOAP call to the external system using org.springframework.ws.client.core.WebServiceTemplate.
Now the system would be protected with Keycloak, so all the request need to beak the auth token.
If it was a REST API, I would just replace the pre-existed RestTemplate with OAuth2RestTemplate. But how to instrument the calls initially done by the org.springframework.ws.client.core.WebServiceTemplate ?
So, I understand, I should put the authentication header manually with value 'Bearer ....token there...'. How I can retrieve that part manually to put it into the request?
you can get current request token using RequestContextHolder class and add into soap request header.
String token = ((ServletRequestAttributes)(RequestContextHolder.getRequestAttributes())).getRequest().getHeader("Authorization");
Also I would suggest use Webservice interecptor instead of adding header in each web service request call.
The problem was caused by
Existing library code, based on org.springframework.ws.client.core.WebServiceTemplate, so large and huge for rewriting it using WebClient, compatible with OAuth2 SpringSecurity or use depricated OAuth2RestTemplate
The webservice we previously communicated with, turns into protected with Gravitee and accepts queries with JWT tokens only. So, the only change here is to add the Authentication header with 'Bearer ....token there...'
We initiate the call from the scheduled jo in the microservice. So, it should be getting token from the Keycloak before the request and be able to update it with time. No one does the explicit authorization like in the frontend, so the OAuth2 client should use client-id and client-secret to connect with no human involved
The Solution
At the beginning, we define the Interceptor to the SOAP calls, that will pass the token as a header, via a Supplier function taking it wherever it can be taken:
public class JwtClientInterceptor implements ClientInterceptor {
private final Supplier<String> jwtToken;
public JwtClientInterceptor(Supplier<String> jwtToken) {
this.jwtToken = jwtToken;
}
#Override
public boolean handleRequest(MessageContext messageContext) {
SoapMessage soapMessage = (SoapMessage) messageContext. getRequest();
SoapHeader soapHeader = soapMessage.getSoapHeader();
soapHeader.addHeaderElement(new QName("authorization"))
.setText(String. format("Bearer %s", jwtToken.get()));
return true;
}
#Override
public boolean handleResponse(MessageContext messageContext) throws WebServiceClientException {
return true;
}
#Override
public boolean handleFault(MessageContext messageContext) throws WebServiceClientException {
return true;
}
#Override
public void afterCompletion(MessageContext messageContext, Exception ex) throws WebServiceClientException {
}
}
Then pass it to the template in addition to other pre-existed interceptor to be called in config class:
protected WebServiceTemplate buildWebServiceTemplate(Jaxb2Marshaller marshaller,
HttpComponentsMessageSender messageSender, String uri, Supplier<String> jwtToken) {
WebServiceTemplate template = new WebServiceTemplate();
template.setMarshaller(marshaller);
template.setUnmarshaller(marshaller);
template.setMessageSender(messageSender);
template.setDefaultUri(uri);
ClientInterceptor[] clientInterceptors = ArrayUtils.addAll(template.getInterceptors(), new Logger(), new JwtClientInterceptor(jwtToken));
template.setInterceptors(clientInterceptors);
return template;
}
Then add the Spring Security Oath2 Client library
compile 'org.springframework.security:spring-security-oauth2-client:5.2.1.RELEASE'
We create OAuth2AuthorizedClientService bean, that uses a standard ClientRegistrationRepository (the repository is initiated through usage of #EnableWebSecurity annotation on the #Configuration class, but please double check about that)
#Bean
public OAuth2AuthorizedClientService oAuth2AuthorizedClientService(ClientRegistrationRepository clientRegistrationRepository) {
return new InMemoryOAuth2AuthorizedClientService(clientRegistrationRepository);
}
Then create a OAuth2AuthorizedClientManager
#Bean
public OAuth2AuthorizedClientManager authorizedClientManager(
ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientRepository authorizedClientRepository) {
Authentication authentication = new Authentication() {
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
GrantedAuthority grantedAuthority = new GrantedAuthority() {
#Override
public String getAuthority() {
return "take_a_needed_value_from_property";
}
};
return Arrays.asList(grantedAuthority);
}
#Override
public Object getCredentials() {
return null;
}
#Override
public Object getDetails() {
return null;
}
#Override
public Object getPrincipal() {
return new Principal() {
#Override
public String getName() {
return "our_client_id_from_properties";
}
};
}
#Override
public boolean isAuthenticated() {
return true;
}
#Override
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
}
#Override
public String getName() {
return "take_a_needed_name_from_properties";
}
};
//we need to emulate Principal there, as other classes relies on it. In fact, Principal isn't needed for the app which is a client and just do the call, as nothing is authorized in the app against this Principal itself
OAuth2AuthorizationContext oAuth2AuthorizationContext = OAuth2AuthorizationContext.withClientRegistration(clientRegistrationRepository.findByRegistrationId("keycloak")).
principal(authentication).
build();
oAuth2AuthorizationContext.getPrincipal().setAuthenticated(true);
oAuth2AuthorizationContext.getAuthorizedClient();
OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder().
//refreshToken().
clientCredentials(). //- we use this one according to our set up
//authorizationCode().
build();
OAuth2AuthorizedClientService oAuth2AuthorizedClientService = oAuth2AuthorizedClientService(clientRegistrationRepository); //use the bean from before step here
AuthorizedClientServiceOAuth2AuthorizedClientManager authorizedClientManager =
new AuthorizedClientServiceOAuth2AuthorizedClientManager(
clientRegistrationRepository, oAuth2AuthorizedClientService);
OAuth2AuthorizedClient oAuth2AuthorizedClient = authorizedClientProvider.authorize(oAuth2AuthorizationContext);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
oAuth2AuthorizedClientService.saveAuthorizedClient(oAuth2AuthorizedClient,
oAuth2AuthorizationContext.getPrincipal());
//this step is needed, as without explicit authorize call, the
//oAuth2AuthorizedClient isn't initialized in the service
return authorizedClientManager;
}
Provide a method for supplied function that can be called each time to retrieve the JWT token from the security stuff (repository and manager). Here it should be auto-updated, so we just call for retrieving it
public Supplier<String> getJwtToken() {
return () -> {
OAuth2AuthorizedClient authorizedClient = authorizedClientService.loadAuthorizedClient("keycloak", "we_havePout_realm_there_from_the_properties");
return authorizedClient.getAccessToken().getTokenValue();
};
}
Pass this Consumer to the #Bean, defining the WebServiceTemplate's
#Bean
public Client client(#Qualifier("Sender1") HttpComponentsMessageSender bnfoMessageSender,
#Qualifier("Sender2") HttpComponentsMessageSender uhMessageSender) {
WebServiceTemplate sender1= buildWebServiceTemplate(buildSender1Marshaller(), sender1MessageSender, properties.getUriSender1(),getJwtToken());
WebServiceTemplate sender2 = buildWebServiceTemplate(buildSender2Marshaller(), sender2MessageSender, properties.getUriSender2(),getJwtToken());
return buildClient(buildRetryTemplate(), sender1, sender2);
}
We add Spring Security Client values to application.yaml in order to configure it.
spring:
security:
oauth2:
client:
provider:
keycloak:
issuer-uri: https://host/keycloak/auth/realms/ourrealm
registration:
keycloak:
client-id: client_id
client-secret: client-secret-here
authorization-grant-type: client_credentials //need to add explicitly, otherwise would try other grant-type by default and never get the token!
client-authentication-method: post //need to have this explicitly, otherwise use basic that doesn't fit best the keycloak set up
scope: openid //if your don't have it, it checks all available scopes on url like https://host/keycloak/auth/realms/ourrealm/ .well-known/openid-configuration keycloak and then sends them as value of parameter named 'scope' in the query for retrieving the token; that works wrong on our keycloak, so to replace this auto-picked value, we place the explicit scopes list here
I was try to hit REST API with authorization from two header fileds: Authorization and Date but Date field has removed from DefaultHeaderFilterStrategy. I was try to replace it with mine filter and set it into Jetty client, but Date header still missing into RequestProcessor and RestProcessor. I need to can transfer this header globally for all our requests. Here is a parts of my code.
#Component
public class RestAPIClientRoute extends RouteBuilder {
#Autowired
private CamelContext camelContext;
#Override
public void configure() throws Exception {
JettyHttpComponent jettyComponent = camelContext.getComponent("jetty", JettyHttpComponent.class);
jettyComponent.setHeaderFilterStrategy(new HeaderFilter());
restConfiguration().component("jetty").scheme("http").port(80).host("localhost");
interceptSendToEndpoint("rest:post:*").process(new Processor() {
public void process(Exchange exchange) {
Properties authProperties = CryptoUtil.duoAuthRequestEncode( duoConfig,"POST", exchange);
Message msg = exchange.getMessage();
msg.setHeader("Authorization", "Basic " + authProperties.getProperty("auth"));
msg.setHeader("Date", authProperties.getProperty("timestamp"));
}
});
rest("/rest")
.post("/accounts/v1/account/list")
.to("direct:hello");
from("direct:hello")
.process(new RequestProcessor());
from("timer:rest-client?period=60s")
.to("direct:accountList");
from("direct:accountList")
.to("rest:post:/rest/accounts/v1/account/list")
.process(new RestProcessor());
}
}
#Component
public class HeaderFilter implements HeaderFilterStrategy {
#Override
public boolean applyFilterToCamelHeaders(String headerName, Object headerValue, Exchange exchange) {
return false;
}
#Override
public boolean applyFilterToExternalHeaders(String headerName, Object headerValue, Exchange exchange) {
return false;
}
}
Actually the problem is coming from the REST component. He set HeaderFilter which override the other setted filter in HTTP component. REST component can not set HeaderFilterStrategy and always use default one. I was start to use only http component with setted mine customised filter instead rest and now can transfer deleted headers from DefaultHeaderFilterStrategy.
I replace it
.to("rest:post:/rest/accounts/v1/account/list")
with
.setHeader(Exchange.HTTP_METHOD, constant("POST"))
.setHeader(Exchange.HTTP_QUERY, constant(urlQuery))
.to("https://host.domain.com/rest/accounts/v1/account/list")
I went through links like: SOAPFaultException "MustUnderstand headers (oasis-200401-wss-wssecurity-secext-1.0.xsd) are not understood", but still struggling.
I'm using Spring Boot v2.2.2..RELEASE and SOAP project.
I am loading two different WSDL file into my project. One URL Generates to http://localhost:8080/employee/employee-soap which works fine. But http://localhost:8080/student/student-soap this gives below error.
2020-02-17 15:31:00.241 WARN 20236 --- [nio-8080-exec-5] o.s.w.soap.server.SoapMessageDispatcher : Could not handle mustUnderstand headers: {http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd}Security. Returning fault
JavaCode:
#EnableWs
#Configuration
public class AppConfig extends WsConfigurerAdapter {
#SuppressWarnings({ "rawtypes", "unchecked" })
#Bean
public ServletRegistrationBean messageDispatcherServlet(ApplicationContext applicationContext) {
MessageDispatcherServlet servlet = new MessageDispatcherServlet();
servlet.setApplicationContext(applicationContext);
servlet.setTransformWsdlLocations(true);
return new ServletRegistrationBean(servlet, "/*");
}
#Bean
public SaajSoapMessageFactory messageFactory() {
SaajSoapMessageFactory messageFactory = new SaajSoapMessageFactory();
messageFactory.setSoapVersion(SoapVersion.SOAP_11);
messageFactory.afterPropertiesSet();
return messageFactory;
}
#Bean("empXSD")
public XsdSchema organizationSchema() {
return new SimpleXsdSchema(new ClassPathResource("/xsd/employee.xsd"));
}
#Bean(name = "employee")
public DefaultWsdl11Definition defaultWsdl11Definition(#Qualifier("empXSD") XsdSchema schema) {
DefaultWsdl11Definition wsdl11Definition = new DefaultWsdl11Definition();
wsdl11Definition.setPortTypeName("employee");
wsdl11Definition.setLocationUri("employee/employee-soap");
wsdl11Definition.setTargetNamespace("urn:example.com:dms:wsdls:employee");
wsdl11Definition.setSchema(schema);
wsdl11Definition.setCreateSoap11Binding(true);
return wsdl11Definition;
}
#Bean
#Qualifier(value="stuXSD")
public XsdSchema stuSchema() {
return new SimpleXsdSchema(new ClassPathResource("/xsd/student.xsd"));
}
#Bean(name = "student")
public DefaultWsdl11Definition geographyWsdl11Definition(#Qualifier("stuXSD") XsdSchema schema) {
DefaultWsdl11Definition wsdl11Definition = new DefaultWsdl11Definition();
wsdl11Definition.setPortTypeName("student");
wsdl11Definition.setLocationUri("student-soap");
wsdl11Definition.setTargetNamespace("urn:example.com:dms:wsdls:student");
wsdl11Definition.setSchema(schema);
wsdl11Definition.setCreateSoap11Binding(true);
return wsdl11Definition;
}
#Override
public void addInterceptors(List<EndpointInterceptor> interceptors) {
interceptors.add(new Interceptor(endpoints, req));
}
}
Code:
#Configuration
public class SimpleMustUnderstandEndpointInterceptor implements SoapEndpointInterceptor{
private final String SAMPLE_NS = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd";
#Override
public boolean handleRequest(MessageContext messageContext, Object endpoint) throws Exception {
return true;
}
#Override
public boolean handleResponse(MessageContext messageContext, Object endpoint) throws Exception {
return true;
}
#Override
public boolean handleFault(MessageContext messageContext, Object endpoint) throws Exception {
return true;
}
#Override
public void afterCompletion(MessageContext messageContext, Object endpoint, Exception ex) throws Exception {
}
#Override
public boolean understands(SoapHeaderElement header) {
if(header.getName().getNamespaceURI().equalsIgnoreCase(SAMPLE_NS)) {
return true;
}
return false;
}
}
Per observation, looks like even this SoapEndpointInterceptor is not calling, before to that only its giving error.
During calling SOAP endpoint, below header information is going and its giving Fault as I mentioned above. Any pointers ?
<soapenv:Header><wsse:Security soapenv:mustUnderstand="1"
xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-
secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-
wss-wssecurity-utility-1.0.xsd"><wsse:UsernameToken wsu:Id="UsernameToken-
518482F2CDC2F635FF158202815227129"><wsse:Username>aispoc_usr1</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-
username-token-profile-1.0#PasswordText">aispoc_usr1</wsse:Password><wsse:Nonce
EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-
message-security-1.0#Base64Binary">/fdGCEilz/dkVeZE05b7LQ==</wsse:Nonce>
2020-02-18T12:15:52.271Z
You can try below config that would solve the issue.
#Bean
public Wss4jSecurityInterceptor securityInterceptor() {
Wss4jSecurityInterceptor security = new Wss4jSecurityInterceptor();
security.setValidationActions("NoSecurity");
security.setValidateRequest(false);
security.setValidateResponse(true);
return security;
}
#Override
public void addInterceptors(List<EndpointInterceptor> interceptors) {
interceptors.add(securityInterceptor());
}
I was able to find the solution looking at https://docs.spring.io/spring-ws/site/apidocs/org/springframework/ws/soap/security/wss4j/Wss4jSecurityInterceptor.html and https://memorynotfound.com/spring-ws-username-password-authentication-wss4j/.
I simply used below bean and its started working fine.
#Bean
public Wss4jSecurityInterceptor securityInterceptor() {
Wss4jSecurityInterceptor security = new Wss4jSecurityInterceptor();
security.setSecurementActions("NoSecurity");
security.setSecurementPasswordType(WSConstants.PW_TEXT);
return security;
}
I have the following code base:
#Component
public class DummyRoute extends RouteBuilder {
#Override
public void configure() throws Exception {
rest("/upload").post().to("file://rest_files");
}
#Bean
public ServletRegistrationBean servletRegistrationBean() {
SpringServerServlet serverServlet = new SpringServerServlet();
ServletRegistrationBean regBean = new ServletRegistrationBean( serverServlet, "/rest/*");
Map<String,String> params = new HashMap<>();
params.put("org.restlet.component", "restletComponent");
regBean.setInitParameters(params);
return regBean;
}
#Bean
public org.restlet.Component restletComponent() {
return new org.restlet.Component();
}
#Bean
public RestletComponent restletComponentService() {
return new RestletComponent(restletComponent());
}
}
I upload file using postman:
It is actually usual csv.
But when I open file my application stored - I see file with following content:
Obvious that file contains full request information.
How can I save only file without other data from http request?
P.S.
I tried to register callback:
#Override
public void process(Exchange exchange) throws Exception {
System.out.println(exchange);
final MultipartFile mandatoryBody = exchange.getIn().getBody(MultipartFile.class);
but it returns null
I want every time when I make a request through feign client, to set a specific header with my authenticated user.
This is my filter from which I get the authentication and set it to the spring security context:
#EnableEurekaClient
#SpringBootApplication
#EnableFeignClients
public class PerformanceApplication {
#Bean
public Filter requestDetailsFilter() {
return new RequestDetailsFilter();
}
public static void main(String[] args) {
SpringApplication.run(PerformanceApplication.class, args);
}
private class RequestDetailsFilter implements Filter {
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
String userName = ((HttpServletRequest)servletRequest).getHeader("Z-User-Details");
String pass = ((HttpServletRequest)servletRequest).getHeader("X-User-Details");
if (pass != null)
pass = decrypt(pass);
SecurityContext secure = new SecurityContextImpl();
org.springframework.security.core.Authentication token = new UsernamePasswordAuthenticationToken(userName, pass);
secure. setAuthentication(token);
SecurityContextHolder.setContext(secure);
filterChain.doFilter(servletRequest, servletResponse);
}
#Override
public void destroy() {
}
}
private String decrypt(String str) {
try {
Cipher dcipher = new NullCipher();
// Decode base64 to get bytes
byte[] dec = new sun.misc.BASE64Decoder().decodeBuffer(str);
// Decrypt
byte[] utf8 = dcipher.doFinal(dec);
// Decode using utf-8
return new String(utf8, "UTF8");
} catch (javax.crypto.BadPaddingException e) {
} catch (IllegalBlockSizeException e) {
} catch (UnsupportedEncodingException e) {
} catch (java.io.IOException e) {
}
return null;
}
}
This is my feign client:
#FeignClient("holiday-client")
public interface EmailClient {
#RequestMapping(value = "/api/email/send", method = RequestMethod.POST)
void sendEmail(#RequestBody Email email);
}
And here I have a request interceptor:
#Component
public class FeignRequestInterceptor implements RequestInterceptor {
private String headerValue;
public FeignRequestInterceptor() {
}
public FeignRequestInterceptor(String username, String password) {
this(username, password, ISO_8859_1);
}
public FeignRequestInterceptor(String username, String password, Charset charset) {
checkNotNull(username, "username");
checkNotNull(password, "password");
this.headerValue = "Basic " + base64encode((username + ":" + password).getBytes(charset));
}
private static String base64encode(byte[] bytes) {
BASE64Encoder encoder = new BASE64Encoder();
return encoder.encode(bytes);
}
#Override
public void apply(RequestTemplate requestTemplate) {
requestTemplate.header("Authorization", headerValue);
}
}
I don't know how to configure this interceptor to my client and how to set the header with the username and password. How can I accomplish that ?
You don't really need your own implementation of the FeignRequestInterceptor as there is already BasicAuthRequestInterceptor in the feign.auth package that does exactly the same.
With this said, you basically have almost everything set up already. All is left to do is to define the basicAuthRequestInterceptor bean with specific username and password:
#Bean
public RequestInterceptor basicAuthRequestInterceptor() {
return new BasicAuthRequestInterceptor("username", "password");
}
I know the thread is a bit old but wanted to give some explanation on what's happening here.
If you'd like to customize your Feign requests, you can use a RequestInterceptor. This can be a custom implementation or you can reuse what's available in the Feign library, e.g. BasicAuthRequestInterceptor.
How to register it? Well, there 2 ways to do it depending on how you use Feign.
If you're using plain Feign without Spring, then you gotta set the interceptor to the Feign builder. An example is here.
Feign.builder()
.requestInterceptor(new MyCustomInterceptor())
.target(MyClient.class, "http://localhost:8081");
If you're using Spring Cloud OpenFeign and you use the #FeignClient annotation to construct your clients, then you have to create a bean from your RequestInterceptor by either defining it as a #Component or as a #Bean in one of your #Configuration classes. Example here.
#Component
public class MyCustomInterceptor implements RequestInterceptor {
#Override
public void apply(RequestTemplate template) {
// do something
}
}
Also, you can check out one of my articles in this topic, maybe that clears it up better: Customizing each request with Spring Cloud Feign