web-dev-qa-db-fra.com

Comment recharger des autorités sur la mise à jour d'utilisateur avec Spring Security

Je fais une application avec authentification par OpenID avec Spring Security . Lorsque l'utilisateur est connecté, certaines autorités sont chargées dans sa session.

J'ai un utilisateur de plein droit qui peut modifier les autorisations (révoquer, ajouter des rôles) d'autres utilisateurs. Ma question est, comment changer les autorités de session d'utilisateur dynamiquement? (impossible d'utiliser SecurityContextHolder parce que je souhaite modifier une autre session utilisateur).

Manière simple: invalider la session utilisateur, mais comment? Meilleure façon: actualiser la session utilisateur avec les nouvelles autorités, mais comment?

33
Aure77

Si vous devez mettre à jour de manière dynamique les autorités d'un utilisateur connecté (lorsque celles-ci ont changé, pour une raison quelconque), sans avoir à vous déconnecter et vous connecter bien sûr, il vous suffit de réinitialiser l'objet Authentication (jeton de sécurité) dans Spring SecurityContextHolder.

Exemple:

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);
35
leo

Merci, aidez-moi beaucoup! Avec SessionRegistry, je peux utiliser getAllPrincipals () pour comparer l’utilisateur à modifier avec les utilisateurs actifs en cours dans les sessions. Si une session existe, je peux invalider sa session en utilisant: expireNow () (from SessionInformation) pour forcer la réauthentification.

Mais je ne comprends pas l'utilité de securityContextPersistenceFilter?

MODIFIER :

// 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
                }
            }
        }
    }
} 
12
Aure77

Le point clé - vous devriez pouvoir accéder aux utilisateurs SecurityContexts. 

Si vous êtes dans un environnement de servlet et que vous utilisez HttpSession comme securityContextRepository dans votre securityContextPersistenceFilter, vous pouvez le faire avec la variable SessionRegistry de spring. Pour forcer l'utilisateur à réautoriser (cela devrait être mieux que la révocation d'autorisations silencieuse), invalide sa HttpSession. N'oubliez pas d'ajouter HttpSessionEventPublisher à web.xml

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

Si vous utilisez thread-local securityContextRepository, vous devez ajouter un filtre personnalisé à springSecurityFilterChain pour gérer le registre SecurityContexts. Pour ce faire, vous devez utiliser la configuration springSecurityFilterChain de plain-bean (sans les raccourcis d'espace de nom security). Avec la configuration standard avec des filtres personnalisés, vous aurez un contrôle total sur l'authentification et l'autorisation.

Certains liens, ils ne résolvent pas exactement votre problème (pas d'OpenID), mais peuvent être utiles:

7
alexkasko

Si quelqu'un cherche encore à mettre à jour les autorités d'un autre utilisateur sans le forcer à se réauthentifier, vous pouvez essayer d'ajouter un intercepteur qui recharge l'authentification. Cela garantira que vos autorités sont toujours à jour.

Toutefois, en raison de l’intercepteur supplémentaire, cela aura un impact sur les performances (par exemple, si vous obtenez vos rôles d’utilisateur de votre base de données, une requête sera demandée pour chaque requête 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;
    }

}

Cette implémentation spécifique utilise OAuth2 (OAuth2AuthenticationToken), mais vous pouvez utiliser UsernamePasswordAuthenticationToken à la place.

Et maintenant, pour ajouter votre intercepteur à la configuration:

@Configuration
public class WebConfiguration extends WebMvcConfigurationSupport {

    @Autowired
    private VerifyAccessInterceptor verifyAccessInterceptor;


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

}

J'ai également fait un article à ce sujet .

3
TwiN

J'utilise la réponse fournie par TwiN, mais je crée une variable de contrôle (users_to_update_roles) pour réduire les conséquences sur les performances.

@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);
}
}

et dans mon contrôleur, j'ajoute l'utilisateur qui a son rôle mis à jour

    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;
}
0
Hasler Choo