How to reload authorities on user update with Spring Security

前端 未结 6 1727
情话喂你
情话喂你 2020-12-04 08:44

I\'m doing an application with authentication by OpenID using Spring Security. When user is logged-in, some authorities are loaded in his session.

I have User with f

相关标签:
6条回答
  • 2020-12-04 09:17

    I have a very specific case of above, I use Redis to track user session with https://github.com/spring-projects/spring-session. Then when admin adds some Role to the user I find user session in Redis and replace principal and authorities and then save the session.

    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);
                            }
                        }
                    }
                }
            }
        }
    
    0 讨论(0)
  • 2020-12-04 09:23

    The key point - you should be able to access users SecurityContexts.

    If you are in servlet environment and are using HttpSession as securityContextRepository in your securityContextPersistenceFilter, then it can be done with spring's SessionRegistry. To force the user to re-auth (it should be better than silent permissions revocation) invalidate his HttpSession. Don't forget to add HttpSessionEventPublisher to web.xml

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

    If you are using thread-local securityContextRepository, then you should add custom filter to springSecurityFilterChain to manage SecurityContexts registry. To do this you must the use plain-bean springSecurityFilterChain configuration (without security namespace shortcuts). With plain-bean config with custom filters you'll have full control on authentication and authorization.

    Some links, they don't solve exactly your problem (no OpenID), but may be useful:

    • NIH session registry for servlet environment
    • it's plain-bean spring config working example
    • real life plain-bean spring config for X.509 auth, you may start with it and modify it to use OpenID instead of X.509.
    0 讨论(0)
  • 2020-12-04 09:24

    If you need to dynamically update a logged in user's authorities (when these have changed, for whatever reason), without having to log out and log in of course, you just need to reset the Authentication object (security token) in the Spring SecurityContextHolder.

    Example:

    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);
    
    0 讨论(0)
  • 2020-12-04 09:25

    I use the answer gived by TwiN, but I create a control variable (users_to_update_roles) to reduce performance impacts.

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

    and in my controller, I add the user that have they role updated

        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 讨论(0)
  • If anybody is still looking into how to update the authorities of another user without forcing that user to re-authenticate, you can try to add an interceptor that reloads the authentication. This will make sure that your authorities are always updated.

    However -- due to the extra interceptor, there will be some performance impacts (e.g. if you get your user roles from your database, it will be queried for every HTTP request).

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

    This specific implementation uses OAuth2 (OAuth2AuthenticationToken), but you can use UsernamePasswordAuthenticationToken instead.

    And now, to add your interceptor to the configuration:

    @Configuration
    public class WebConfiguration extends WebMvcConfigurationSupport {
    
        @Autowired
        private VerifyAccessInterceptor verifyAccessInterceptor;
    
    
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(verifyAccessInterceptor).addPathPatterns("/**");
        }
    
    }
    

    I also made an article about this.

    0 讨论(0)
  • 2020-12-04 09:43

    Thanks, help me a lot ! With SessionRegistry, I can use getAllPrincipals() to compare the user to modify with the current active users in sessions. If a session exist, I can invalidate his session using : expireNow() (from SessionInformation) to force re-authentication.

    But I don't understand the usefulness of securityContextPersistenceFilter ?

    EDIT :

    // 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
                    }
                }
            }
        }
    } 
    
    0 讨论(0)
提交回复
热议问题