Фиксация сеанса и сессионные компоненты в JSF 2.3 с CDI - PullRequest
5 голосов
/ 09 января 2020

Общепринятой практикой является возобновление сеанса HTTP при входе в систему пользователя. Это заставит новый идентификатор сеанса, избегая уязвимостей фиксации сеанса.

Есть ли предпочтительный шаблон для реализации этого с CDI, когда задействованы компоненты @SessionScoped? Сложность состоит в том, что, сделав недействительным текущий сеанс HTTP, вы получите другой bean-объект сессионной области со следующим запросом, но не до следующего запроса.

Например, предположим, что сессионный компонент для хранения пользователя информация для входа в систему:

@Named("sessionbean")
@SessionScoped
public class SessionBean implements Serializable {
    private int userId;
    private String username;
    private List<String> privileges;

    // Accessors omitted 
}

И еще один bean-компонент для управления входом в систему:

@Named("loginbean")
@ViewScoped
public class LoginBean implements Serializable {

    private String username;
    private String password;
    @Inject private SessionBean session;
    @Inject private SessionManager sessionManager;
    @Inject private PrivilegeManager privilegeManager;      

    public String doLogin() {
        String destinationUrl;

        if (validate(username, password)) {
            FacesContext context = FacesContext.getCurrentInstance();

            // force renewal of HTTP session
            context.getExternalContext().invalidateSession();

            // retrieve new session bean  ** No longer works with CDI **
            Application app = context.getApplication();
            session = app.evaluateExpressionGet(context, "#{sessionbean}", SessionBean.class);

            session.setUsername(username);
            session.setSessionId(sessionManager.createNewSession(username));
            session.setPrivileges(privilegeManager.getPrivileges(username));

            destinationUrl = createLandingPageUrl();

        } else {
            destinationUrl = createFailureUrl("Unknown user or password");
        }

        return destinationUrl;
    }
}

Для Managed Beans это приведет к получению нового SessionBean, но для CDI приведенный выше код просто вернет тот же SessionBean. Любые рекомендации или умные идеи?

Ответы [ 2 ]

7 голосов
/ 10 января 2020

Сложность состоит в том, что, сделав недействительным текущий сеанс HTTP, вы получите другой bean-объект в рамках сеанса со следующим запросом, но не до следующего запроса.

Тогда не аннулируйте сеанс, а измените идентификатор сеанса. Другими словами, не используйте HttpSession#invalidate(), но используйте HttpServletRequest#changeSessionId() (новинка с Servlet 3.1, которую вы, несомненно, должны уже использовать, учитывая, что вы используете JSF 2.3 ).

В коде замените

// force renewal of HTTP session object
context.getExternalContext().invalidateSession();

на

// force renewal of HTTP session ID
((HttpServletRequest) context.getExternalContext().getRequest()).changeSessionId();

Это в основном изменяет JSESSIONID cook ie без изменения HttpSession. Он идеально подходит для предотвращения фиксации сеанса.

Явное аннулирование сеанса обычно полезно только при выходе из системы.

0 голосов
/ 09 января 2020

Я собираюсь ограничить этот ответ исключительно о CDI, так как я не эксперт по безопасности. Я также не знаю, хорошая ли вещь, о которой просят, хорошая идея или нет. Несмотря на это, вот как я думаю, вы бы поступили так, как вы просите.

Выражаясь в чисто терминах CDI, вопрос можно перефразировать следующим образом:

У меня есть объект, который Я знаю, что пришел из определенного Context. Я знаю жизненный цикл объектов, произведенных этим Context. Как я могу правильно сказать Context, чтобы сделать недействительным текущий объект, которым он управляет, и загрузить или создать новый?

Общий подход будет следующим:

  • @Inject a Provider<SessionBean> вместо SessionBean напрямую (это позволит вам правильно запросить у CDI «новый» объект)
  • @Inject a BeanManager (чтобы вы могли получить право Context, которое управляет SessionScoped объектами)
  • попросите BeanManager дать вам AlterableContext, соответствующий аннотации SessionScoped
  • , скажите AlterableContext уничтожить контекстный экземпляр текущего компонента
  • вызывает Provider.get() для создания нового

Таким образом, соответствующие части вашего doLogin метода могут выглядеть так (не проверено):

final AlterableContext context = (AlterableContext) this.beanManager.getContext(SessionScoped.class);
assert context != null;

final Bean<?> bean = beanManager.resolve(beanManager.getBeans(SessionBean.class));
assert bean != null;

context.destroy(bean);

final SessionBean newSessionBean = this.sessionBeanProvider.get();
assert newSessionBean != null;

Я думаю, что это должно работать.

...