Unable to get ServletContext in Spring Controller when using Websockets - websocket

Client Code:
$scope.socket = new SockJS("ws/ws");
$scope.stompClient = Stomp.over($scope.socket);
$scope.stompClient.connect("guest", "guest",connectCallback, errorCallback);
//in connectCallback
$scope.stompClient.subscribe('/topic/agent-sendstatus', showScreenPop);
Java Code:
#MessageMapping("/agent-sendstatus")
public void testmethod()
{
//How do i get ServletContext here to further implement solution?
template.convertAndSend("/topic/agent-sendstatus","bcd");
}
Please suggest.
I am getting session using SimpMessageHeaderAccessor in the controller but i need ServletContext too.
Is there some way in Spring?

When you upgrade to WebSocket there's no HTTP involved in the communication, the only place where you have HTTP is during the handshake. You could implement a HandshakeInterceptor to get the ServletContext and expose any parameter to the WebSocket session, so you can get them in your message handler method with a HeaderAccessor. Check out the HttpSessionHandshakeInterceptor for an example.

Related

Wiremock does not receive any requests

I am writing an integrationtest for a resource (Jax-rs with resteasy) which invokes a thirdparty request to the Trustpilot Api to get a review link for a product.
In my tests I wanted to mock this call by using Wiremock and invoke the request by using resteasy Dispatcher.
The code below shows my Setup (names etc are changed).
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = { MyResourceTestContext.class })
public class MyResourceIntegrationTest {
#Rule
public WireMockRule wireMockRule = new WireMockRule(wireMockConfig().dynamicPort().dynamicHttpsPort());
#Autowired
private MyResource myResource;
private Dispatcher dispatcher;
private MockHttpResponse response;
#Before
public void setUp() throws Exception {
dispatcher = MockDispatcherFactory.createDispatcher();
dispatcher.getRegistry().addSingletonResource(myResource);
response = new MockHttpResponse();
}
#Test
public void testResourceShouldReturnRedirectToTrustpilotreview() throws Exception {
URI apiRequestURI = UriBuilder
.fromUri("https://api.trustpilot.com/v1/private/product-reviews/business-units"
+ "/{businessUnitId}}/invitation-links")
.build(BUSINESS_UNIT_ID);
URI expectedRedirectURI = UriBuilder.fromUri("https://products.trustpilot.com/#evaluate/{reviewLinkId}")
.build(REVIEW_LINK_ID);
stubFor(post(apiRequestURI.toString())
.willReturn(okJson("{\"reviewLinkId\":\"" + REVIEW_LINK_ID + "\","
+ "\"reviewUrl\":\"" + expectedRedirectURI.toString() + "\"}")));
MockHttpRequest request = MockHttpRequest.get("/myResource");
dispatcher.invoke(request, response);
WireMock.verify(1, postRequestedFor(urlEqualTo(apiRequestURI.toString())));
Assert.assertEquals(HttpStatus.TEMPORARY_REDIRECT.value(), response.getStatus());
Assert.assertEquals(expectedRedirectURI.toString(), response.getOutputHeaders().getFirst("Location"));
}
But what I'm getting is (with my actual bid in the url):
com.github.tomakehurst.wiremock.client.VerificationException: Expected at least one request matching: {
"url" : "https://api.trustpilot.com/v1/private/product-reviews/business-units/<bid>/invitation-links",
"method" : "POST"
}
Requests received: [ ]
Stepping into the request with the Debugger I can say this request and other requests are actually executed successfully. But they are not recognized/mocked by Wiremock.
Is there something wrong with my Wiremock setup? Any Ideas? Thank you!
There are a couple of reasons why this isn't sending anything to WireMock at the moment:
WireMock only accepts relative URLs when stubbing so passing the full URL including protocol and domain won't work. Try something equivalent to stubFor(post(urlPathEqualTo("/v1/private/product-reviews/business-units/<bid>/invitation-links") instead.
There doesn't seem to be any way for your class under test to know which host/port WireMock is running on and no forward proxying set up so the code is presumably still trying to call the real Trustpilot API.
To solve 2. you have a couple of options:
Set up your class/code under test to use localhost as the destination host and the result of wireMockRule.port() as the port number, or wireMockRule.httpsPort() if you're on HTTPS.
Use the new auto JVM proxy setup feature to proxy your app's calls through WireMock. This assumes you're on WireMock 2.31.0+ and that your app's HTTP client respects the JVM proxy system properties.

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
}
}
}

issue with Spring and asynchronous controller + HandlerInterceptor + IE/Edge

I am working on a Spring application that serves up REST endpoints. One of the endpoints essentially acts as a proxy between the HTML client and a third party cloud storage provider. This endpoint retrieves files from the storage provider and proxies them back to the client. Something like the following (note there is a synchronous and asynchronous version of the same endpoint):
#Controller
public class CloudStorageController {
...
#RequestMapping(value = "/fetch-image/{id}", method = RequestMethod.GET, produces = MediaType.IMAGE_JPEG_VALUE)
public ResponseEntity<byte[]> fetchImageSynchronous(#PathVariable final Long id) {
final byte[] imageFileContents = this.fetchImage(id);
return ResponseEntity.ok().body(imageFileContents);
}
#RequestMapping(value = "/fetch-image-async/{id}", method = RequestMethod.GET, produces = MediaType.IMAGE_JPEG_VALUE)
public Callable<ResponseEntity<byte[]>> fetchImageAsynchronous(#PathVariable final Long id) {
return () -> {
final byte[] imageFileContents = this.fetchImage(id);
return ResponseEntity.ok().body(imageFileContents);
};
}
private byte[] fetchImage(final long id) {
// fetch the file from cloud storage and return as byte array
...
}
...
}
Due to the nature of the client app (HTML5 + ajax) and how this endpoint is used, user authentication is supplied to this endpoint differently that the other endpoints. To handle this, a HandlerInterceptor was developed to deal with authentication for this endpoint:
#Component("cloudStorageAuthenticationInterceptor")
public class CloudStorageAuthenticationInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(final HttpServletRequest request, final HttpServletResponse response, final Object handler) {
// examine the request for the authentication information and verify it
final Authentication authenticated = ...
if (authenticated == null) {
try {
pResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED);
} catch (IOException e) {
throw new RuntimeException(e);
}
return false;
}
else {
try {
request.login(authenticated.getName(), (String) authenticated.getCredentials());
} catch (final ServletException e) {
throw new BadCredentialsException("Bad credentials");
}
}
return true;
}
}
The interceptor is registered like this:
#Configuration
#EnableWebMvc
public class ApiConfig extends WebMvcConfigurerAdapter {
#Autowired
#Qualifier("cloudStorageAuthenticationInterceptor")
private HandlerInterceptor cloudStorageAuthenticationInterceptor;
#Override
public void addInterceptors(final InterceptorRegistry registry) {
registry.addInterceptor(this.cloudStorageAuthenticationInterceptor)
.addPathPatterns(
"/fetch-image/**",
"/fetch-image-async/**"
);
}
#Override
public void configureAsyncSupport(final AsyncSupportConfigurer configurer) {
final ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(this.asyncThreadPoolCoreSize);
executor.setMaxPoolSize(this.asyncThreadPoolMaxSize);
executor.setQueueCapacity(this.asyncThreadPoolQueueCapacity);
executor.setThreadNamePrefix(this.asyncThreadPoolPrefix);
executor.initialize();
configurer.setTaskExecutor(executor);
super.configureAsyncSupport(configurer);
}
}
Ideally, the image fetching would be done asynchronously (using the /fetch-image-asyc/{id} endpoint) because it has to call a third party web service which could have some latency.
The synchronous endpoint (/fetch-image/{id}) works correctly for all browsers. However, if using the asynchronous endpoint (/fetch-image-async/{id}), Chrome and Firefox work as expect.
However, if the client is Microsoft IE or Microsoft Edge, we seem some strange behavior. The endpoint is called correctly and the response sent successfully (at least from the server's viewpoint). However, it seems that the browser is waiting for something additional. In the IE/Edge DevTools window, the network request for the image shows as pending for 30 seconds, then seems to timeout, updates to successful and the image is successfully display. It also seems the connection to the server is still open, as the server side resources like database connections are not released. In the other browsers, the async response is received and processed in a second or less.
If I remove the HandlerInterceptor and just hard-wire some credentials for debugging, the behavior goes away. So this seems to have something to with the interaction between the HandlerInterceptor and the asynchronous controller method, and is only exhibited for some clients.
Anyone have a suggestion on why the semantics of IE/Edge are causing this behavior?
Based on your description, there are some different behaviors when using IE or Edge
it seems that the browser is waiting for something additional
the connection seems still open
it works fine if remove HandlerInterceptor and use hard code in auth logic
For the first behavior, I would suggest you use fiddler to trace all http requests. It is better if you could compare two different actions via fiddler (1) run on chrome, 2) run on edge ). Check all http headers in requests and responses carefully to see whether there is some different part. For the other behaviors, I would suggest you write logs to find which part spend the most time. It will provide you useful information to troubleshot.
After much tracing on the server and reading through the JavaDocs comments for AsyncHandlerInterceptor, I was able to resolve the issue. For requests to asynchronous controller methods, the preHandle method of any interceptor is called twice. It is called before the request is handed off to the servlet handling the request and again after the servlet has handled the request. In my case, the interceptor was attempting to authenticate the request for both scenarios (pre and post request handling). The application's authentication provider checks credentials in a database. For some reason if the client is IE or Edge, the authentication provider was unable to get a database connection when called from preHandle in the interceptor after the servlet handled the request. The following exception would be thrown:
ERROR o.a.c.c.C.[.[.[.[dispatcherServlet] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.dao.DataAccessResourceFailureException: Could not open connection; nested exception is org.hibernate.exception.JDBCConnectionException: Could not open connection] with root cause
java.sql.SQLTransientConnectionException: HikariPool-0 - Connection is not available, request timed out after 30001ms.
So the servlet would successfully handle the request and send a response, but the filter would get hung up for 30 seconds waiting for the database connection to timeout on the post processing called to preHandle.
So for me, the simple solution was to add a check in preHandle if it is being called after the servlet has already handled the request. I updated the preHandle method as follows:
#Override
public boolean preHandle(final HttpServletRequest pRequest, final HttpServletResponse pResponse, final Object pHandler) {
if (pRequest.getDispatcherType().equals(DispatcherType.REQUEST)) {
... perform authentication ...
}
return true;
}
That solved the issue for me. It doesn't explain everything (i.e., why only IE/Edge would cause the issue), but it seems that preHandle should only do work before the servlet handles the request anyways.

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
}
}
}

How can I get HttpServletRequest and HttpServletResponse object in Spring AOP

I want to get the response object in spring AOP before advice. If the session is invalidate I want to redirect to the login page, but unable to get the HttpServletResponse object in the Before advice method.
Tried with the following way.
#Autowired
private HttpServletResponse response;
public void setResponse(HttpServletResponse response) {
this.response = response;
}
Stacktrace:
caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire field: javax.servlet.http.HttpServletResponse com.****.****.aspect.LogProvider.response; nested exception is
org.springframework.beans.factory.NoSuchBeanDefinitionException: No matching bean of type [javax.servlet.http.HttpServletResponse] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {#org.springframework.beans.factory.annotation.Autowired(required=true)}
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:506)
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:87)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:284)
... 33 more
Any help will be appreciated.
You can get response by under method:
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
HttpServletResponse response = ((ServletRequestAttributes)requestAttributes).getResponse();
Basically we do redirect from a jsp page i.e. from UI layer we handle such kind of operation(redirection). So, I hope you will be using some restful services in your application. And for most of the restful services we go for Asynchronous request. If it is combination of Asynchronous and restful services; and I am sure you will be using this in your application. If your session is invalid and you try to access perform any operation on 'session' then it will land you in 'IllegalStateException'. For such type of scenario please follow the below centralized 'Exception Handling' mechanism provided by JAX-RS: javax.ws.rs.ext.ExceptionMapper.
Please follow below steps:
step-1: Create a user-defined unchecked exception like MyApplicationException:
public class MyApplicationException extends RuntimeException {
public MyApplicationException() {super();}
// implement other methods of RuntimeException as per your requirement
}
step-2: Create a user-defined type of ExceptionMapper
public class MyApplicationExceptionHandler implements ExceptionMapper<MyApplicationException>
{
#Override
public Response toResponse(MyApplicationException exception)
{
return Response.status(Status.FORBIDDEN).entity(exception.getMessage()).build();
// set any Status code of 4XX as this is client side error not server side
}
}
step-3: In all your ajax request in the UI code check this Status Code and redirect to the login page.
That's it and you are done with a finer implementation. Guaranteed...
/**
* #return the HttpServletResponse handled by the current thread
*/
public static Optional<HttpServletResponse> getThreadLocalResponse() {
return Optional.ofNullable(RequestContextHolder.getRequestAttributes())
.filter(ra -> ra instanceof ServletRequestAttributes)
.map(ServletRequestAttributes.class::cast)
.map(ServletRequestAttributes::getResponse);
}
To get the response object you can use this code:
ServletWebRequest servletWebRequest=new ServletWebRequest(request);
HttpServletResponse response=servletWebRequest.getResponse();
To get the request object:
HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getR‌equest();
If you get a null response then I can see the response is not yet formed when the control is returned. Then the only way ahead is to go with interceptors.

Resources