2

I am using AngularJS for front-end and Java EE for back-end. I have created two methods to login and logout using RESTful services:

@POST
@Path("login")
@Produces("application/json")
public Response login(@Context HttpServletRequest req, UserTable user) {
    if (req.getUserPrincipal() == null) {
        try {
            req.login(user.getUsername(), user.getPassword());
        } catch (ServletException e) {
            return Response.status(Response.Status.BAD_REQUEST).type("text/plain").entity("Login or Password is incorrect").build();
        }
    } else {
        return Response.status(Response.Status.OK).type("text/plain").entity("You are already logged in").build();
    }
    return Response.status(Response.Status.OK).type("text/plain").entity("Login successfull").build();
}

@GET
@Path("logout")
@Produces("application/json")
public Response logout(@Context HttpServletRequest req) {
    try {
        req.logout();
        req.getSession().invalidate();
    } catch (ServletException e) {
        return Response.status(Response.Status.BAD_REQUEST).type("text/plain").entity("Can not logout").build();
    }
    return Response.status(Response.Status.OK).type("text/plain").entity("Logout successfull").build();
}

On front-end side, I use these methods:

    $http.post(apiUrl + 'usertable/login', {
        username: 'admin',
        password: 'admin'
    });

    $http.get(apiUrl + 'usertable/logout').success(function (a) {
        var o = a;
    });

I can successfully login:

FINEST:   JDBC login succeeded for: admin groups:[Admin]
FINE:   JAAS login complete.
FINE:   JAAS authentication committed.
FINE:   Password login succeeded for : admin

But when I try to logout, or login again, getUserPrincipal() returns null. What am I doing wrong?

Yan Ivan Evdokimov
  • 179
  • 1
  • 4
  • 15

2 Answers2

4

Create the HTTP Session manually before you call the login method.

You can do this by calling the HttpServletRequest.getSession() method, like this:

req.getSession(true); // Creates a new HTTP Session BEFORE the login.
req.login(user.getUsername(), user.getPassword());

Make sure the server is returning the JSESSIONID cookie in the response. After that the browser will take care of it and send it to the server without you having to worry about it or handling it yourself.

Response from the Login

enter image description here

Request for further calls (service calls, check the login, logout, etc)

enter image description here

Update

Maybe you need to tell AngularJS to send the Cookies. For what I have read just now it seems that the default is not send cookies.

Try this post $http doesn't send cookie in Requests

Update 2

Cross-domain policies can be a pain but they need to exist in order to secure access to content in the web. The Java server is behaving normally, authentication is happening and the cookies are being shared. It is all good with the Java server since the beginning.

The problem is the same for any middleware technology when it come to cross-domain.

The browser is blocking your application because your client domain (where your JavaScript is running) is not allowed to access the Java server domain. The applications (your AngularJS and the Java Server) are executing in different domains, therefore JavaScript calls "under the hoods" are blocked by browsers. Old IE versions used to allow it in the past.

Example:

  • JavaScript code is loaded from localhost or local file system
  • Java Server is running at http://192.168.1.15:8080 (replace this for your IP or any address)

Maybe JSONP can save you:

JSONP allows a page to receive JSON data from a different domain by adding a element to the page which loads a JSON response with a callback from a different domain.

Cross-domain policy will verify: protocol, hostname and port, for both applications. If one is different, security takes place and the program fails.

More info here: Same-origin policy

There is also nothing wrong with your AngularJS. I ran across this situation in the past, and you can replicate it using plain Java Script (this is functional code that you can adapt to you problem):

function loadXMLDoc() {
    var xmlhttp=new XMLHttpRequest();
    xmlhttp.onreadystatechange=function() {
        if (xmlhttp.readyState==4 && xmlhttp.status==200) {
            document.getElementById("myDiv").innerHTML=xmlhttp.responseText;
        }
    }
    xmlhttp.open("POST","http://192.168.9.117:8080/restful-login-web/rest/loginservice/login",true);
    xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded");
    xmlhttp.send("username=evandro&password=senha123");  
}

The following error will show up in your browsers console:

XMLHttpRequest cannot load http://192.168.9.117:8080/restful-login-web/rest/loginservice/login. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'null' is therefore not allowed access.

It will also happen with GET calls.

You can allow your external application to use JavaScript to communicate with the Java server setting the Access-Control-Allow-Origin somewhat like this (.header("Access-Control-Allow-Origin", "*")):

return Response.status(Response.Status.OK).header("Access-Control-Allow-Origin", "*").type("text/plain").entity("Login successfull").build();

Warning: Replace the "*" for the domain in which your application client is running.

I suggest you to investigate on the cross-domain policy subject. If you can, maybe a simple solution is to run the client JavaScript inside Java container. Then both applications will be automatically in the same domain.

What I mean with that is that if you use a JSP/Servlet to publish your application client Java Script, you wouldn't have this problem. If you are developing the JavaScript in plain files, just wrap them inside the Java Web application. Since you will execute a Java Script code retrieved from "xyz.com" domain, the Java code running in the same "xyz.com" will be allowed to exchange information by the browser. This will work.

I am sorry, it took me more time than it should to realize you were doing an AJAX call to a different domain.

If you need to communicate different domains (maybe the server is an external service) and you can't find any help I suggest you to open another question specifically for the cross-domain issue. As I understand this problem is no longer associated with the Java server itself.

Community
  • 1
  • 1
Evandro Pomatti
  • 13,341
  • 16
  • 97
  • 165
  • Server has successfully returned JSESSIONID (like in your first screenshot). However, when I called logout, getUserPrincipal() still had null value – Yan Ivan Evdokimov Feb 02 '15 at 18:29
  • @YanIvanEvdokimov Is the JSESSIONID cookie beeing sent in the logout request, after receive it in the login? – Evandro Pomatti Feb 02 '15 at 19:07
  • Unfortunately, no. It looks like in your screenshot, but without Cookies tab – Yan Ivan Evdokimov Feb 02 '15 at 19:16
  • @YanIvanEvdokimov Check my update. I am testing with JSP pages, it seems you need to tell the $http in AngularJS to send Cookies. – Evandro Pomatti Feb 02 '15 at 19:20
  • It is throwing now this error: `A wildcard '*' cannot be used in the 'Access-Control-Allow-Origin' header when the credentials flag is true.` – Yan Ivan Evdokimov Feb 02 '15 at 19:32
  • This is an x-domain issue. You need to specify which URL you are allowing access to cookies or JS. Try [this link](http://stackoverflow.com/questions/13734686/communication-between-angularjs-and-a-jersey-webservice-which-are-on-a-different/14111039#14111039) which shows a different approach on allowing credentials. – Evandro Pomatti Feb 02 '15 at 19:47
  • I am still receiving this error, but now I can send cookie to logout request. However, getUserPrincipal still equals to null – Yan Ivan Evdokimov Feb 02 '15 at 21:55
  • Thank you! You saved my life! The problem was that my client was deployed on `http://localhost:8383/client/`, but server on `http://localhost:8080/server/`. When I have changed client port to 8080, everything worked like a charm! – Yan Ivan Evdokimov Feb 03 '15 at 12:11
1

RESTful services are meant to be stateless. Requests which require authorization and authentication send authentication tokens on every request which is then verified by the server. The idea behind this is that given a token, you can make request from any client(desktop, phone) to get the resource. This will be handy for third party integration and a must if your application will be behind load balancers.

This particular read is quite helpful to understand some concepts about RESTful services:

Do sessions really violate RESTfulness?

When using session, it has to be tied to single server. From looking at your problem and reading further comments it seems the application is losing track of the session connected with the user.

Community
  • 1
  • 1
ZakiMak
  • 2,072
  • 2
  • 17
  • 26