When my users try to do an action our website after their session has expired (for instance, if they left their browser open), the server responds with a HTTP Status 405 because the user is no longer logged in.
When this happens, I want to redirect the user to the login screen.
How can I recognize when a 405 error code is returned within GWT so I can then redirect the users?
Thanks
Edit:
Here's the filter I'm using:
public class AuthenticationFilter implements Filter {
public void destroy() {
}
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
if (req instanceof HttpServletRequest) {
boolean isLoggedIn = CustomSecurity.login((HttpServletRequest)req);
if (isLoggedIn) {
// TODO: How to redirect the user here???
}
}
chain.doFilter(req, res);
}
public void init(FilterConfig arg0) throws ServletException {
}
}
web.xml content:
<filter>
<filter-name>Authentication Filter</filter-name>
<filter-class>com.company.AuthenticationFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>Authentication Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
How can I make that redirect the user? Also, is there a way to force the whole browser to redirect? Since this goes into a widget, I think a Window.Location.assign('url') will only redirect the widget's HTML content.
I currently work on a GWT application that does something very similar to what you are asking. However, we handle these redirects in a standard javax.servlet.Filter subclass that we define in our web.xml as per usual and it is mapped to /* so it catches all requests (we use this filter for many other reasons as well).
I don't know if you are also using a Filter or perhaps even just some sort of catch-all Servlet in your GWT app, but if you are then the solution to your issue is quite easy. You will have a handle to the HttpServletRequest and you can see if the getSession(false) method returns null then you know that the user who sent the request no longer has a session. Then you can just do a standard response.sendRedirect(...) to your login page.
For GWT-RPC you will get a StatusCodeException whenever the server returns anything but a 200 response code. The status code can be retrieved from the exception and you can redirect via GWT by using Window.Location.assign(url). For RequestBuilder the status code cqan be accessed via response.getStatusCode().
EDIT: Here is code to redirect if GWT's window isn't that root window.
private native void redirect(String url)/*-{
var win = $wnd;
while (win.parent != null && win.parent != win)
win = win.parent;
win.location = url;
}-*/;
In your RequestCallback implementation, you can check response.getStatusCode(). We have Spring Security on the backend, which is wired up to force the user to a form based login page if they are not authenticated. Upon authentication, it redirects them back.
For example:
#Override
public void onResponseReceived(Request request, Response response) {
if (SC_UNAUTHORIZED == response.getStatusCode()) {
Window.Location.reload();
}
}
use a try / catch for coders sakes!
AsyncCallback<String> callback = new AsyncCallback<String>() {
public void onFailure(Throwable caught) {
try {
throw caught;
} catch (StatusCodeException statusCodeException) {
Util.handleException(statusCodeException);
} catch (Throwable exception) {
Util.handleException(exception);
}
}
public void onSuccess(String result) {
Window.alert("Result : " + result);
}
};
then to process your results
final static public void handleException(StatusCodeException exception) {
String errorMessage = exception.toString() +
"\n CODE: " + exception.getStatusCode() +
"\n MESSAGE: " + exception.getEncodedResponse();
Window.alert("ERROR: " + errorMessage);
}
final static public void handleException(Throwable exception) {
String errorMessage = exception.toString() + "\n";
for (StackTraceElement trace : exception.getStackTrace()) {
errorMessage += trace.getClassName() + " :: " +
trace.getMethodName() + " :: " +
trace.getLineNumber() + "\n";
}
Window.alert("ERROR: " + errorMessage);
}
remember that overloading is your friend! Dont forgot that if you do not compile your code in detailed or pretty you will get garbage for the stack trace. Unless of course someone figured out how to hack the JSStackEmulation class
http://code.google.com/p/google-web-toolkit/wiki/WebModeExceptions
Related
I have a old rest filter. Can the following be ported to shiro.ini? Or, is it possible to initialize/configure a filter in shiro.ini and extend it in java with doFilter()? It looks like there are some strange things going on with error handling and a "shiro bug". I'm not looking for someone to write my code, but a hint where to look would be helpful. Thank you.
UPDATE: MyRestFilter class:
#Override
#SneakyThrows(Throwable.class)
public void doFilter(final HttpServletRequest request, final HttpServletResponse response,
HttpSession httpSession, final FilterChain chain) throws ServletException, IOException
{
debugRequest(request);
List<String> sessionIds = Servlets.getRequestQueryStringMap(request).get(SESSION_ID);
String sessionId = sessionIds == null? null : Iterables.getOnlyElement(sessionIds);
String sessionIdHeader = request.getHeader(SESSION_ID_HEADER);
if(sessionId == null)
{
sessionId = sessionIdHeader;
}
ThreadContext.unbindSubject();
ThreadContext.bind(securityManager);
if(sessionId != null)
{
// strip out host / instance ID from the session
sessionId = sessionId.replaceFirst("\\.[\\w_\\-+]+$", "");
Subject subject = new Subject.Builder().sessionId(sessionId).buildSubject();
Session session = subject.getSession(false);
if(session != null)
{
session.touch(); // this should not be necessary, but due to Shiro bug, it is
StatefulUtil.pingStateful(session);
}
if(subject.getPrincipal() == null)
{
response.sendError(HttpStatus.SC_UNAUTHORIZED, "Unauthorized API Call");
}
else
{
try
{
subject.execute(() ->
{
SharedWebstats webStats = Beans.getReference(SharedWebstats.class);
webStats.webStats("mobile");
if (chain != null)
{
chain.doFilter(request, response);
}
return null;
});
}
catch(ExecutionException e)
{
// unwrap Shiro's ExecutionException, interferes
// with global exceptino handling mechanisms
Throwable cause = e.getCause();
if (cause != null)
{
throw cause;
}
else
{
throw e;
}
}
}
}
else
{
chain.doFilter(request, response);
}
ThreadContext.unbindSecurityManager();
}
//MyRestFilter also contains an init() which contains the real problem: how to set a session timeout that applies to the /api/ url only? (note, many of these SecurityManagerFactories are deprecated which is why I have to rewrite this).
#Override
public void init() throws ServletException
{
//securityManager = new WebIniSecurityManagerFactory("classpath:META-INF/shiro.ini").createInstance();
securityManager = WebIniSecurityManagerFactory("classpath:META-INF/shiro.ini").createInstance();
int sessionTimeout = WebXml.INSTANCE.getSessionTimeout();
DefaultSecurityManager dsm = (DefaultSecurityManager)securityManager;
DefaultSessionManager sm = (DefaultSessionManager)dsm.getSessionManager();
sm.setGlobalSessionTimeout(sessionTimeout * 60 * 1000);
realmCache.putAll(securityManager);
ssse.addSessionListeners(sm.getSessionListeners());
}
private org.apache.shiro.mgt.SecurityManager securityManager;
private #Inject RealmCacheHolder realmCache;
private #Inject ShiroSessionScopeExtension ssse;
Here is some configuration. Currently I have:
<filter>
<filter-name>ShiroFilter</filter-name>
<filter-class>com.dependencyfromhell.shiro.ee.filters.ShiroFilter</filter-class>
<async-supported>true</async-supported>
</filter>
<context-param>
<param-name>shiroConfigLocations</param-name>
<param-value>classpath:META-INF/shiro.ini</param-value>
</context-param>
<!-- Security - Enforce Filter Ordering -->
<filter-mapping>
<filter-name>ShiroFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
<dispatcher>INCLUDE</dispatcher>
<dispatcher>ERROR</dispatcher>
</filter-mapping>
<filter-mapping>
<filter-name>MyRestFilter</filter-name>
<url-pattern/>
</filter-mapping>
I'd like to have something like this.
# rest support
rest = MyRestFilter
#Rest api
/api/* = MyRestFilter
Could I configure MyRestFilter in shiro.ini for the session timeout, but then have a MyRestFilter class with the doFilter method? Thanks.
Your init() method looks to be the source of your problems.
You basically have two instances of Shiro configured. One from the ShiroFilter in your web.xml and another in your custom filter's init() method.
I'm guessing this was a workaround to inject your RealmCacheHolder and ShiroSessionScopeExtension, but it's unclear how they are used.
You can bootstrap Shiro using an IoC framework. (Shiro has direct support for Guice and Spring), if you are using CDI (or something else), it can still be done, but it would require some custom code (likely similar to what you have).
You would implement a WebEnvironment and set the shiroEnvironmentClass filter context param with its classname.
You might want to hit up the Shiro User list if want to get a little deeper into the WebEnvironment options.
I have created a very simple servlet that uses HTTP Post method. I have tested it on my local Apache Tomcat server using a simple HTML form that works. I want to integrate it with my GWT app. I am able to call it using FormPanel - in that case it downloads content and there is a flicker in my browser window.
I know I need to use RequestBuilder to access it. But my response.getStatusCode() in my overloaded public void onResponseReceived(Request request, Response response) method always return status as 0 and response.getText() return null
String url = "http://localhost:8080/servlets/servlet/ShapeColor";
builder = new RequestBuilder(RequestBuilder.POST, URL.encode(url));
try {
String json = getJSONString();
//builder.setTimeoutMillis(10000);
builder.setHeader("Access-Control-Allow-Origin", "*");
builder.setHeader("Content-type", "application/x-www-form-urlencoded");
builder.sendRequest(json, new RequestCallback() {
#Override
public void onError(Request request, Throwable exception) {
Window.alert("Couldn't retrieve JSON");
}
#Override
public void onResponseReceived(Request request, Response response) {
if (200 == response.getStatusCode()) {
System.out.println("res:"+response.getText());
} else {
System.out.println("err: " + response.getStatusCode()+","+response.getText());
}
}
});
//Request response = builder.send();
} catch (RequestException e) {
// TODO Auto-generated catch block
}
I have tried many thing including changing my servlet following CORS reference ( https://code.google.com/p/gwtquery/wiki/Ajax#CORS_%28Cross_Origin_Resource_Sharing%29 )
It always works on browser using my test.html, but not from my App. Although, onResponseReceived method always gets called
Thanks
KKM
Have you checked if your call in the app violates the Same-origin policy (http://en.wikipedia.org/wiki/Same-origin_policy) in some way? The GWT RequestBuilder uses XMLHttpRequest internally, so it does fall under the SOP.
Does your GWT app run inside the same domain (server + port) as the servlet? Does it use the same protocol (https or http)?
I got this problem while trying to access REST web service using GWT client.
I inspect the chrome page then i got the following error from console
XMLHttpRequest cannot load http://localhost:8080/RestWeb/webresources/generic/get. Origin http://127.0.0.1:8888 is not allowed by Access-Control-Allow-Origin.
Following is my client side code
public void onModuleLoad() {
RequestBuilder builder = new RequestBuilder(RequestBuilder.GET,
"http://localhost:8080/RestWeb/webresources/generic/get");
builder.setCallback(new RequestCallback() {
#Override
public void onResponseReceived(Request request, Response response) {
Window.alert("onResponseReceived");
}
#Override
public void onError(Request request, Throwable exception) {
}
});
builder.setHeader("Content-Type",
"text/plain,application/json,text/xml");
builder.setHeader("Access-Control-Allow-Methods",
"PUT, GET, POST, DELETE, OPTIONS");
builder.setHeader("Access-Control-Allow-Headers", "Content-Type");
builder.setHeader("Access-Control-Allow-Origin", "http://127.0.0.1:8888");
try {
builder.send();
} catch (RequestException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
My server side code is :
#Path("generic")
#WebService
public class GenericResource {
#Context
private UriInfo context;
#Context
private HttpServletResponse response;
private String content = "content";
/**
* Creates a new instance of GenericResource
*/
public GenericResource() {
}
#GET
#Path("/get")
#Produces("application/json,text/plain")
public String getXml() {
System.out.println("GET");
//response.addHeader(content, content);
return this.content + " from get method";
}
}
I tried in different ways to get answer. Please help me.
You need to change your server code to support CORS.
One option is a filter:
public class CorsFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
if (request.getHeader("Access-Control-Request-Method") != null && "OPTIONS".equals(request.getMethod())) {
// CORS "pre-flight" request
response.addHeader("Access-Control-Allow-Origin", "*");
response.addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
response.addHeader("Access-Control-Allow-Headers", "Content-Type");
response.addHeader("Access-Control-Max-Age", "1800");//30 min
}
filterChain.doFilter(request, response);
}
}
The web.xml needs adding the following too:
<filter>
<filter-name>cors</filter-name>
<filter-class>com.xxx.CorsFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>cors</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Since it's an old question, if one is facing similar issues today - he or she may want to consider using a nice "CORS Filter" that handles all CORS stuff for you in a completely transparent way. Here's the link
I have a GWT + Spring Security web app. I was trying to add:
<security:session-management invalid-session-url="/X.html"/>
However, when I try to test this. It seems I see a:
com.google.gwt.user.client.rpc.InvocationException
with message as the HTML content of X.html. Can someone please advise on how to fix this?
Because GWT communicates with a server via Ajax RPC requests, the browser will not be redirected to X.html. What you need to do in your service calls is throw an exception if they are not authorized and handle in in void onFailure(Throwable caught) method of your AsyncCallback.
If you want to redirect to /X.html try:
Window.Location.replace(GWT.getHostPageBaseURL()+"X.html");
However, if you want to send the request to the server use RequestBuilder:
String url = GWT.getHostPageBaseURL() + "/X.html";
RequestBuilder builder = new RequestBuilder(RequestBuilder.GET, URL.encode(url));
try {
Request request = builder.sendRequest(null, new RequestCallback() {
public void onError(Request request, Throwable exception) {
// invalid request
}
public void onResponseReceived(Request request, Response response) {
if (200 == response.getStatusCode()) {
// success
} else {
// sth went wrong
}
}
});
} catch (RequestException e) {
// couldn't connect to server
}
This problems occur when I click on Login button on the android emulator, it appear on Username textbox.
Kindly help me to resolve it...your help is appreciate ~
java.lang.IllegalArgumentException: Illegal character in scheme at index 0:
android.os.NetworkOnMainThreadException
--
Update 7.9.2011
I post my code over here:
http://pastebin.com/EX0ArwaE --> Login.java
http://pastebin.com/WgGctGHN --> CustomHttpClient.java
So first the Android stuff: You're getting this NetworkOnMainThreadException because you're trying to make a HTTP request on your main application thread which is the UI thread. You shouldn't do any blocking operations in this thread. Use an AsyncTask instead.
I'm not quite sure what's causing the IllegalArgumentException but I guess it's this line:
response = CustomHttpClient.executeHttpPost("http://127.0.0.1/es/check.php", postParameters);
You have probably changed the URL (localhost usually doesn't make sense on a phone). The scheme part is the http. Maybe you have something like " http://..." (note the leading space character) in the original code?
Short note on the PHP:
$sql = 'SELECT * from people WHERE username = "' .$_POST['un'] . '" and password = "' .md5($_POST['pw']) . '"';
That's what you call an SQL injection.
Update: Here's some example. Didn't test it, hopefully it works.
public class LoginLayout extends Activity {
EditText un,pw;
TextView error;
Button ok;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
un=(EditText)findViewById(R.id.et_un);
pw=(EditText)findViewById(R.id.et_pw);
ok=(Button)findViewById(R.id.btn_login);
error=(TextView)findViewById(R.id.tv_error);
ok.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
new LoginTask().execute(un.getText().toString(), pw.getText().toString());
}
});
}
private class LoginTask extends AsyncTask<String, Void, Object> {
protected Object doInBackground(String... params) {
ArrayList<NameValuePair> postParameters = new ArrayList<NameValuePair>();
postParameters.add(new BasicNameValuePair("username", params[0]));
postParameters.add(new BasicNameValuePair("password", params[1]));
String response = null;
try {
response = CustomHttpClient.executeHttpPost("http://127.0.0.1/es/check.php", postParameters);
String res = response.toString();
res = res.replaceAll("\\s+","");
return res;
} catch (Exception e) {
return e;
}
}
protected void onPostExecute(Object result) {
if (result instanceof String) {
if (result.equals("1")) {
error.setText("Correct Username or Password");
} else {
error.setText("Sorry!! Wrong Username or Password Entered");
}
} else if (result instanceof Exception) {
un.setText(result.toString());
}
}
}
}