Maintaining session state across two http different requests in an application - java

I have a scenario where I want to store session information across multiple sessions in Application # 2. We have two applications deployed on a tomcat server. Our use case is as follows:
A. Web Application # 1 makes a HTTP Post request to Application # 2 using a HTTP Rest Client. POST request contains a JSON http request body encapsulating the data to be send to Application # 2. The code block is as follows:
RestTemplate template = new RestTemplate();
final SearchCustomer customer = new SearchCustomer();
restTemplate.execute(
SEND_CUSTOMER_PROFILE, HttpMethod.POST,
new SearchRequestCallback(searchCustomer), null);
The request callback function is
static class SearchRequestCallback implements RequestCallback {
/**
* Write a JSON response to the request body.
*/
#Override
public void doWithRequest(ClientHttpRequest request) throws IOException {
HttpHeaders httpHeaders = request.getHeaders();
List<MediaType> acceptableMediaTypes = new LinkedList<>();
acceptableMediaTypes.add(MediaType.APPLICATION_JSON);
httpHeaders.setAccept(acceptableMediaTypes);
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
request.getBody().write(
new Gson().toJson(this.searchCustomer).getBytes(StandardCharsets.UTF_8.displayName()));
}
}
The second application has a Spring controller with the following set up
#Controller
public class SearchCustomerController {
/**
* Builds customer profile knowledge graph.
*
* <p>This is invoked as an synchronous request.
*/
#RequestMapping(value="/searchProfilePayload.go", method=RequestMethod.POST)
#ResponseStatus(HttpStatus.OK)
public void constructSearchCustomerProfileKnowledgeGraph(
#RequestBody final SearchCustomer customer, HttpServletRequest request) {
UserContext userContext =
(UserContext) request.getSession().getAttribute("userContext");
if (userContext == null) {
// Perform heavy operation to fetch user session.
userContext = UserContextHelper.getUserContext(request);
request.getSession("userContext", userContext)
}
userContext.setCustomerProfile(customer);
}
}
When I make a call to another URI within the application # 2 say via browser, I want it done in such as way that the session attributes are retained when making this call. Is there a way to do that?
I know about URL rewriting that stores JSESSIONIDin the cookie, but I don't think how you can set the value when making a rest call, and using the same JESSIONID to maintain session attributes.
Is there a better way to do this? These have no answers. I have looked at these links, but none seem to answer my question.
HTTP and Sessions
comparison of ways to maintain state

jraahhali is spot on.
Set the cookie header with the value of JSESSIONID=${sessionId} or use it directly in the url as per the URL rewriting link.
First step is to retrieve the JSESSIONID from the initial response (this will depend on how you decide to set the session id - URL or Cookies, lets assume by cookie for now)
#Override
public void doWithRequest(ClientHttpRequest request) throws IOException {
HttpHeaders httpHeaders = request.getHeaders();
List<MediaType> acceptableMediaTypes = new LinkedList<>();
acceptableMediaTypes.add(MediaType.APPLICATION_JSON);
httpHeaders.setAccept(acceptableMediaTypes);
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
request.getBody().write(
new Gson().toJson(this.searchCustomer).getBytes(StandardCharsets.UTF_8.displayName()));
ClientHttpResponse response = request.execute();
String sessionId = response.getHeaders().get(HttpHeaders.SET_COOKIE).split(":")[1].trim(); // I didnt test this, will prolly get a NPE :P
this.sessionId = sessionId;
}
Then in subsequent requests (ie from the app #1 or a browser or whatever)
if (this.sessionId != null && !this.sessionId.equals(""))
httpHeaders.set(HttpHeaders.COOKIE, "JSESSIONID=" + this.sessionId);
// ...
request.execute();
Note if you really want to use a browser as the other client then I would use the URL rewriting method for ease of use ...

Related

Passing parameters between JAX-RS WriterInterceptor and ReaderInterceptor

I'm using JAX-RS and within the WriterInterceptor, I need to access some information contained in the original request.
As an example, consider the below request body.
{
"ClientId": "MY_CLIENT_ID",
"UserId": "MY_USER_ID",
"AccountId": "MY_ACCOUNT_ID",
"Scope" : "MY_SCOPES",
}
Within my WriteInterceptor, I need to read Client ID and User ID from the request and add those values to the response.
I am currently working on a ReadInterceptor implementation for this. I initially assumed there is a way to put parameters to ReaderInterceptorContext and then read it somehow from the WriterInterceptorContext. But It seems there is no way to do that. ( Please correct me if I'm wrong).
So, now I'm trying to use a concurrent hashmap to store these parameters in the ReaderInterceptor and retrieve it in the WriteInterceptor. I need a unique key to create the correlation between request and response. Is it ok to use the thread ID for this?
Please point me if there is a better approach to resolve this problem
I resolved this problem by adding a container response filter which can add a header to the response. Read interceptor reads required parameters from the request and set those as context properties.
#Override
public Object aroundReadFrom(ReaderInterceptorContext readerInterceptorContext)
throws IOException, WebApplicationException {
InputStream is = readerInterceptorContext.getInputStream();
String requestBody = new Scanner(is, StandardCharsets.UTF_8.name()).useDelimiter("\\A").next();
JSONObject request = new JSONObject(requestBody);
//Adding the stream back to the context object
readerInterceptorContext.setInputStream(new ByteArrayInputStream(requestBody.getBytes()));
//Adding properties to read in filter
readerInterceptorContext.setProperty("ClientId", request.get("ClientId"));
readerInterceptorContext.setProperty("UserId","UserId"));
return readerInterceptorContext.proceed();
}
These properties are then read inside the container response filter and added as a response header.
#Override
public void filter(ContainerRequestContext containerReqContext, ContainerResponseContext containerResponseContext) {
//Adding temporary headers to read in WriterInterceptor
containerResponseContext.getHeaders().add(
"ClientId", containerReqContext.getProperty("ClientId"));
containerResponseContext.getHeaders().add(
"UserId", containerReqContext.getProperty("UserId"));
}
Existing writer interceptor read these headers, add those to JWT and then remove as header values. I did a POC for this and it is working as expected
#Override
public void aroundWriteTo(WriterInterceptorContext writerInterceptorContext) throws IOException {
OutputStream outputStream = writerInterceptorContext.getOutputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
writerInterceptorContext.setOutputStream(baos);
String clientId = writerInterceptorContext.getHeaders().getFirst("ClientId").toString();
String user = writerInterceptorContext.getHeaders().getFirst("UserId").toString();
}

New Http Session obtained in PreProcessInterceptor on every Request

I have an interceptor that implements PreProcessInterceptor.
I need to get the HttpSession object in the preProcess method, so I'm using:
#Context
private HttpServletRequest httpRequest;
and then:
httpRequest.getSession();
to get the HttpSession object.
At first I thought everything was working fine, but then I realized that the httpRequest.getSession() was returning a new HttpSession object on every request.
I need to set some session attributes on the user first request and then use those attributes on futher requests. The attributes are being set all right, I can even access those attributes down along that same request stack. However, as I am getting a new Session on every new request, I am not able to access those attributes.
Do I need to send something from the client side do my REST services, like a token or something?
Here is a more complete view of my Interceptor
#Provider
#ServerInterceptor
#SecurityPrecedence
public class SecurityInterceptor implements PreProcessInterceptor, AcceptedByMethod {
...
#Context
private HttpServletRequest httpRequest;
#Override
public boolean accept(Class classe, Method metodo) {
return metodo.getAnnotation(PermitAll.class) == null;
}
...
#Override
public ServerResponse preProcess(HttpRequest request, ResourceMethod resourceMethod) {
HttpSession httpSession = httpRequest.getSession();
// Set attributes on httpSession
...
return null;
}
}
Session is tried to the concept of HTTP Cookies.
The processing of the First HTTP request would have detected that there is no current Session and created a new one. The Session ID would've then been populated as a Cookie (Http Response Header) whenever the Response was returned to the Client.
If your Second HTTP Request had a Request Header for that same Cookie, then httpSession wouldn't be created new.
So, whenever you are sending requests to the Server, check if there exists a Cookie in the Server and send that cookie with each request.

What are the methods testing POST to prevent status=405?

I was trying to build a RESTful web service using Jersey.
In my server side code, there is a path with name "domain" which I use to display content. The content of the page the "domain" refers to is accessible only correct username and password are input.
#POST
#Produces(MediaType.APPLICATION_JSON)
#Path("domain")
public ArrayList<String> domainList(#Context HttpServletRequest req) throws Exception{
Environments environments = new DefaultConfigurationBuilder().build();
final ALMProfile profile = new ALMProfile();
profile.setUrl(environments.getAutomation().getAlmProfile().getUrl());
profile.setUsername((String) req.getSession().getAttribute("username"));
//Set username from input, HTML form
profile.setPassword((String) req.getSession().getAttribute("password"));
//Set password from input, HTML form
try (ALMConnection connection = new ALMConnection(profile);) {
if (connection.getOtaConnector().connected()) {
Multimap<String, String> domain = connection.getDomains();
ArrayList<String> domain_names = new ArrayList<String>();
for(String key : domain.keys()){
if(domain_names.contains(key)) domain_names.add(key);
}
return domain_names; //return the content
}
} catch (Exception e) {
e.printStackTrace();
return null;
}
return null;
}
When I attempted to test if correct content was returned, I got an error (status=405, reason=Method Not Allowed). Below is my client side test.
public static void main(String[] args){
Environments environments = new DefaultConfigurationBuilder().build();
final ALMProfile profile = new ALMProfile();
profile.setUrl(environments.getAutomation().getAlmProfile().getUrl());
profile.setUsername("username"); //Creating a profile with username and password
profile.setPassword("password");
ClientConfig config = new ClientConfig();
Client client = ClientBuilder.newClient(config);
WebTarget target = client.target(getBaseURI());
String response = target.path("domain").request().accept
(MediaType.APPLICATION_JSON).get(Response.class).toString();
//Above is the GET method I see from an example,
//probably is the reason why 405 error comes from.
System.out.println(response);
}
private static URI getBaseURI() {
return UriBuilder.fromUri("http://localhost:8080/qa-automation-console").build();
}
The servlet configuration is good. We have other paths succesfully running.
I suspect the reason might come from I used a GET method to do the job that is supposed to be POST.
But I am not familiar to Jersey methods I can use.
Does anyone know any methods that I can use to test the functionality?
See 405 Status Code
405 Method Not Allowed
The method specified in the Request-Line is not allowed for the resource identified by the Request-URI. The response MUST include an Allow header containing a list of valid methods for the requested resource.
Your endpoint is for a #POST request. In your client you are trying to get().
See the Client API documentation for information on how to make a POST request. If it is supposed to be a GET request, then simply change the method annotation to #GET.
Also note, for your #POST resource methods, you should always put a #Consumes annotation with the media types the method supports. If the client send a media type not supported, then they will get a 415 not supported as expected. I would have posted an example of the client post, but I have no idea what type are you are expecting because of the missing annotation, also you don't even have a post object as a method parameter so I am not even sure if your method is really even supposed to be for POST.
See Also:
How to send json object from REST client using javax.ws.rs.client.WebTarget

Creating a cookie inside a jax-rs endpoint

I have a jax-rs endpoint. The purpose of the endpoint is to authorize a user. I need to login details inside a cookie. Below I have mentioned the related part of my code.
public Response authorize(#Context HttpServletRequest request) throws URISyntaxException {
if (authnResult.isAuthenticated()) {
//TODO create a cookie to maintain login state
Cookie authCookie = new Cookie(FrameworkConstants.COMMONAUTH_COOKIE, "test");
authCookie.setSecure(true);
authCookie.setHttpOnly(false);
authCookie.setMaxAge(5 * 60);
}
EDIT:
This is my first time when creating cookies. I followed some tutorials. In these tutorials it has added the created cookie to the response. But inside the endpoint I can't access the response. So how can I create the cookie? Please advice me.
Updated code:
public Response authorize(#Context HttpServletRequest request) throws URISyntaxException {
NewCookie cookie = new NewCookie("CookieName","CookieValue");
Response.ResponseBuilder builder = Response.ok("Cool Stuff");
builder.cookie(cookie);
Response response=builder.build();
Cookie[] cookies = request.getCookies();
}
what I need to know is how to access the newly created cookie.
You can create a javax.ws.rs.core.NewCookie. There are a bunch of different constructors, just go through the API docs.
Then you can add cookies through ResponseBuilder#cookie(NewCookie). So for example:
#GET
public Response getCookie() {
NewCookie cookie = new NewCookie("Name", "Value", "path", "domain",
"comment", 300, true, true);
ResponseBuilder builder = Response.ok("Cool Stuff");
builder.cookie(cookie);
return builder.build();
}
UPDATE (with complete example)
#Path("cookie")
public class CookieResource {
#GET
public Response getCookie(#CookieParam("A-Cookie") String cookie) {
Response response = null;
if (cookie == null) {
response = Response.ok("A-Cookie: Cookie #1")
.cookie(new NewCookie("A-Cookie", "Cookie #1"))
.build();
return response;
} else {
String cookieNum = cookie.substring(cookie.indexOf("#") + 1);
int number = Integer.parseInt(cookieNum);
number++;
String updatedCookie = "Cookie #" + number;
response = Response.ok("A-Cookie: " + updatedCookie)
.cookie(new NewCookie("A-Cookie", updatedCookie))
.build();
return response;
}
}
}
After 38 requests, you can see the result. I used a Firefox plugin Firebug. You can see the sent cookie #37, and returned cookie #38
If you need help trying to access the cookie from the client (as suggested in your comment), that may be suitable for another question on SO. Maybe off topic for this discussion, as it will rely on another technology. If this is not what you are looking for, then maybe a better explanation of exactly what you are trying to accomplish would help.

encodeRedirectURL changes some characters

I have an Interceptor on Struts2, and I want for some pages to redirect to the ssl version of them.
Example: http://localhost/xhtml/path.do?ossesionid=value1 to https://localhost/xhtml/path.do?ossesionid=value1
For doing this I created a Interceptor that does this:
public String intercept(ActionInvocation invocation) throws Exception {
// initialize request and response
final ActionContext context = invocation.getInvocationContext();
final HttpServletRequest request = (HttpServletRequest) context
.get(StrutsStatics.HTTP_REQUEST);
final HttpServletResponse response = (HttpServletResponse) context
.get(StrutsStatics.HTTP_RESPONSE);
// check scheme
String scheme = request.getScheme().toLowerCase();
// check method
String method = request.getMethod().toUpperCase();
// If the action class uses the SSLProtected marker annotation, then see
// if we need to
// redirect to the SSL protected version of this page
if (invocation.getAction() instanceof SSLProtected) {
if (HTTP_GET.equals(method) && SCHEME_HTTP.equals(scheme)) {
// initialize https port
String httpsPortParam = request.getSession().getServletContext().getInitParameter(HTTP_PORT_PARAM);
int httpsPort = httpsPortParam == null ? HTTPS_PORT : Integer.parseInt(httpsPortParam);
response.setCharacterEncoding("UTF-8");
URI uri = new URI(SCHEME_HTTPS, null, request.getServerName(), httpsPort, response.encodeRedirectURL(request.getRequestURI()), request.getQueryString(), null);
log.debug("Going to SSL mode, redirecting to " + uri.toString());
response.sendRedirect(uri.toString());
return null;
}
}
My problem is that I expect this
https://localhost/xhtml/path.do?ossesionid=value1
and got
https://localhost/xhtml/path.do;jsessionid=value1?osessionid=value1
And I'm Completly lost! help anyone?
i strongly suggest you to use S2-SSL plugin which is more flexible and provides a much better support to handle switch from SSL to non-SSL and vice-versa.
regarding generation of Jsessionid,JSESSIONID cookie is created/sent when session is created. Session is created when your code calls request.getSession() or request.getSession(true) for the first time. If you just want get session.You have ways to disable the creation of Jsessionid
There are number of way you can disable the creation of this id, please refer to this discussion thread.
I am still not sure what is the problem you are facing with this session-id as it is a very common case in web applications
is-it-possible-to-disable-jsessionid-in-tomcat-servlet

Categories

Resources