Подтвердить что ты не робот

Как перезагрузить полномочия по обновлению пользователя с помощью Spring Безопасность

Я делаю приложение с аутентификацией по OpenID с помощью Spring Security. Когда пользователь входит в систему, некоторые службы загружаются в его сеанс.

У меня есть Пользователь с полным правом, который может изменять полномочия (отменять, добавлять роли) других пользователей. Мой вопрос заключается в том, как динамически менять полномочия пользовательской сессии? (нельзя использовать SecurityContextHolder, потому что я хочу изменить другой сеанс пользователя).

Простой способ: аннулировать сеанс пользователя, но как это сделать? Лучший способ: обновить сеанс пользователя новыми полномочиями, но как?

4b9b3361

Ответ 1

Если вам необходимо динамически обновлять зарегистрированные в пользовательских полномочиях (когда они изменились по какой-либо причине), без необходимости самостоятельного выхода из системы и входа в систему, вам просто нужно reset объект Authentication (безопасность токен) в Spring SecurityContextHolder.

Пример:

Authentication auth = SecurityContextHolder.getContext().getAuthentication();

List<GrantedAuthority> updatedAuthorities = new ArrayList<>(auth.getAuthorities());
updatedAuthorities.add(...); //add your role here [e.g., new SimpleGrantedAuthority("ROLE_NEW_ROLE")]

Authentication newAuth = new UsernamePasswordAuthenticationToken(auth.getPrincipal(), auth.getCredentials(), updatedAuthorities);

SecurityContextHolder.getContext().setAuthentication(newAuth);

Ответ 2

Спасибо, помогите мне много! С помощью SessionRegistry я могу использовать getAllPrincipals(), чтобы сравнить пользователя для изменения с текущими активными пользователями в сеансах. Если сеанс существует, я могу аннулировать его сеанс, используя: expireNow() (от SessionInformation), чтобы принудительно повторить аутентификацию.

Но я не понимаю полезности securityContextPersistenceFilter?

ИЗМЕНИТЬ:

// user object = User currently updated
// invalidate user session
List<Object> loggedUsers = sessionRegistry.getAllPrincipals();
for (Object principal : loggedUsers) {
    if(principal instanceof User) {
        final User loggedUser = (User) principal;
        if(user.getUsername().equals(loggedUser.getUsername())) {
            List<SessionInformation> sessionsInfo = sessionRegistry.getAllSessions(principal, false);
            if(null != sessionsInfo && sessionsInfo.size() > 0) {
                for (SessionInformation sessionInformation : sessionsInfo) {
                    LOGGER.info("Exprire now :" + sessionInformation.getSessionId());
                    sessionInformation.expireNow();
                    sessionRegistry.removeSessionInformation(sessionInformation.getSessionId());
                    // User is not forced to re-logging
                }
            }
        }
    }
} 

Ответ 3

Ключевой момент - вы должны иметь доступ к пользователям SecurityContext s.

Если вы находитесь в среде сервлета и используете HttpSession как securityContextRepository в securityContextPersistenceFilter, то это можно сделать с помощью spring SessionRegistry. Чтобы заставить пользователя повторить авторизацию (это должно быть лучше, чем молчание отмены прав), аннулировать его HttpSession. Не забудьте добавить HttpSessionEventPublisher в web.xml

<listener>
    <listener-class>
        org.springframework.security.web.session.HttpSessionEventPublisher
    </listener-class>
</listener>

Если вы используете thread-local securityContextRepository, добавьте настраиваемый фильтр в springSecurityFilterChain для управления реестром SecurityContext. Для этого вы должны использовать конфигурацию plain-bean springSecurityFilterChain (без security ярлыков пространства имен). С помощью простой конфигурации bean с настраиваемыми фильтрами вы будете иметь полный контроль над аутентификацией и авторизацией.

Некоторые ссылки, они не решают точно вашу проблему (нет OpenID), но могут быть полезны:

Ответ 4

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

Однако из-за дополнительного перехватчика произойдет некоторое снижение производительности (например, если вы получите свои пользовательские роли из вашей базы данных, она будет запрашиваться для каждого HTTP-запроса).

@Component
public class VerifyAccessInterceptor implements HandlerInterceptor {

    // ...

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        Set<GrantedAuthority> authorities = new HashSet<>();
        if (auth.isAuthenticated()) {
            authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
        }

        User userFromDatabase = getUserFromDatabase(auth.getName());
        if (userFromDatabase != null) {
            // add whatever authorities you want here
            authorities.add(new SimpleGrantedAuthority("...")); 
        }

        Authentication newAuth = null;

        if (auth.getClass() == OAuth2AuthenticationToken.class) {
            OAuth2User principal = ((OAuth2AuthenticationToken)auth).getPrincipal();
            if (principal != null) {
                newAuth = new OAuth2AuthenticationToken(principal, authorities,(((OAuth2AuthenticationToken)auth).getAuthorizedClientRegistrationId()));
            }
        }

        SecurityContextHolder.getContext().setAuthentication(newAuth);
        return true;
    }

}

Эта конкретная реализация использует OAuth2 (OAuth2AuthenticationToken), но вместо этого вы можете использовать UsernamePasswordAuthenticationToken.

А теперь, чтобы добавить ваш перехватчик в конфигурацию:

@Configuration
public class WebConfiguration extends WebMvcConfigurationSupport {

    @Autowired
    private VerifyAccessInterceptor verifyAccessInterceptor;


    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(verifyAccessInterceptor).addPathPatterns("/**");
    }

}

Я также сделал статью об этом.

Ответ 5

Я использую ответ от TwiN, но создаю управляющую переменную (users_to_update_roles), чтобы уменьшить влияние на производительность.

@Component
public class RoleCheckInterceptor implements HandlerInterceptor {
public static ArrayList<String> update_role = new ArrayList<>();

@Autowired
private IUser iuser;

public static Set<String> users_to_update_roles = new HashSet<>();

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
        throws Exception {

    Authentication auth = SecurityContextHolder.getContext().getAuthentication();

    try {

        CurrentUser current = (CurrentUser) auth.getPrincipal();

        String username = current.getUser().getUsername();
        if (users_to_update_roles.contains(username)) {
            updateRoles(auth, current);
            users_to_update_roles.remove(username);
        }

    } catch (Exception e) {
        // TODO: handle exception
    }

    return true;
}

@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
        ModelAndView modelAndView) throws Exception {

}

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
        throws Exception {

}

private void updateRoles(Authentication auth, CurrentUser current) {
    User findOne = iuser.findOne(current.getUser().getUsername());
    List<GrantedAuthority> updatedAuthorities = new ArrayList<>();
    for (Role role : findOne.getRoles()) {
        updatedAuthorities.add(new SimpleGrantedAuthority(role.name()));
    }

    Authentication newAuth = new UsernamePasswordAuthenticationToken(auth.getPrincipal(), auth.getCredentials(),
            updatedAuthorities);

    SecurityContextHolder.getContext().setAuthentication(newAuth);
}
}

и в моем контроллере я добавляю пользователя с обновленной ролью

    public ModelAndView roleSave(@PathVariable long numero_documento, Funcionario funcionario) {
    ModelAndView modelAndView = new ModelAndView("funcionario/role");
    Set<Role> roles = funcionario.getPessoa().getUser().getRoles();
    funcionario = funcionarioService.funcionarioNumero_documento(numero_documento);
    funcionario.getPessoa().getUser().setRoles(roles);
    iUser.save(funcionario.getPessoa().getUser());
    RoleCheckInterceptor.users_to_update_roles.add(funcionario.getPessoa().getUser().getUsername());
    modelAndView.addObject("funcionario", funcionario);
    modelAndView.addObject("sucess", "Permissões modificadas");
    return modelAndView;
}

Ответ 6

У меня есть очень конкретный случай выше, я использую Redis для отслеживания сеанса пользователя с https://github.com/spring-projects/spring-session. Затем, когда администратор добавляет некоторую роль пользователю, я нахожу сеанс пользователя в Redis и заменяю principal и authorities, а затем сохраняю сеанс.

public void updateUserRoles(String username, Set<GrantedAuthority> newRoles) {
        if (sessionRepository instanceof FindByIndexNameSessionRepository) {
            Map<String, org.springframework.session.Session> map =
                    ((FindByIndexNameSessionRepository<org.springframework.session.Session>) sessionRepository)
                            .findByPrincipalName(username);
            for (org.springframework.session.Session session : map.values()) {
                if (!session.isExpired()) {
                    SecurityContext securityContext = session.getAttribute(SPRING_SECURITY_CONTEXT_KEY);
                    Authentication authentication = securityContext.getAuthentication();
                    if (authentication instanceof UsernamePasswordAuthenticationToken) {
                        Collection<GrantedAuthority> authorities = new HashSet<>(authentication.getAuthorities());
                        //1. Update of authorities
                        authorities.addAll(newRoles);
                        Object principalToUpdate = authentication.getPrincipal();
                        if (principalToUpdate instanceof User) {
                            //2. Update of principal: Your User probably extends UserDetails so call here method that update roles to allow
                            // org.springframework.security.core.userdetails.UserDetails.getAuthorities return updated 
                            // Set of GrantedAuthority
                            securityContext
                                    .setAuthentication(new UsernamePasswordAuthenticationToken(principalToUpdate, authentication
                                            .getCredentials(), authorities));
                            session.setAttribute(SPRING_SECURITY_CONTEXT_KEY, securityContext);
                            sessionRepository.save(session);
                        }
                    }
                }
            }
        }
    }