我正在使用Spring Security通过OpenID进行身份验证的应用程序。 当用户登录时,会话中会加载一些权限。
我拥有完全权限的用户,可以修改其他用户的权限(撤销,添加角色)。我的问题是,如何动态更改用户会话权限? (不能使用 SecurityContextHolder ,因为我想更改另一个用户会话。)
简单方法:使用户会话无效,但如何? 更好的方法:刷新用户会话与新权限,但如何?
答案 0 :(得分:38)
如果您需要动态更新登录用户的权限(当这些权限因任何原因而发生变化时),而无需注销并登录,您只需要重置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);
答案 1 :(得分:12)
谢谢,帮助我很多!使用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
}
}
}
}
}
答案 2 :(得分:7)
关键点 - 您应该能够访问用户SecurityContext
。
如果您在servlet环境中并且在HttpSession
中使用securityContextRepository
作为securityContextPersistenceFilter
,则可以使用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),但可能很有用:
答案 3 :(得分:3)
如果有人仍在研究如何更新另一个用户的权限而又不强迫该用户重新进行身份验证,则可以尝试添加一个拦截器来重新加载身份验证。这样可以确保您的权限始终处于更新状态。
但是-由于额外的拦截器,会对性能产生一些影响(例如,如果您从数据库中获得用户角色,则会针对每个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("/**");
}
}
答案 4 :(得分:0)
我使用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;
}
答案 5 :(得分:0)
我在上面有一个非常具体的案例,我使用Redis来跟踪https://github.com/spring-projects/spring-session的用户会话。然后,当管理员向用户添加一些角色时,我在Redis中找到了用户会话,并替换了principal
和authorities
,然后保存了会话。
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);
}
}
}
}
}
}