【问题标题】:How to reload authorities on user update with Spring Security如何使用 Spring Security 重新加载用户更新权限
【发布时间】:2012-04-12 04:58:13
【问题描述】:

我正在使用 Spring Security 通过 OpenID 进行身份验证的应用程序。 当用户登录时,会在他的会话中加载一些权限。

我拥有完全权限的用户,可以修改其他用户的权限(撤销、添加角色)。我的问题是,如何动态更改用户会话权限? (不能使用 SecurityContextHolder 因为我想更改另一个用户会话)。

简单的方法:使用户会话无效,但如何? 更好的方法:用新的权限刷新用户会话,但是如何?

【问题讨论】:

    标签: java spring spring-security


    【解决方案1】:

    关键点 - 你应该能够访问用户SecurityContexts。

    如果您在 servlet 环境中并且在您的securityContextPersistenceFilter 中使用HttpSession 作为securityContextRepository,那么可以使用spring 的SessionRegistry 来完成。强制用户重新认证(它应该比静默权限撤销更好)使他的HttpSession无效。不要忘记在 web.xml 中添加HttpSessionEventPublisher

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

    如果您使用线程本地securityContextRepository,则应将自定义过滤器添加到springSecurityFilterChain 以管理SecurityContexts 注册表。为此,您必须使用普通 bean springSecurityFilterChain 配置(没有 security 命名空间快捷方式)。使用带有自定义过滤器的普通 bean 配置,您将完全控制身份验证和授权。

    一些链接,它们不能完全解决您的问题(没有 OpenID),但可能有用:

    【讨论】:

      【解决方案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
                      }
                  }
              }
          }
      } 
      

      【讨论】:

      • securityContextPersistenceFilter 默认会将SecurityContext 放入servlet 环境中的HttpSession。由于您已经拥有开箱即用的 spring SessionRegistry,因此您无需自定义此过滤器。
      • 我是servlet环境,自定义securityContextPersistenceFilter有什么用?
      • 可能有不同的情况,例如HttpSessions 被禁用,您不需要线程本地存储。所以你可以使用你自己的securityContextRepository 实现。如果HttpSession存储满足你的需要,那就没有用处了。
      • 我正在使用上面的代码(请参阅编辑)使用户会话无效。但是我有一个问题,用户没有被强制重新登录......我认为该用户没有清除 SecurityContextHolder。我该怎么做?
      • SecurityContext 对于每个用户都位于每个用户的会话中,请参阅here 的详细信息。如果您可以通过注册表访问其他用户的会话,那么您可以使用它做您想做的事情。
      【解决方案3】:

      如果您需要动态更新已登录用户的权限(无论出于何种原因,这些权限已更改),当然无需注销并登录,您只需重置Authentication 对象(安全令牌)在春天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);
      

      【讨论】:

      • 嗯,它几乎对我有用。这个“auth”变量与登录用户(即我)有关。如果我以“x”身份登录并且想撤销“y”权限,我如何从该特定用户那里获取 Authentication 的对象?
      • 这仅适用于当前用户。如何为其他用户实现这一目标?
      • 我很困惑为什么这个答案有这么多赞成票:它没有完全回答清楚说明有必要更改另一个用户的数据的问题。
      【解决方案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("/**");
          }
      
      }
      

      I also made an article about this.

      【讨论】:

        【解决方案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;
        }
        

        【讨论】:

        • 我喜欢你的想法,但 users_to_update_roles 存在竞争条件。在 Set 上同步(如果像这样访问它应该是一个 ConcurrentHashSet)会起作用,但会引入一个不同的问题。
        • @RüdigerSchulz 你有好的解决方案/示例代码吗?
        【解决方案6】:

        我有一个非常具体的上述案例,我使用 Redis 跟踪用户会话 https://github.com/spring-projects/spring-session。然后当管理员向用户添加一些角色时,我在 Redis 中找到用户会话并替换 principalauthorities 然后保存会话。

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

        【讨论】:

        • 非常感谢!搜索了几天!
        猜你喜欢
        • 2010-10-27
        • 2015-05-10
        • 2011-12-14
        • 2020-04-12
        • 2013-07-07
        • 2020-07-23
        • 2021-12-19
        • 2011-04-11
        • 2016-03-03
        相关资源
        最近更新 更多