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

ee7vknir  于 2024-01-09  发布在  Spring
关注(0)|答案(7)|浏览(268)

我正在使用Spring Security通过OpenID进行身份验证的应用程序。当用户登录时,一些权限会加载到他的会话中。
我有完全权限的用户,可以修改其他用户的权限(撤销,添加角色)。我的问题是,如何动态更改用户会话权限?(不能使用 SecurityContextHolder,因为我想更改另一个用户会话)。
简单的方法:使用户会话无效,但如何?更好的方法:用新的权限刷新用户会话,但如何?

zpqajqem

zpqajqem1#

如果您需要动态更新登录用户的权限(当这些权限发生变化时,无论出于何种原因),而不必注销并登录,您只需要在Spring SecurityContextHolder中重置Authentication对象(安全令牌)。
范例:

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

字符串

i1icjdpr

i1icjdpr2#

谢谢,帮了我很多!使用SessionRegistry,我可以使用getAllPrincipals()来比较要修改的用户和会话中当前活动的用户。如果会话存在,我可以使用:MyReNow()(来自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
                }
            }
        }
    }
}

字符串

ffdz8vbo

ffdz8vbo3#

如果有人还在研究如何更新另一个用户的权限而不强制该用户重新进行身份验证,您可以尝试添加一个重新加载身份验证的拦截器,这将确保您的权限始终得到更新。
但是,由于额外的拦截器,会有一些性能影响(例如,如果从数据库获取用户角色,则会针对每个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的一个。

irlmq6kh

irlmq6kh4#

关键点-您应该能够访问用户SecurityContext s。
如果你是在servlet环境中,并且在你的securityContextPersistenceFilter中使用HttpSession作为securityContextRepository,那么它可以用spring的SessionRegistry来完成。为了强制用户重新认证(这应该比静默权限撤销更好),使他的HttpSession无效。不要忘记将HttpSessionEventPublisher添加到web.xml中

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

字符串
如果你使用的是线程本地securityContextRepository,那么你应该在springSecurityFilterChain中添加自定义过滤器来管理SecurityContext的注册表。要做到这一点,你必须使用普通bean springSecurityFilterChain配置(没有security命名空间快捷方式)。使用带有自定义过滤器的普通bean配置,你将完全控制身份验证和授权。
有些链接,它们不能完全解决你的问题(没有OpenID),但可能有用:

ffvjumwh

ffvjumwh5#

我有一个非常具体的例子,我使用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);
                        }
                    }
                }
            }
        }
    }

字符串

smdncfj3

smdncfj36#

我使用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;
}

pxyaymoc

pxyaymoc7#

代码:

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.session.Session;
import org.springframework.stereotype.Component;

import java.util.Map;

import static org.springframework.security.web.context.HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY;

@Slf4j
@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class SessionAuthorityManager {

    @SuppressWarnings("rawtypes") private final FindByIndexNameSessionRepository sessionRepository;

    public void updateAuthority(User user) {
        var principal = UserPrincipal.create(user);
        @SuppressWarnings("unchecked")
        Map<String, Session> map = sessionRepository.findByPrincipalName(principal.getName());
        var sessions = map.values();
        for (var session: sessions) {
            if (session.isExpired()) continue;
            var context = session.<SecurityContext>getAttribute(SPRING_SECURITY_CONTEXT_KEY);
            if (context == null) continue;
            var token = new PreAuthenticatedAuthenticationToken(principal, null, principal.getAuthorities());
            context.setAuthentication(token);
            session.setAttribute(SPRING_SECURITY_CONTEXT_KEY, context);
            //noinspection unchecked
            sessionRepository.save(session);
        }
    }
}

字符串

相关问题