Have been seeing a very unique situation. Need some input.
Background: We have tomcat servers on ec2 instances with ELB. Our application is a spring application which does not use sessions cause we didn't want the load balancer to track sessions. We use cookies to identify the user. We use Memcache to store some basic session information. For the most part everything works great.
Problem: In some remote cases 0.01% of the time (we analyzed) the response is getting mixed up between the users. And the users are usually coming from the same clientIP. We know this because our application is being used extensively by schools. The client cookie and the cookie we read in the servlet matches. We are positive our application is not looking up the wrong info in the DB. But the response (the view) that is getting rendered is for another user. We know both users are logged in at the same time.
Is this something Tomcat related? Or Network related?
Controller Code:
#RequestMapping("/kids")
public ModelAndView kids(HttpServletRequest request, HttpServletResponse response) {
StudentUserSession studentUserSession = BaseController.getStudentUserSession(request);
if(studentUserSession != null) {
ModelAndView mv = new ModelAndView("kids");
mv.addObject("studentUserSession", studentUserSession);
return mv;
} else {
return new ModelAndView("redirect:/signin");
}
}
JSP Code:
var acookie = readCookie("sp_utkn");
var studentId = ${studentUserSession.studentId};
$.getJSON("/getStudentProfile", function(data){
var sId = -1;
$.each(data, function (key, value) {
if(key == 'studentId'){
sId = value;
}
});
dojo.xhrGet({
url:"/logStudentEvent?cookie=" + acookie+"&stuId="+studentId+"&aId="+sId+"&ref="+otherInfo.join(','),
});
});
The JSON Get Call is getting the same object . aId matches with the cookie but studentId doesn't. The other data on the page are also for another user. But the cookies are consistent and accurate.
Related
I was wondering what the best way is for me to refresh an access token that is obtained through the client credentials flow within OAuth 2.0. I've read over the spec, but I can't seem to be able to find an answer for my particular situation.
For my specific case, I am using the Spotify Web API for my Android app in order to search for content (tracks, albums, and/or artists). In order to perform a search, I need an access token. Since I'm not interested in a Spotify user's data, I can use the client credentials flow to obtain the access token, which is explain in Spotify's terms here.
Because the access token can eventually expire, I had to figure out a way to refresh it once expiration occurred. What I'm ultimately wondering is if my approach is valid and if there's any concern with how I've approached this.
First and foremost, I stored my access token within SharedPreferences. Within onCreate(), I have the following:
#Override
protected void onCreate(Bundle savedInstanceState) {
// A bunch of other stuff, views being initialized, etc.
mAccessToken = getAccessToken();
// If the access token is expired, then we will attempt to retrieve a new one
if (accessTokenExpired()) {
retrieveAccessToken();
}
}
I've defined accessTokenExpired() and retrieveAccessToken() as follows:
private boolean accessTokenExpired() {
// If mAccessToken hasn't yet been initialized, that means that we need to try to retrieve
// an access token. In this case, we will return true;
if (mAccessToken == null) {
return true;
}
SharedPreferences preferences = getPreferences(Context.MODE_PRIVATE);
long timeSaved = preferences.getLong(PREFERENCES_KEY_TOKEN_RESPONSE_TIME_SAVED, 0L);
long expiration = preferences.getLong(PREFERENCES_KEY_TOKEN_RESPONSE_EXPIRATION, 0L);
long now = System.currentTimeMillis()/1000;
long timePassed = Math.abs(now - timeSaved);
if (timePassed >= expiration) {
return true;
} else {
return false;
}
}
One thing worth noting about retrieveAccessToken() is that I'm using Retrofit for my HTTP request:
private void retrieveAccessToken() {
// First, we obtain an instance of SearchClient through our ClientGenerator class
mClient = ClientGenerator.createClient(SearchClient.class);
// We then obtain the client ID and client secret encoded in Base64.
String encodedString = encodeClientIDAndSecret();
// Finally, we initiate the HTTP request and hope to get the access token as a response
Call<TokenResponse> tokenResponseCall = mClient.getAccessToken(encodedString, "client_credentials");
tokenResponseCall.enqueue(new Callback<TokenResponse>() {
#Override
public void onResponse(Call<TokenResponse> call, Response<TokenResponse> response) {
Log.d(TAG, "on Response: response toString(): " + response.toString());
TokenResponse tokenResponse = null;
if (response.isSuccessful()) {
tokenResponse = response.body();
Log.d(TAG, tokenResponse.toString());
mAccessToken = tokenResponse.getAccessToken();
saveAccessToken(tokenResponse);
}
}
#Override
public void onFailure(Call<TokenResponse> call, Throwable t) {
Log.d(TAG, "onFailure: request toString():" + call.request().toString());
mAccessToken = "";
}
});
}
Finally, saveAccessToken(tokenResponse) is sort of the complement of accessTokenExpired(), where I'm saving the values from the token response into SharedPreferences rather than retrieving them.
Are there any concerns with how I'm doing this? I got the idea from this SO post and slightly modified it. Spotify doesn't provide a refresh token in their access token response. Therefore, I can't make use of it here to reduce the number of access token requests I make.
Any input on this would be greatly appreciated!
Two considerations are:
you probably want some error handling around the requests you make using the access token that can handle the token expiring and do retries. The two situations where this will help are
when the token expires between checking if it's valid and your usage of it
when in the cycle of check the token is valid -> make some requests with the token -> repeat, you spend over an hour using the token. Another way you can do it is to calculate now + expected_api_request_time > token_expiration_time where expected_api_request_time would be a constant you set, but I think handling token expiry as an exception is better practice (you probably want to be able to make retries anyway in cases of network instability).
you can perform the calculations to work out when the token expires either when you retrieve the timeSaved and expiration from your local storage, or just calculate the time the token will expire initially and save that. This is relatively minor, both this and the way you've done it are fine I think.
I am testing a new functionality in my web app where i'm allowing the user to remain signed in even after the browing session expires. In order to achieve this, i have a "Keep me logged in" checkbox on my login page. Upon successful login, a cookie is created containing the userID and the login process continues normally. The first thing the first method in my main controller does is check for the presence of that cookie, read the userId in it, set the User object in the session correctly and bypass the login process, going directly to the home page. When the user signs out, the cookie is recreated with an expiration of 0 which means it's automatically deleted.
My Spring sessions last 30 minutes currently. I have successfully tested everything but i'm trying to replicate the Spring session expiring, which gets rid of the User object in memory and would normally force the user to login. In order to replicate it, i am deleting the JSESSIONID cookie from Chrome. When i do that, the HttpServletRequest object (which I am checking cookies on) is null.
Here is the first code that runs in my main controller :
#RequestMapping(value = {"/"}, method = RequestMethod.GET)
public String home(ModelMap model, HttpServletRequest request)
{
if (session.getAttribute("loggedInUser") != null)
{
return "home";
}
else
{
String userId = null;
for (Cookie cookie : request.getCookies())
{
if (cookie.getName().compareTo("MDHISStaySignedIn") == 0)
{
userId = cookie.getValue();
break;
}
}
if (userId != null)
{
session.setAttribute("loggedInUser", userService.findByUserId(userId));
return "redirect:/";
}
else
{
model.addAttribute("login", new Login());
model.addAttribute("register", new Register());
model.addAttribute("registering", false);
return "login";
}
}
}
Is it normal that my request object is null when deleting this cookie? Am i not correctly replicating the Spring session timing out by deleting it? This NullPointerException only happens when deleting this cookie and upon first running the web app, the controller does not thrown the exception when the cookie is created by the first run. Should i somehow be checking for this null value and redirecting the controller back to this method if it is?
Thanks!
The solution is to add the following method parameter :
#CookieValue(value = "MDHISStaySignedIn", defaultValue = "notFound") String cookie
This will assign the content of the cookie to the value if it's present and assign "notFound" if it is not. This way one can access cookies when the request object is null!
I'm making a queue management system. I've hit on stump here. I'm letting the customer/user select their desired service whose token they want to get, but the thing is each time the servlet is called it re-initializes the service-option objects and the token number for that chosen services goes back to 1. How can I store the token count so that it doesn't goes back to 1 again.
public class XmlServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
String Service_Option = request.getParameter("Service_Option");
out.println("You selected Service is: "+ Service_Option);
Customer_Console cc1 = new Customer_Console();
Customer_Console cc2 = new Customer_Console();
Customer_Console cc3 = new Customer_Console();
if(Service_Option.equals("Cash Withdrawal"))
{
cc1.setConsole(1,Service_Option);
Database_Manager.Insert(cc1);
}
else if(Service_Option.equals("Account Service"))
{
cc2.setConsole(2,Service_Option);
Database_Manager.Insert(cc2);
}
else
{
cc3.setConsole(3,Service_Option);
Database_Manager.Insert(cc3);
}
}
}
The Console class contains
private int serviceNum;
private String Service_Option;
private Token token;
and the setConsole method is
public void setConsole(int sNum,String sName)
{
serviceNum = sNum;
Service_Option = sName;
token.incrementToken();
}
UPDATE
I'm having problems with dealing the session for more then 1 customer consoles
HttpSession session = request.getSession(false);
if(session == null)
{
session = request.getSession(true);
Integer count = 0;
session.setAttribute("tokenCount",count);
}
if(Service_Option.equals("Cash Withdrawal"))
{
Integer count = (int)session.getAttribute("tokenCount");
count = new Integer(count.intValue() + 1);
cc1.setToken(count);
Database_Manager.Insert(cc1);
session.setAttribute("tokenCount",count);
}
Also how can I reset the session that every time I restart tomcat I get started with the token number 1.
If I'm supposed to use only one session for all three consoles then how can I do that?
I tried with placing the console objects
if(session == null)
{
session = request.getSession(true);
session.setAttribute("cc1", cc1);
}
if(Service_Option.equals("Cash Withdrawal"))
{
cc1.issueToken();
session.setAttribute("cc1", cc1);
cc1 = (Customer_Console)session.getAttribute("cc1");
Database_Manager.Insert(cc1);
}
But still it doesn't save the increments, again reinitializes the token, why is that?
Store Your service-option Object in session Scope So every time your servlet it called you can access the Object using
HttpSession session = request.getSession();
Console service-option=null;
service-option=(Console)session.getAttribute("console");
if(service-option==null){
service-option=new Console();
}
// After doing your work(setting some variables etc)of Console Class Object
// you can set that Object in Session Like this
session.setAttribute("console", service-option); // here "console" is key
In this way it is reinitialized only for the first time
As Java Docs Says
void setAttribute(String name,Object value)
Binds an object to this session, using the name specified. If an
object of the same name is already bound to the session, the object is
replaced.
Update
A HttpSession is created when calling request.getSession() and it get destroyed/invalidated when you call session.invalidate() or session timeout occurs .
Now coming to
Also how can I reset the session that every time I restart tomcat I
get started with the token number 1. If I'm supposed to use only one
session for all three consoles then how can I do that?
It will automatically get restarted but there will be no token number exist into it because it is fresh , if you need to access token number from your entire application then i suggest you to use Servlet Context
An object of ServletContext is created by the web container at time of
deploying the project.This object can be used to get configuration
information from web.xml file. There is only one ServletContext object
per web application.
If any information is shared to many servlet, it is better to provide
it from the web.xml file using the element.
Now your another confusion
I'm having problems with dealing the session for more then 1 customer
consoles
Please read Session Management in Java . As this topic is too broad so i have given you the link to read it yourself
I'm trying to enable SSO under Tomcat such that users who go to http://mydomain.com and http://www.mydomain.com will have their session cookie available for requests made to http://subdomain.mydomain.com. All three of these domains go to the same webapp, so ideally I'd like to not mess with SSO at all and just set the domain on the standard JSESSIONID cookie.
However, that doesn't seem possible, so I'm trying to enable Tomcat's SSO Valve. The problem is that the Valve requires a definition of a Realm, and a Realm is supposed to specify a database of users and roles. However, I am not using container-based authentication nor role-based authorization, so I do not need or want to configure a Realm. All I want is for the session cookie(s) to be able to be shared across each of these different subdomains.
Is there any straightforward way to do this?
Edit
My current workaround for this is to have the server redirect every incoming request to the "canonical" server name. This works well enough, but obviously it is not actually solving the problem.
We were having the same problem and created a Tomcat Valve that would overwrite or set the Domain part of the session Cookie. Quite a simple thing and it already works for many years. The code goes like this:
public class CrossSubdomainSessionValve extends ValveBase {
public CrossSubdomainSessionValve() {
super();
info = "common-tomcat-CrossSubdomainSessionValve";
}
#Override
public void invoke(Request request, Response response) throws IOException, ServletException {
// cookie will only need to be changed, if this session is created by this request.
if (request.getSession(true).isNew()) {
Cookie sessionCookie = findSessionCookie(response.getCookies());
if (sessionCookie != null) {
String cookieDomainToSet = getCookieDomainToSet(request.getServerName());
if (cookieDomainToSet != null) {
// changing the cookie only does not help, because tomcat immediately sets
// a string representation of this cookie as MimeHeader, thus we also
// have to change this representation
replaceCookie(response.getCoyoteResponse().getMimeHeaders(), sessionCookie, cookieDomainToSet);
}
}
}
// process the next valve
getNext().invoke(request, response);
}
protected Cookie findSessionCookie(Cookie[] cookies) {
if (cookies != null)
for (Cookie cookie : cookies)
if (Globals.SESSION_COOKIE_NAME.equals(cookie.getName())) {
return cookie;
return null;
}
protected void replaceCookie(MimeHeaders headers, Cookie originalCookie, String domainToSet) {
// if the response has already been committed, our replacementstrategy will have no effect
// find the Set-Cookie header for the existing cookie and replace its value with new cookie
for (int i = 0, size = headers.size(); i < size; i++) {
if (headers.getName(i).equals("Set-Cookie")) {
MessageBytes value = headers.getValue(i);
if (value.indexOf(originalCookie.getName()) >= 0) {
if (originalCookie.getDomain() == null) {
StringBuilder builder = new StringBuilder(value.getString()).append("; Domain=").append(domainToSet);
value.setString(builder.toString());
} else {
String newDomain = value.getString().replaceAll("Domain=[A-Za-z0-9.-]*", "Domain=" + domainToSet);
value.setString(newDomain);
}
}
}
}
}
protected String getCookieDomainToSet(String cookieDomain) {
String[] parts = cookieDomain.split("\\.");
if (parts.length >= 3) {
return "." + parts[parts.length - 2] + "." + parts[parts.length - 1];
}
return null;
}
public String toString() {
return ("CrossSubdomainSessionValve[container=" + container.getName() + ']');
}
}
The algorithm works like this:
- Only if the session is new - find the session cookie
- Get the requested host name
- Split the host name with '.'
- If it has at least 3 parts (like www.google.de), remove first part (to .google.de)
- Reset the cookie
In your Context configuration you can apply the valve like this
<Valve className="my.package.CrossSubdomainSessionValve" httpOnlyEnabled="true" />
Caveat: In the code the Valve creates a session if no session was created before and does not care if you need a session at all...
Hope that helps... Good luck!
I’m building a J2EE web application which uses Oracle SSO with an OID back-end as the means for authenticating users.
If a user wants to use the application, first he must provide a valid login/password at SSO's login page.
When the user is done using the application, he may click on the logout button; behind the scenes, the action associated with this button invalidates the user’s session and clears up the cookies using the following Java code:
private void clearCookies(HttpServletResponse res, HttpServletRequest req) {
res.setContentType("text/html");
for (Cookie cookie : req.getCookies()) {
cookie.setMaxAge(0);
cookie.setPath("/");
cookie.setDomain(req.getHeader("host"));
res.addCookie(cookie);
}
}
Also, I have an onclick JavaScript event associated with the logout button, which is supposed to delete the SSO cookies by calling the delOblixCookie() function (as found in some Oracle forum):
function delCookie(name, path, domain) {
var today = new Date();
// minus 2 days
var deleteDate = new Date(today.getTime() - 48 * 60 * 60 * 1000);
var cookie = name + "="
+ ((path == null) ? "" : "; path=" + path)
+ ((domain == null) ? "" : "; domain=" + domain)
+ "; expires=" + deleteDate;
document.cookie = cookie;
}
function delOblixCookie() {
// set focus to ok button
var isNetscape = (document.layers);
if (isNetscape == false || navigator.appVersion.charAt(0) >= 5) {
for (var i=0; i<document.links.length; i++) {
if (document.links.href == "javascript:top.close()") {
document.links.focus();
break;
}
}
}
delCookie('ObTEMC', '/');
delCookie('ObSSOCookie', '/');
// in case cookieDomain is configured delete same cookie to all subdomains
var subdomain;
var domain = new String(document.domain);
var index = domain.indexOf(".");
while (index > 0) {
subdomain = domain.substring(index, domain.length);
if (subdomain.indexOf(".", 1) > 0) {
delCookie('ObTEMC', '/', subdomain);
delCookie('ObSSOCookie', '/', subdomain);
}
domain = subdomain;
index = domain.indexOf(".", 1);
}
}
However, my users are not getting logged out from SSO after they hit the logout button: although a new session is created if they try to access the index page, the SSO login page is not presented to them and they can go straight to the main page without having to authenticate. Only if they manually delete the cookies from the browser, the login page shows up again - not what I need: the users must provide their login/password every time they log out from the application, so I believe there must be something wrong in the code that deletes the cookies.
I’d greatly appreciate any help with this problem, thanks in advance.
Oracle have two web SSO products - Oracle Access Manager and Oracle Single Sign On. The Javascript code you have posted is for Access Manager, so it won't help you. Besides, you shouldn't need to do anything in Javascript to log the user out.
Have a look at the logout section of the OSSO docs. It recommends using the following code:
// Clear application session, if any
String l_return_url := return url to your application
response.setHeader( "Osso-Return-Url", l_return_url);
response.sendError( 470, "Oracle SSO" );
You need a page, with a name of logout, that includes those JavaScript functions.
That's what the documentation says:
The WebGate logs a user out when it receives a URL containing
"logout." (including the "."), with the exceptions of logout.gif and
logout.jpg, for example, logout.html or logout.pl. When the WebGate
receives a URL with this string, the value of the ObSSOCookie is set
to "logout.
Cookies don't "delete" untill the browser is closed.