Как сделать недействительным сеанс пользователя, если он дважды входит в систему с одними и теми же учетными данными

Я использую JSF 1.2 с Richfaces и Facelets.

У меня есть приложение с множеством bean-компонентов с ограниченным сеансом и некоторыми bean-компонентами приложения.

Пользователь входит, скажем, в Firefox. Сессия создается с ID = "A"; Затем он открывает Chrome и снова входит в систему с теми же учетными данными. Сеанс создается с ID = "B".

Когда сеанс «B» создан, я хочу иметь возможность уничтожить сеанс «A». Как это сделать?

Также. когда пользователь в Firefox что-то делает, я хочу иметь возможность отображать всплывающее окно или какое-то уведомление, говорящее: «Вы вышли из системы, потому что вы вошли в систему из другого места».

У меня есть sessionListener, который отслеживает созданные и уничтоженные сеансы. Дело в том, что я могу сохранить объект HTTPSession в bean-компоненте с областью действия приложения и уничтожить его, когда я обнаружу, что пользователь вошел в систему дважды. Но что-то мне подсказывает, что это неправильно и не сработает.

JSF отслеживает сеансы где-нибудь на стороне сервера? Как получить к ним доступ по идентификатору? Если нет, как выгнать пользователя при первом входе в систему, когда он входит в систему дважды?


person pakore    schedule 03.03.2010    source источник


Ответы (3)


Независимый от БД подход заключался бы в том, чтобы позволить User иметь переменную static Map<User, HttpSession> и реализовать _ 3__ 4_ и _ 5_). Таким образом, ваше веб-приложение по-прежнему будет работать после непредвиденного сбоя, который может привести к тому, что значения БД не будут обновлены (вы, конечно, можете создать ServletContextListener, который сбрасывает БД при запуске веб-приложения, но это только все больше и больше работы).

Вот как должен выглядеть User:

public class User implements HttpSessionBindingListener {

    // All logins.
    private static Map<User, HttpSession> logins = new ConcurrentHashMap<>();

    // Normal properties.
    private Long id;
    private String username;
    // Etc.. Of course with public getters+setters.

    @Override
    public boolean equals(Object other) {
        return (other instanceof User) && (id != null) ? id.equals(((User) other).id) : (other == this);
    }

    @Override
    public int hashCode() {
        return (id != null) ? (this.getClass().hashCode() + id.hashCode()) : super.hashCode();
    }

    @Override
    public void valueBound(HttpSessionBindingEvent event) {
        HttpSession session = logins.remove(this);
        if (session != null) {
            session.invalidate();
        }
        logins.put(this, event.getSession());
    }

    @Override
    public void valueUnbound(HttpSessionBindingEvent event) {
        logins.remove(this);
    }

}

Когда вы входите в User следующим образом:

User user = userDAO.find(username, password);
if (user != null) {
    sessionMap.put("user", user);
} else {
    // Show error.
}

затем он вызовет valueBound(), который удалит всех ранее вошедших в систему пользователей с карты logins и аннулирует сеанс.

При выходе из User следующим образом:

sessionMap.remove("user");

или по истечении времени ожидания сеанса будет вызвана valueUnbound(), которая удалит пользователя с карты logins.

person BalusC    schedule 03.03.2010
comment
Спасибо за ответ. Я предполагаю, что sessionMap.put (пользователь, пользователь); должно быть sessionMap.put (имя пользователя, пользователь) ;. В противном случае, если другой пользователь с другими учетными данными войдет в систему, мы вышвырнем первого пользователя. - person pakore; 04.03.2010
comment
Это ненормально. Вы не хотите, чтобы во время одного клиентского сеанса разные пользователи вошли в систему. Также не путайте карту сеанса с картой приложения. - person BalusC; 04.03.2010
comment
Хорошо, теперь я понимаю, что sessionMap - это ExternalContext.sessionMap. Он работает :). - person pakore; 04.03.2010
comment
Когда вызывается valueUnbound(), разве вы не хотите invalidate сеанс? - person Thang Pham; 07.09.2010
comment
@Harry: Нет. Более того, это фактически само признание недействительности, которое вызвало метод, который был вызван :) - person BalusC; 07.09.2010
comment
@balusC: Что возвращает getExternalContext().getSessionMap()? Он возвращает Map<User, HttpSession> logins внутри User? - person Thang Pham; 10.09.2010
comment
Нет, Map<String, Object> с атрибутами в области сеанса. По сути, это обратное отображение HttpSession#setAttribute()/#getAttribute(), известного из необработанного API сервлетов. Если атрибут сеанса реализует HttpSessionBindingListener, тогда соответствующие методы valueBound()/valueUnbound() будут вызываться во время вставки / удаления области сеанса. Это всего лишь часть необработанного API сервлетов, а не JSF. JSF работает поверх Servlet API. - person BalusC; 10.09.2010
comment
@BalusC: Разве нам не нужно защищать HashMap от одновременных модификаций? - person AndrewBourgeois; 29.05.2012
comment
@AndrewBourgeois: это может создать проблему только тогда, когда один и тот же пользователь входит в систему дважды в точно в один и тот же момент, что очень маловероятно. - person BalusC; 29.05.2012
comment
@BalusC: Я никогда не верю, что мои пользователи не пытаются делать странные вещи. ;) - person AndrewBourgeois; 29.05.2012
comment
@BalusC, спасибо за объяснение. по-прежнему он не понимает / не работает, если мы следуем вышеуказанным шагам как его, потому что его расширение очень высокого уровня ... Это могло бы понравиться: когда пользовательский объект добавляется в сеанс через session.setAttribute (), метод valueBound () вызывается для этот конкретный объект. И valueUnbound () вызывается каждый раз, когда пользовательский объект удаляется из сеанса через session.removeAttribute () или session.invalidate (). Ex должно быть: if (user! = Null) {session.setAttribute (userName, user) ; } else {// Показать ошибку. } поправьте меня, пожалуйста, я ошибаюсь. - person Ramnath; 26.11.2020

  1. создать целочисленное поле в базе данных userLoggedInCount
  2. При каждом входе в систему увеличивайте этот флаг и сохраняйте результат в сеансе.
  3. При каждом запросе проверяйте значение в базе данных и значение в сеансе, и если значение в сеансе меньше значения в БД, invalidate() сеанс и уменьшает значение в базе данных
  4. всякий раз, когда сеанс уничтожается, также уменьшайте значение.
person Bozho    schedule 03.03.2010
comment
я думаю, вместо БД я могу использовать класс с областью действия приложения. Дело в том, как получить к нему доступ, например, из слушателя фазы? Но хорошее решение. - person pakore; 03.03.2010
comment
ну, вы можете получить ExternalContext через FacesContext - person Bozho; 03.03.2010
comment
Этого алгоритма может быть недостаточно. Теория идеальна, но на практике это не сработает, если браузер сохранит сеанс в файле cookie и восстановит его из него. Взгляните на шаг 2. Если, как я уже сказал, обзор автоматически восстанавливает сеанс, он не будет проходить через LoginHandler, LoginMethod или любое другое контролируемое место для выполнения приращения этого флага. Как действовать в этом случае? - person ElPiter; 29.12.2012

Мне нравится ответ от BalusC с HttpSessionBindingListener.

Но в Спецификации Enterprise JavaBeansTM версии 2.0 написано:

Корпоративный компонент не должен использовать статические поля для чтения / записи. Допускается использование статических полей только для чтения. Поэтому рекомендуется объявить все статические поля в классе корпоративного компонента как окончательные.

Так что, не лучше ли создать бин ApplicationScoped, который будет хранить все таблицы приложений без использования статических полей ???

Он попробовал, и вроде работает ...

Вот мой пример:

@Named
@ApplicationScoped
public class UserSessionStorage implements java.io.Serializable,HttpSessionBindingListener {

@Inject
UserManagement userManagement;

private static final long serialVersionUID = 1L;

/**
 * Application wide storage of the logins
 */
private final Map<User, List<HttpSession>> logins = new HashMap<User, List<HttpSession>>();

@Override
public void valueBound(final HttpSessionBindingEvent event) {
    System.out.println("valueBound");

    /**
     * Get current user from userManagement...
     */
    User currentUser = userManagement.getCurrentUser();

    List<HttpSession> sessions = logins.get(currentUser);
    if (sessions != null) {
        for (HttpSession httpSession : sessions) {
            httpSession.setAttribute("invalid", "viewExpired");
        }
    } else {
        sessions = new ArrayList<HttpSession>();
    }
    HttpSession currentSession = event.getSession();
    sessions.add(currentSession);
    logins.put(currentUser, sessions);
}

@Override
public void valueUnbound(final HttpSessionBindingEvent event) {
    System.out.println("valueUnbound");

    User currentUser = userManagement.getCurrentUser();

    List<HttpSession> sessions = logins.get(currentUser);
    if (sessions != null) {
        sessions.remove(event.getSession());
    } else {
        sessions = new ArrayList<HttpSession>();
    }
    logins.put(currentUser, sessions);
}

}

-> Простите за мой английский ...

person Gatschet    schedule 17.06.2013
comment
HttpSessionBindingListener не является EJB. Только классы с javax.ejb.* основной аннотацией, например @Stateless, являются EJB-компонентами. - person BalusC; 17.06.2013