欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

spring security中动态更新用户的权限

程序员文章站 2022-05-08 14:17:12
...

     在程序的执行过程中,有时有这么一种需求,需要动态的更新某些角色的权限或某些人对应的权限,当前在线的用户拥有这个角色或拥有这个权限时,在不退出系统的情况下,需要动态的改变的他所拥有的权限。

 

需求:张三 登录了系统拥有 ROLE_ADMIN,和ROLE_USER 的权限,但是ROLE_ADMIN的权限太强了,不应该给张三,后台管理人员应该可以取消张三的ROLE_ADMIN的权限,那么在张三还在线且没有退出系统的情况下应该不可以继续访问ROLE_ADMIN的权限的资源。

 

解决思路:

    1、监听系统中所有的在线用户,需要监听session的创建和销毁事件

    2、spring secuirty在登录成功后为了防止session的固化攻击会改变sessionId的值,因此需要监听spring security发布的 SessionFixationProtectionEvent事件

    3、当我们系统中的资源发生了改变时,需要发布自己的一个资源改变事件,在此事件中修改当前在线用户的权限。

 

大致步骤如下:

一、拿到系统中所有的在线用户对象

    |-  将 HttpSessionEventPublisher 注册成一个listener,注册之后就可以监听到session的创建和销毁事件

/**
	 * 注册Servlet Listener,用于发布Session的创建和销毁事件
	 */
	@Bean
	public ServletListenerRegistrationBean httpSessionEventPublisher() {
		ServletListenerRegistrationBean<HttpSessionEventPublisher> registration = new ServletListenerRegistrationBean<>();
		registration.setListener(new HttpSessionEventPublisher());
		return registration;
	}

    |- 监听session的创建事件(HttpSessionCreatedEvent),在这一步需要保存session对象

/**
 * 监听session创建对象
 */
@Component
@Slf4j
class HttpSessionCreatedEventListener implements ApplicationListener<HttpSessionCreatedEvent> {

	@Override
	public void onApplicationEvent(HttpSessionCreatedEvent event) {
		log.info("新建session:{}", event.getSession().getId());
		try {
			// 保存 session
		} catch (Exception e) {
			log.info(String.format("添加session:[%s]出现异常.", event.getSession().getId()), e);
		}
	}
}

   |- 监听session的销毁事件(HttpSessionDestroyedEvent),此事件主要是移除没有的session对象

 

/**
 * 监听session失效事件
 */
@Component
@Slf4j
class SessionDestroyedEventListener implements ApplicationListener<HttpSessionDestroyedEvent> {

	@Override
	public void onApplicationEvent(HttpSessionDestroyedEvent event) {
		log.info("失效session:{}", event.getSession().getId());
		try {
			// 移除session
		} catch (Exception e) {
			log.error(String.format("失效session:[%s]发生异常.", event.getId()), e);
		}
	}
}

   |- 保存系统中所有的session对象

 

二、监听SessionFixationProtectionEvent事件,这个主要是spring security为了防止session的固化攻击,在用户登录成功后,会修改用户的sessionId的值,并发布此事件,需要在此监听,不然上一步保存的session中的sessionId的值是有问题的。

 (注:可以查看org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter#sessionStrategy这个

 

/**
 * @author huan.fu
 *         Spring security 登录成功后,防止session的固化攻击,会将旧的sessionId销毁,重新生成一个新的sessionId,
 *         因此此处需要做一下处理
 */
@Component
class SessionFixationProtectionEventListener implements ApplicationListener<SessionFixationProtectionEvent> {
	@Override
	public void onApplicationEvent(SessionFixationProtectionEvent event) {
		String oldSessionId = event.getOldSessionId();
		String newSessionId = event.getNewSessionId();
                // 更改sessionId的值
	}
}

 

三、当系统中的资源发生改变时,修改当前在线用户的资源

    有了上面2步的基础,可以获取到当前所有在线的用户对象,当我们自己系统中的资源发生了改变的时候,比如给用户增加了一个新的角色,删除了用户的一个角色,或者给角色新增加或删除了资源时,我们就可以对当前当前用户做一些处理(比如过滤没有这个权限的用户,当session还是新建的时候的处理等),来动态更新用户的权限。

 

  获取当前用户信息:

  1、从session中获取:

SecurityContext securityContext = (SecurityContext) session.getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY);
					Object principal =  securityContext.getAuthentication().getPrincipal();

  2、重新加载当前用户的资源

 

/**
 * 重新加载用户的权限
 *
 * @param session
 */
private void reloadUserAuthority(HttpSession session) {

	// 新的权限
	List<GrantedAuthority> authorityList = AuthorityUtils.createAuthorityList(new String[]{"从数据库中查询出来"});

	SecurityContext securityContext = (SecurityContext) session.getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY);
	Authentication authentication = securityContext.getAuthentication();
	SecurityUser principal = (SecurityUser) authentication.getPrincipal();
	principal.setAuthorities(authorityList);

	// 重新new一个token,因为Authentication中的权限是不可变的.
	UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(
			principal, authentication.getCredentials(),
			authorityList);
	result.setDetails(authentication.getDetails());
	securityContext.setAuthentication(result);
}

 

  到此,应该就可以实现动态的修改在线用户的权限信息了。

  注意:上面的代码有些是不全的,但是大致思路是这样的。