luchob/softuni-feb2023

ROLES

Closed this issue · 5 comments

Здравей Лъчо,

Имам един казус и не съм сигурен, как точно да подходя.

Ролите са ми наредени както следва:
ADMIN -> ADMIN, MODERATOR, USER
MODERATOR -> MODERATOR, USER
USER-> USER

Приемаме, че работим с потребител, който е ADMIN и бихме искали да променим ролята на потребител
от MODERATOR на USER. Модераторите в апликацията имат право да виждат определени неща, както да трият и ъпдейтват. Логвам се през два браузъра за да имитирам 2 сесии, в единия логвам ADMIN и в другия MODERATOR.

С админа променям ролята на въпросния модератор, като извиквам следния контролер:

  @PatchMapping("/user-edit-role/{userId}")
    public String updateUserRole(@PathVariable("userId") Long userId,
                                 RoleTypeEnum role) {
        this.adminService.updateUserRole(userId, role);
        return "redirect:/admin/user-edit/" + userId;
    }

Знам, че трябва да направя нещо, което да инвалидира сесията на потребителя, който е бил модератор и вече е станал обикновен юзър за да го накарам да се логне на ново и да има нова сесия с вече актуалните роли. Понеже докато не се разлогне той може да си цъка, каквото иска, а моята идея е за незабавна промяна.

Ако може малко насока какво е подходящо да се направи? Дали изобщо има начин да го разлогна, предполагам че това е начина?

Благодаря педварително!
Поздрави,
Росен :)

luchob commented

Здравей!

Не съм сигурен дали ще работи и сега нямам време да го тествам, затова ще оставя на теб да провериш и да кажеш дали става.

  1. Инжектираме всички session registries:
    private Set<SessionRegistry> sessionRegistries;
  1. Доимплементираме следния код (или подобен):
sessionRegistries.forEach(
            sr -> {
                sr.getAllPrincipals().
                    stream().
                    filter(p -> ...).
                    forEach(sr.getAllSessions(p, true) .forEach(
                     si-> {
                         si.expireNow();
                         sr.removeSessionInformation(si.getSessionId());
                     }
                    ));
            }
        );

Поздрави,
Л.

Здравей Лъчо,

Поради някаква причина в SessionRegistry -> getAllPrincipals() няма никой...и не мога да разбера защо, ако може малко насока какво може да го накара да е пълен? За да мога да инджектвам SessionRegistry направих @bean, който да връща new SessionRegistryImpl(); и може би това е причината да не е, но не съм сигурен?

Като допълнение, не точно към тази тема, но пак става дума за разлогване на потребител, дали е достатъчно добро да имам такъв Util за ръчно логаутване на текущо логнатия юзър в определена ситуация?

public class SessionManagementUtils {

    public static void logoutUser(HttpServletRequest httpServletRequest) throws ServletException {

        httpServletRequest.logout();

        SecurityContextHolder.getContext().setAuthentication(null);
        SecurityContextHolder.clearContext();

        HttpSession httpSession = httpServletRequest.getSession();
        Enumeration<String> attributeNames = httpSession.getAttributeNames();

        while (attributeNames.hasMoreElements()) {
            String attr = attributeNames.nextElement();
            httpSession.setAttribute(attr, null);
        }

        removeCookies(httpServletRequest);
        httpSession.invalidate();
    }

    private static void removeCookies(HttpServletRequest httpServletRequest) {
        Cookie[] allCookies = httpServletRequest.getCookies();
        if (allCookies != null && allCookies.length > 0) {
            for (Cookie cookie : allCookies) {
                cookie.setMaxAge(0);
            }
        }
    }
}

И където е необходимо да извиквам SessionManagementUtils.logoutUser(httpServletRequest);

Предварително благодаря!

Поздрави,
Росен Митров

luchob commented

Здравей!

За текущо логнатия юзър е невереоятно, но има конкретна документация в spring security :-) Тук. Предлагам да и се доверим.

Сега за SessionRegistry-то. Хубаво е, че си направил такъв bean, но в документацията на SessionRegistryImpl пише:

Default implementation of SessionRegistry which listens for SessionDestroyedEvents published in the Spring application context. For this class to function correctly in a web application, it is important that you register an HttpSessionEventPublisher in the web.xml file so that this class is notified of sessions that expire.

Т.е. имаме две неща.

  1. Трябва ни някакъв ивент публишър (яхуууу, знаем какво са ивенти в спринг :-) ) който обаче слуша само за SessionDestroyedEvents... Т.е. не слуша кога се създават сесии. Окей, го регистрираме както пише:
@Bean
  public SessionRegistry sessionRegistry() {
    return new SessionRegistryImpl();
  }
  @Bean
  public HttpSessionEventPublisher httpSessionEventPublisher() {
    return new HttpSessionEventPublisher();
  }

Ако пуснеш, ще видиш че пак няма да работи. Затова да пробваме следващата стъпка.

  1. След малко гледане и ровене, виждам че може да се добави т.нар. sessionAuthenticationStrategy където састройваш спринг секюрити.
          and().
              sessionManagement().
              sessionAuthenticationStrategy(new RegisterSessionAuthenticationStrategy(sessionRegistry)).

RegisterSessionAuthenticationStrategy е липсващато нещо за да проработи въпросното реджистри.

Т.е. вече имаме регистри дето работи. Остава като че ли още малко, но сега нямам много време да ровя. Ако имаш време пробвай и пиши, ако не ще има следващ рунд защото Spring Security не е съвсем точно само 1 сървлет филтър, както ви залъгвах :-) :-)

Поздрави,
Л.

luchob commented

В допълнение искам да отбележа, че в SessionInformation е загатнато:

This is primarily used for concurrent session support.

Преведено означава, че експирейшън статуса на SessionInformation се следи само от ConcurrentSessionFilter-а, който се активира по подобен начин:

         and().
            sessionManagement().
            sessionAuthenticationStrategy(new RegisterSessionAuthenticationStrategy(sessionRegistry)).
            sessionConcurrency(session -> session.maximumSessions(5)). <-- !

Демек той следи за сесиите през SessionRegistry-то. Т.е. няма да логаутнеш юзъра "веднага" а по-скоро ще се случи това при първия му рикуест към сървъра. Като крайния резултат е еднакъв.

Л.

luchob commented

И понеже допускам, че ще ме попиташ как да редиректнем към логин страницата като експайрне сесията...  :-) Може са предложа нещо такова.

            sessionConcurrency(conf -> {
              conf.maximumSessions(5);
              conf.expiredUrl("...");
            })

Уау :-)

Поздрави,
Л.