Spring-ws SoapHeader fields access in endpoint method - spring

We are developing a contract-first WebService using spring-ws 2.2.0. We are trying to manage the authentication using a custom tag, named AuthToken, located in the SoapHeader.
The AuthToken has the following structure:
<authToken>
<username>USERNAME</xa:username>
<password>PASSWORD</xa:password>
</authToken>
We are able to generate a WSDL schema containing the specified custom authentication tag inside the SoapHeader.
The problem is that when the client performs the call towards our server we are not able to unmarshal the AuthToken tag (located in the SoapHeader) in our Ws Endpoint implementation.
Using the #RequestPayload annotation in the binding method signature (handleMethodRequest as specified in the example below), we are able to access the unmarshalled payload content (located in the SoapBody).
We tried to make the same thing with the SoapHeader content without success.
In the following code examples we show you what we would like to obtain:
1
#PayloadRoot(namespace = NAMESPACE_URI, localPart = "methodRequest")
#ResponsePayload
public MethodResponse handleMethodRequest(#RequestPayload MethodRequest request, #SoapHeader(value = "authToken") AuthToken authToken) { }
2
#PayloadRoot(namespace = NAMESPACE_URI, localPart = "methodRequest")
#ResponsePayload
public MethodResponse handleMethodRequest(#RequestPayload MethodRequest request, AuthToken authToken) { }
3
#PayloadRoot(namespace = NAMESPACE_URI, localPart = "methodRequest")
#ResponsePayload
public MethodResponse handleMethodRequest(#RequestPayload MethodRequest request, org.springframework.ws.soap.SoapHeader header) { }
4
#PayloadRoot(namespace = NAMESPACE_URI, localPart = "methodRequest")
#ResponsePayload
public MethodResponse handleMethodRequest(#RequestPayload MethodRequest request, MessageContext messageContext) { }
In case 1, 2 we obtain the following error:
No adapter for endpoint [MethodResponse EndpointImplementation.handleMethodRequest(MethodRequest, AuthToken) throws java.lang.Exception]: Is your endpoint annotated with #Endpoint, or does it implement a supported interface like MessageHandler or PayloadEndpoint?
In case 3, 4 we have no errors but we are not able to handle SoapHeader or MessageContext (respectively in case 3 and 4) to reach our purposes, accessing the AuthToken to retrieve username and password sub element.
Looking for a solution in the web we found that many people having the same problem uses Spring Interceptors to handle the authentication.
Following the "Interceptors-way" we should access the AuthToken inside the interceptor. Unfortunately we need to use AuthToken field inside handleMethodRequest method for other purposes, for example loading user specific data, not accessible outside handleMethodRequest.
Therefore we cannot follow this way because we need to refer user specific data inside the handleMethodRequest method.
Does anyone know how can we solve the problem? Thanks in advance.

For that use case, the only supported combination of annotation and parameter type is #SoapHeader and SoapHeaderElement. Spring-WS currently doesn't support unmarshalling headers.

A hacky way of getting the value from interceptor to the handleMethodRequest is using a static ThreadLocal instance. Since the same thread that invokes the interceptor also invokes the handleMethodRequest you can use
ThreadLocal.set(AuthToken); // in interceptor.
ThreadLocal.get();// in handler and then clear it after use.
Also, I noticed that #SoapHeader(value = "{authToken") in your example does not have } is that a typo here or in your code?

Related

Spring Security 6.0 CsrfToken behavior change

I tested Spring Security as part of my Spring Boot Setup in version 6.0-M5, 6.0-RC1 and 6.0-RC2. I recognized a behavior change and wanted to ask whether this may be a bug. I return the CSRF token as a serialized JSON, but since RC1 the content of the token in the JSON is garbage.
My working code in Spring Boot 6 Milestone 5 still working as expected.
#RestController
public class CsrfController {
#GetMapping("/rest/user/csrf")
public CsrfToken csrf(CsrfToken token) {
return token;
}
}
In my use case I query the controller using a unit test.
#LocalServerPort
int serverPort;
#Autowired
private TestRestTemplate webclient;
#Test
public void getCsrf() {
ResponseEntity<String> entity = webclient.getForEntity("http://localhost:" + serverPort +
"/rest/user/csrf", String.class);
// ... here some code to get the token from the JSON body ...
assertTrue(result.matches("^[a-f0-9\\-]+$"));
This is the first query of the server. A session object between client and server is not established in past queries. This worked in M5 but stopped working in Spring Boot 6 RC1 and RC2
The following controller code made it work again in RC2:
#GetMapping("/rest/user/csrf")
public CsrfToken csrf(HttpServletRequest request, HttpServletResponse response) {
CsrfToken repoToken = tokenRepo.loadToken(request);
if (repoToken != null) {
return repoToken;
}
// required because it is required but ay not be initialized by the tokenRepo
request.getSession();
repoToken = tokenRepo.generateToken(request);
tokenRepo.saveToken(repoToken, request, response);
return repoToken;
}
If I tried the old code in RC2, I received on client side a malformed string. I did not receive a UUID styled token in my JSON serialized response body. I think it is related to the uninitialized session object.
Is this a bug or is an uninitialized session and a resulting not working CrsfToken specified behavior?
I think the issue is in the way I try to get and use the XSFR token.
Because I want to use an Angular frontend, I configured my token repository to provide the tokens via Cookie.
http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
This produces cookies the old UUID style. However the authentication expects the new tokens as generated by https://github.com/spring-projects/spring-security/issues/11960 . Probably the cookie mechanism still needs to be migrated until final Spring Boot 3.0.

Restrict API access by domain name [duplicate]

I have one war file for my application and I will be using 2 domains to access it. For example I want to access admin.jsp using admin.mydomain.com/adminpage and other jsp pages I want to access with local.mydomain.com.
Also, admin.jsp should be only accessible via admin.mydomain.com and not via local.mydomain.com. How to do this in spring-security / spring-mvc? Is there a support in spring framework for this?
Any help on this would be helpful. Thanks.
You can implement RequestMatcher, and maybe like
HostOnlyRequestMatch(String relativePath, String hostname)
and then override the boolean matches(HttpServletRequest request) method, and if the relativePath and hostname are same with request, return true.
Add the requestMatcher to http like this:
http
.authorizeRequests()
.requestMatcher(new HostOnlyRequestMatch("/admin", "admin.mydomain.com")).permitAll()
.antMatchers("/admin").denyAll();
One way would be to configure proxy (e.g. Nginx) to route your requests to your application server (e.g Tomcat) properly. Read here for more details https://www.nginx.com/resources/admin-guide/reverse-proxy/
You can get the requested url from request object in you mvc controller and if it is not form correct domain then you can throw or show proper error based on your project. Following is the code snippet
#Controller
#RequestMapping("/adminpage")
public class AdminPageController{
#RequestMapping(method = RequestMethod.GET)
public String getAdminPage(HttpServletRequest request) {
String url = request.getRequestURL().toString();
if(!url.contains("admin.mydomain.com")) {
throw RuntimeException("Not accessible through this domain.");
// You can implement your own logic of showing error here
}
}
}

Resolving POST /** request URL to full request URL using micrometer

With the micro-service architecture I have written a generic POST request handler which is consumed by all the micro-services. The post mapping in spring look like this:
#RestController
#RequestMapping(value = "/v1/", consumes = {MediaType.APPLICATION_JSON_VALUE}, produces = {MediaType.APPLICATION_JSON_VALUE})
public class V1Controller {
#PostMapping(path = "/**")
public #ResponseBody Json post () {}
}
Now while I am consuming the metrics for this endpoint using micrometer I am only getting /v1/ as the endpoint in the metrics while I am sending the full URL like /v1/demo/foo from the calling service. I tried lot of the combination but it is not working. I have also added the WebMvcTagsProvider where I am listing to request and resolving the POST api calls.
#Bean
#SuppressWarnings("unchecked")
public WebMvcTagsProvider webMvcTagsProvider(ObjectMapper objectMapper) {
return new DefaultWebMvcTagsProvider() {
public Iterable<Tag> getTags(HttpServletRequest request, HttpServletResponse response, Object handler, Throwable exception) {
if ("POST".equals(request.getMethod())) {
Tag uriTag = Tag.of("uri", String.valueOf(request.getRequestURI()));
return Tags.of(WebMvcTags.method(request), uriTag, WebMvcTags.exception(exception), WebMvcTags.status(response));
}
return Tags.of(WebMvcTags.method(request), WebMvcTags.uri(request, response), WebMvcTags.exception(exception), WebMvcTags.status(response));
}
};
}
Still it is resolving to /v1/ URL in the metrics. I tried googling alot but didn't find any solution. Thanks in advance.
The build in Spring Boot RequestMapping based metrics match on the annotations and add those as tags.
This is to avoid a tag explosion. Imagine a #RequestMapping for a path like user/{userId}, you would want to group all those calls together (user/1, user/2, user/3).
You'll want to create your own Timer in your post method that set that url tags, etc there.
If you decide to reuse the same metric name as the built in Spring Boot metric, you'll want to disable that one as well, so you don't double count those requests.

How to filter request based on domain in spring-mvc

I have one war file for my application and I will be using 2 domains to access it. For example I want to access admin.jsp using admin.mydomain.com/adminpage and other jsp pages I want to access with local.mydomain.com.
Also, admin.jsp should be only accessible via admin.mydomain.com and not via local.mydomain.com. How to do this in spring-security / spring-mvc? Is there a support in spring framework for this?
Any help on this would be helpful. Thanks.
You can implement RequestMatcher, and maybe like
HostOnlyRequestMatch(String relativePath, String hostname)
and then override the boolean matches(HttpServletRequest request) method, and if the relativePath and hostname are same with request, return true.
Add the requestMatcher to http like this:
http
.authorizeRequests()
.requestMatcher(new HostOnlyRequestMatch("/admin", "admin.mydomain.com")).permitAll()
.antMatchers("/admin").denyAll();
One way would be to configure proxy (e.g. Nginx) to route your requests to your application server (e.g Tomcat) properly. Read here for more details https://www.nginx.com/resources/admin-guide/reverse-proxy/
You can get the requested url from request object in you mvc controller and if it is not form correct domain then you can throw or show proper error based on your project. Following is the code snippet
#Controller
#RequestMapping("/adminpage")
public class AdminPageController{
#RequestMapping(method = RequestMethod.GET)
public String getAdminPage(HttpServletRequest request) {
String url = request.getRequestURL().toString();
if(!url.contains("admin.mydomain.com")) {
throw RuntimeException("Not accessible through this domain.");
// You can implement your own logic of showing error here
}
}
}

CUCM AXL API wrong SoapAction

I want to make SQL query to CUCM DB. I generated Java classes from WSDL with Maven jaxb2 plugin, but Cisco AXL docs advice to use wsdl2java. I've got lot of Java classes with Req/Res endings (request and response as I understand). This is my code:
public class CUCMDatabaseConnector extends WebServiceGatewaySupport{
private String SOAP_ACTION = "CUCM:DB ver=10.5";
public void updateData(){
String END_USERS_REQUEST = REQUEST,
AXLurl = "https://" + properties.getCurrentCUCM_IP() + ":8443/axl/";
ExecuteSQLQueryReq sqlRequest = new ExecuteSQLQueryReq();
sqlRequest.setSql(END_USERS_REQUEST);
WebServiceTemplate template = getWebServiceTemplate();
template.setMessageSender(NullHostnameVerifier.getMessageSender());
ExecuteSQLQueryRes sqlResponse = (ExecuteSQLQueryRes) template
.marshalSendAndReceive(
AXLurl,
sqlRequest,
new WebServiceMessageCallback() {
#Override
public void doWithMessage(WebServiceMessage message) throws IOException, TransformerException {
TransportContext context = TransportContextHolder.getTransportContext();
HttpUrlConnection connection = (HttpUrlConnection) context.getConnection();
//adding required headers
connection.addRequestHeader( "SOAPAction", SOAP_ACTION);
connection.addRequestHeader("Authorization", autenString);
}
}
});
}
}
}
But when I run it, I get error:
org.springframework.ws.soap.client.SoapFaultClientException: The endpoint reference (EPR) for the Operation not found is https://IP:8443/axl/services/AXLAPIService and the WSA Action = CUCM:DB ver=10.5 executeSQLQueryReq
So, as I see, problem is CUCM AXL service has executeSQLQuery method, but not executeSQLQueryReq.
How can I make Spring putting correct method in SoapAction? Or I need to use only wsdl2java?
UPDATE
When I was generating java classes, there was also .xsd schema in the directory. jaxb2 config pointed to WSDL file, however, I've got error URI [src/main/resources/AXLAPI.wsdl] does not provide the scheme part., and It looks like plugin built classes from xsd schema, not wsdl. But this wsdl was original file downloaded from CUCM. What could be wrong?
Found this link on developers.cisco.com. This how-to advices to use AXLPort, a kind of wrapper for making SOAP request to CUCM.
Looks to me, CUCM AXL SOAP interface is not the best choice to start working with Spring WS

Resources