如何使用Spring Security重新加载用户更新的权限

Aur*_*e77 38 java spring spring-security

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

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

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

leo*_*leo 46

如果您需要动态更新登录用户的权限(当这些权限因任何原因而发生更改时),而无需注销并登录,您只需要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);
Run Code Online (Sandbox Code Playgroud)

  • 嗯,它几乎对我有用.这个"auth"变量与登录用户(即我)有关.如果我以"x"登录并且我想撤销"y"权限,我如何从该特定用户获取身份验证的对象? (12认同)
  • 这仅适用于当前用户.如何为其他用户实现这一目标? (5认同)
  • 我很困惑为什么这个答案有这么多的赞成票:它没有完全回答明确指出有必要更改另一个用户的数据的问题。 (4认同)

Aur*_*e77 12

谢谢,帮帮我了!有了SessionRegistry,我可以使用getAllPrincipals()来比较要修改的用户与会话中的当前活动用户.如果存在会话,我可以使用以下命令使其会话无效:expireNow()(from 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
                }
            }
        }
    }
} 
Run Code Online (Sandbox Code Playgroud)

  • 我正在使用上面的代码(请参阅编辑)来使用户会话无效。但我有一个问题,用户没有被迫重新登录...我认为该用户的 SecurityContextHolder 没有清除。我怎样才能做到这一点? (2认同)

Twi*_*wiN 10

如果有人仍在研究如何在不强制该用户重新进行身份验证的情况下更新另一个用户的权限,您可以尝试添加一个重新加载身份验证的拦截器。这将确保您的权限始终处于更新状态。

然而——由于额外的拦截器,将会有一些性能影响(例如,如果你从你的数据库中获取你的用户角色,它将为每个 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;
    }

}
Run Code Online (Sandbox Code Playgroud)

此特定实现使用 OAuth2 ( OAuth2AuthenticationToken),但您可以UsernamePasswordAuthenticationToken改用。

现在,将拦截器添加到配置中:

@Configuration
public class WebConfiguration extends WebMvcConfigurationSupport {

    @Autowired
    private VerifyAccessInterceptor verifyAccessInterceptor;


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

}
Run Code Online (Sandbox Code Playgroud)

我也写了一篇关于这个的文章


ale*_*sko 7

关键点 - 您应该能够访问用户SecurityContext.

如果你是在servlet环境和使用HttpSession作为securityContextRepository您的securityContextPersistenceFilter,那么就可以使用Spring的完成SessionRegistry.强制用户重新授权(它应该比静默权限撤销更好)使他失效HttpSession.不要忘记添加HttpSessionEventPublisher到web.xml

<listener>
    <listener-class>
        org.springframework.security.web.session.HttpSessionEventPublisher
    </listener-class>
</listener>
Run Code Online (Sandbox Code Playgroud)

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

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