How exactly Spring MVC handle cookie? - spring

I am studying how Spring handle cookie on a tutorial and I have some doubts.
In this example there is this CookieControllerExample that perform some cookie operation when are performed.
#Controller
public class CookieControllerExample {
#RequestMapping(value = "/readcookie", method=RequestMethod.GET)
public ModelAndView readCookie(#CookieValue(value = "URL") String URL, HttpServletRequest request,
HttpServletResponse response) {
System.out.println("CookieControllerExample readcookie is called");
return new ModelAndView("/cookie/cookieView", "cookieValue", URL);
}
#RequestMapping(value = "/writecookie", method=RequestMethod.GET)
public String writeCookie(HttpServletRequest request,
HttpServletResponse response) {
System.out.println("CookieControllerExample writeCookie is called");
Cookie cookie = new Cookie("URL", request.getRequestURL().toString());
response.addCookie(cookie);
return "/cookie/cookieView";
}
#RequestMapping(value = "/readAllCookies", method=RequestMethod.GET)
public ModelAndView readAllCookies(HttpServletRequest request) {
System.out.println("CookieControllerExample readAllCookies is called");
Cookie[] cookies = request.getCookies();
System.out.println("All Cookies in your browsers");
String cookiesStr = "";
for(Cookie cookie : cookies){
System.out.println(cookie.getName() + " : " + cookie.getValue());
cookiesStr += cookie.getName() + " : " + cookie.getValue() + "<br/>";
}
return new ModelAndView("/cookie/cookieView", "cookieValue", cookiesStr);
}
}
From what I have understand the first method (readcookie()) read the content of a coockie named URL stored inside my computer.
The second method (writecookie()) create a cookie named URL and store it on my computer.
And the third method read the contet of all the cookies stored on my computer.
I have 2 doubts:
1) I know that cookies are textual file. Where exactly are stored?
2) Why the **writeCookie() method, after create a new cookie, add it to the response? How can I examinate the cookie stored on my system?
response.addCookie(cookie);
I think that it could depend by the fact that the response come back to the user browser and it retrieve the cookie from this response and create a textual file somewhere on my system. Is it true or am I missing something?

You asked:
1) I know that cookies are textual file. Where exactly are stored?
The cookies are stored by the clients browser, somewhere at the clients machine. - The exact location depends on the browser
2) Why the **writeCookie() method, after create a new cookie, add it to the response? How can I examinate the cookie stored on my system?
As I told in answer for question 1), the cookie is stored at client side. So it's values need to be send to the client (in the header of the http-response). And that is the reason why the cookie (object) is added to the http response.
I strongly recommend you to read the wikipedia article about Http Cookies. And do not get confused by mixing cookies and sessions (sessions are often implement with a session-tracking-cookie, but its data resist on the server side.)

Related

How to add cookies to browser and redirect to Shopify store

We are trying to create an affiliate URL, so that we can track users when they install our app on their Shopify store. We provide a URL, https://ourappname.com/services/convert?ref=xxxx, and when user clicks this url, we set browser cookies and then they are redirected to https://apps.shopify.com/appname.
#GetMapping("/convert")
public void setCookieAndRedirect(HttpServletRequest request,HttpServletResponse response,
#RequestParam(value ="ref", required = false) String ref) {
System.out.println(" REceived ref code " + ref +" ::: redirecting to appstore.");
Cookie cookie = new Cookie("RefCookie", ref);
try {
//add cookie to response
response.addCookie(cookie);
cookie.setPath("/");
cookie.setMaxAge(60 * 60 * 24 * 365 * 10);
URL urlToRedirect = new URL("https://apps.shopify.com/appname");
cookie.setDomain(urlToRedirect.getHost());
response.addCookie(cookie);
response.sendRedirect("https://apps.shopify.com/appname");
}catch(Exception ex) {
ex.printStackTrace();
}
}
Our plan was - once they install the app and we get the control in our server, we are checking the browser cookies and if the ref is saved with in our database, we provide a special plan for the user.
#GetMapping("/checkForDiscounts")
public ResponseEntity<String> checkForDiscounts(HttpServletRequest request){
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for(Cookie ck : cookies) {
System.out.println(" Cookie Name = "+ck.getName()+" :::: Value = "+ck.getValue());
if("RefCookie".equals(ck.getName())) {
//provide special discount plan for user
}
}
}else {
System.out.println(" Cookie Not Found");
}
return new ResponseEntity<String>("success", HttpStatus.OK);
}
But unfortunately, cookies will not work for redirect urls, as per this answer. Is there any other solution for this issue?
On Calling https://ourappname.com/services/convert?ref=xxxx
provide a response with redirect status(3xx) with Location header as https://apps.shopify.com/appname which will set that cookies for ourappname.com and it also redirects
Cookies will be restricted to accesible for ourappname.com and it's subdomains for security and privacy reasons.
But if there is an XHR call happening from https://apps.shopify.com/appname to ourappname.com then that call will send the cookies along with the request.
Important : Since this is CORS, proper headers,cookie flags and parameters need to be set in FE/BE inorder to get it working.
Follow this answer as a guide for that.

RestAssured testing, get user token

What I want to do: I want to test my endpoint using RestAssured. The key is that the endpoint is available only for users who are logged in. For logging in I'm using spring security default endpoint with custom successHandler in which I'm setting some random token, saving it to database and returning in header "User-Token". I'm not creating a session on the back end. When I want to access a secured endpoint, front-end makes a call to it, with "User-Token" header. Then I'm using the token for checking in the database. Each token is different and random. Also I don't use any spring-security things for token. Now I want to test this behavior.
Technologies: React & Redux, Spring Boot, RestAssured, JUnit, Tomcat
What's not working: First of all, I'm not really sure how to obtain the token. I mean I can force it by hand to database to some test user, but AFAIK it's a bad bad practice. I read the documentation and come across part about auth().form. But below it was mentioned that it's not the best approach as have to made to the server in order to retrieve the webpage with the login details and it's not possible - webpage is totally separated from backend. I did try the approach nevertheless but it didn't work.
#Before
public void LogInUser(){
String loginUrl = "http://localhost:8080/login";
userToken =
given().auth().form("username","password").
when().get(loginUrl).getHeader("User-Token");
System.out.println(userToken);
}
So then I thought that maybe I don't need auth() at all - I don't need session, so calling the endpoint itself with data should be enough. I checked how data is passed from front-end to back-end and did this:
Form Data: username=something&password=something
#Before
public void LogInUser(){
String loginUrl = "http://localhost:8080/login";
userToken =
given().parameter("username=oliwka&password=jakies")
.when().get(loginUrl).getHeader("User-Token");
System.out.println(userToken);
}
And while it's passing, userToken is null. It's declared as class variable not method variable and it's String.
How can I obtain token for user and test my endpoint for which I need a token?
You can use below procedure to get the access token.
Step 1 : Create a method that will accept a json string and parse the data and return the access token. below is the method. You can use your preferable json parser library.
public String getAccessToken(String jsonStr) {
JSONParser parser = new JSONParser();
Object obj = null;
try {
obj = parser.parse(jsonStr);
} catch (ParseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
JSONObject jsonObject = (JSONObject) obj;
String accessToken = (String) jsonObject.get("access_token");
System.out.println("access_token : " + accessToken);
return accessToken;
}
Step 2 : Now call your login api with username and password like below
String loginUrl = "http://localhost:8080/login";
Response res = null;
String returnValue = "";
response = given().param("username", "yourUserName")
.param("password", "yourpassword")
.param("client_id", "If Any otherwise skip it")
.param("grant_type", "If Any otherwise skip it")
.param("clear_all", "true")
.post(loginUrl);
returnValue = response.body().asString();
String accessToken = getAccessToken(returnValue);
Please let me know if you can get your desired access token.

How to redirect from spring ajax controller?

I have a controller with #ResponseBody annotation. What I want to do is if this user doesn't exists process user's Id and return a json object. If exists redirect to user page with userInfo. Below code gives ajax error. Is there any way to redirect to user page with userInfo?
#RequestMapping(value = "/user/userInfo", method = {RequestMethod.GET})
#ResponseBody
public String getUserInfo(HttpServletRequest request, HttpServletResponse response, ModelMap modelMap) {
if(...){
.....//ajax return
}else{
modelMap.addAttribute("userInfo", userInfoFromDB);
return "user/user.jsp";
}
}
Well, this method is annotated with #ResponseBody. That means that the String return value will be the body of the response. So here you are just returning "user/user.jsp" to caller.
As you have full access to the response, you can always explicitely do a redirect with response.sendRedirect(...);. It is even possible to explicitely ask Spring to pass userInfoFromDB as a RedirectAttribute through the flash. You can see more details on that in this other answer from me (this latter is for an interceptor, but can be used the same from a controller). You would have to return null to tell spring that the controller code have fully processed the response. Here it will be:
...
}else{
Map<String, Object> flash = RequestContextUtils.getOutputFlashMap(request);
flash.put("userInfo", userInfoFromDB);
response.redirect(request.getContextPath() + "/user/user.jsp");
return null;
}
...
The problem is that the client side expects a string response that will not arrive and must be prepared to that. If it is not, you will get an error client side. The alternative would then be not to redirect but pass a special string containing the next URL:
...
}else{
Map<String, Object> flash = RequestContextUtils.getOutputFlashMap(request);
flash.put("userInfo", userInfoFromDB); // prepare the redirect attribute
return "SPECIAL_STRING_FOR_REDIRECT:" + request.getContextPath() + "/user/user.jsp");
}
and let the javascript client code to process that response and ask for the next page.

Losing Google Analytics tracking due to Spring Security login redirect

I have a mailing campaign where all links include google analytics tracking code such as:
http://example.com/account/somePage.html?utm_source=example&utm_medium=email&utm_campaign=reminder
The context /account/** is protected via Spring security and once the user clicks on the link on the email, he is re-directed to login BEFORE actually seeing somePage.html. This way the first page that is displayed is something like /login.do which does not have the analytics tracking code. Therefore google does not track my source, medium and campaign parameters.
Any ideas how to solve?
Based on http://support.google.com/analytics/answer/1009614?hl=en , I updated my LoginController that shows the login page to redirect to /login?GOOGLE_PARAMATERS:
private static final String ALREADY_REDIRECTED = "ALREADY_REDIRECTED";
....
#RequestMapping(value = "/login", method = RequestMethod.GET)
public ModelAndView loginView(HttpServletRequest request, HttpServletResponse response){
....
Boolean alreadyRedirected = (Boolean) request.getSession().getAttribute(ALREADY_REDIRECTED);
if (alreadyRedirected==null){
SavedRequest savedRequest = new HttpSessionRequestCache().getRequest(request, response);
if (savedRequest!=null){
String source[] = savedRequest.getParameterValues("utm_source");
if (source!=null && source.length>0){
// we need to redirect with login instead
String mediums[] = savedRequest.getParameterValues("utm_medium");
String medium = mediums.length==0 ? "" : mediums[0];
String campaigns[] = savedRequest.getParameterValues("utm_campaign");
String campaign = campaigns.length==0 ? "" : campaigns[0];
String redirect = "redirect:/login?utm_source=" + source[0] + "&utm_medium=" + medium + "&utm_campaign=" + campaign;
mav.setViewName(redirect);
// mark not to do twice
request.getSession().setAttribute(ALREADY_REDIRECTED, new Boolean(true));
return mav;
}
}
}
We have similar problem and have solved with the next solution.
We have a signup form via Ajax, and in the callback if everything is OK we auto-login the user and lost Google Analytics tracking code for Funnel visualization because of Spring Security session invalidation and set up a new cookie.
What we have done by JS just before auto-login call the new user this
_gaq.push(['_trackPageview', '/signupDone']);
https://gist.github.com/moskinson/5418938
signupDone is a fake url that does not exists.
This way GA receive a call of a new url is loaded and we can track the funnel!
http://packageprogrammer.wordpress.com/2013/04/19/seguimiento-con-google-analytics-a-traves-del-login-con-spring-security/

Redirect to the page containing # (hash) sign after login

I am using Spring Security and wondering how can I implement redirection after succesfull login to the source page if that page contains # (hash) sign.
Right now I use always-use-default-target="false" and it works fine on URL kind of: /path/to/page/.
But when the URL become to #/path/to/page it doesn't make any redirections.
Is there any way to fix it?
Here is the solution I used at the end:
$(document).ready(function(){
$('#auth-form').submit(function() {
var el = $(this);
var hash = window.location.hash;
if (hash) el.prop('action', el.prop('action') + '#' + unescape(hash.substring(1)));
return true;
});
});
This snippet addes the hash to authorization form's action attribute and Spring redirect you to the URL of kind: #/path/to/page without any problem.
Maybe this is the old question, but during my recent research in this topic, I found that the problem is common and still exists (especially in case of modern AngularJS front-end apps with back-end security). I'd like to share my solution with you.
On the login page, e.g., /login.html, put following code before the </body> tag:
<script type="text/javascript">
var hash = window.location.hash;
document.cookie="hashPart=" + window.btoa(hash);
</script>
Note (1): btoa() function works in IE >= 10 (http://www.w3schools.com/jsref/met_win_btoa.asp), for older browsers use jQuery equivalent.
Note (2): The encryption of the # part of URL is necessary as it may contain special characters, which are not allowed to be stored in cookie value string.
From the server side you have to modify onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) method of class implementing AuthenticationSuccessHandler interface.
In my case, I simply extend the SavedRequestAwareAuthenticationSuccessHandler class and override the onAuthenticationSuccess method using its original code. Then I obtain the hashPart cookie value from the request, decode it and add to resolved redirect URL. My code fragment below:
#Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication)
throws ServletException, IOException {
// ... copy/paste original implementation here, until ...
// Use the DefaultSavedRequest URL
String targetUrl = savedRequest.getRedirectUrl();
for (Cookie cookie : req.getCookies()) {
if (cookie.getName().equals("hashPart")) {
targetUrl += new String(Base64Utils.decodeFromString(cookie.getValue()));
cookie.setMaxAge(0); // clear cookie as no longer needed
response.addCookie(cookie);
break;
}
}
getRedirectStrategy().sendRedirect(request, response, targetUrl);
}
Finally, just inject your success handler class to your Spring Security configuration, as described in: https://stackoverflow.com/a/21100458/3076403
I'm looking forward to your comments or other solutions to this problem.

Resources