Связь между AngularJS и веб-сервисом Jersey, которые находятся в другом домене. Не удается получить доступ к правильной сессии

В последнее время я играл с AngularJS и Java EE 6. Я создал веб-сервис с Джерси и развернул проект на Glassfish. Поскольку мне нужна была какая-то аутентификация, а реализация OAuth или JDBCRealm казалась излишней, я решил просто создать сеанс, если пользователь успешно вошел в систему.

@POST
@Path("/login")
@Produces({MediaType.APPLICATION_JSON})
@Consumes({MediaType.APPLICATION_JSON})
public Response login(LoginDAO loginData, @Context HttpServletRequest req) {
    req.getSession().invalidate();
    loginData.setPassword(PasswordGenerator.hash(loginData.getPassword()));
    User foundUser = database.login(loginData);
    if(foundUser == null) {
        return Response.status(Status.CONFLICT).build();
    }
    req.getSession(true).setAttribute("username", foundUser.getUsername());
    return Response.ok().build();
}

@GET
@Path("/ping")
public Response ping(@Context HttpServletRequest req) {
    if(req.getSession().getAttribute("username") == null) {
        return Response.ok("no session with an username attribute has been set").build();
    }
    return Response.ok(req.getSession(true).getAttribute("username")).build();
}

Кажется, что все работает нормально, если я отправляю сообщение в/из Postman или с базовой веб-страницы jQuery, развернутой на стеклянной платке, я верну свое имя пользователя и сессия была помещена. Если я затем отправлю запрос GET в /ping, я получаю имя пользователя, с которого я вошел.

У меня есть приложение AngularJS, развернутое на веб-сервере node.js, которому необходимо войти в систему. Поскольку этот сервер находится на другом порту, он находится в другом домене, и мне пришлось пройти через боль от включения корса. Я сделал это, создав фильтр ответов контейнера, который устанавливает заголовки ответов.

public class CrossOriginResourceSharingFilter implements ContainerResponseFilter {
    @Override
    public ContainerResponse filter(ContainerRequest creq, ContainerResponse cresp) {
        cresp.getHttpHeaders().putSingle("Access-Control-Allow-Origin", "http://localhost:8000");
        cresp.getHttpHeaders().putSingle("Access-Control-Allow-Credentials", "true");
        cresp.getHttpHeaders().putSingle("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT");
        cresp.getHttpHeaders().putSingle("Access-Control-Allow-Headers", "Content-Type, Accept, X-Requested-With");
        return cresp;
    }
}

Это позволило мне отправить различные типы HTTP-запросов от приложения AngularJS к Java EE 6, развернутого на стеклянной платке.

Проблема заключается в том, что когда я отправляю запрос POST из AngularJS в метод /login, создается сеанс, и я возвращаю свое имя пользователя. Но когда я отправляю запрос GET методу /ping, я получаю уведомление об отсутствии "сеанса с атрибутом имени пользователя".

Я считаю, что это связано с предотвращением перекрестных доменов и что я должен установить тег withCredentials, когда я отправляю запрос xhr. Я пытался сделать это в AngularJS, но не узнал, как это сделать.

function LoginCtrl($scope, $http) {
    $scope.login = function() {
        $http.post("glassfish:otherport/api/login", $scope.credentials).
            success(function(data) {
                console.log(data);
            }).
            error(function(data, error) {
                console.log(error);
            });
    };
};

И в другом контроллере:

$scope.getUsername = function() {
    $http.get("glassfish:otherport/api/ping", {}).
        success(function(data) {
            $scope.username = data;
        }).
        error(function() {
            $scope.username = "error";
        })
    }

Я попытался установить withCredentials, это правда

$http.defaults.withCredentials = true;

Это, однако, не решило мою проблему. Я также попытался отправить его с каждым запросом в параметре конфигурации, но это также не решило мою проблему.

Ответ 1

В зависимости от версии AngularJS, которую вы используете, вам, возможно, придется установить ее на каждом $http.

Начиная с 1.2 вы можете:

$http.get(url,{ withCredentials: true, ...})

Из 1.1.1 вы можете глобально настроить его:

config(['$httpProvider', function($httpProvider) {
  $httpProvider.defaults.withCredentials = true;
}]).

Если вы используете более старую версию Angular, попробуйте передать объект конфигурации в $http, который указывает withCredentials. Это должно работать в версиях до версии 1.1:

$http({withCredentials: true, ...}).get(...)

См. также mruelans answer и:

Ответ 2

просто обновление для @iwein anwser, которое мы теперь можем установить в самой конфигурации

config(['$httpProvider', function($httpProvider) {
  $httpProvider.defaults.withCredentials = true;
}]).

https://github.com/angular/angular.js/pull/1209

(доступен только после нестабильной версии: 1.1.1)

Ответ 3

В версии 1.2 это не работает для меня:

$http({withCredentials: true, ...}).get(...) 

Если я прочитал документ, метод ярлыка должен взять объект конфигурации

$http.get(url,{ withCredentials: true, ...})

$http - одноэлементный, Единственный способ смешать в одном приложении запросы с учетными данными и без них.