相信很多用了Spring Security的小伙伴在日常开发中,总会遇到需要实时更新权限的问题,比如,对一个新调入某个部门的员工,我们希望在管理员设置他的权限之后,这位员工立刻就能使用了,而不需要先退出登录然后再重新登录,以达到更好的用户体验。但是Spring Security似乎并没有直接给出如何按照用户名修改Redis中存储的权限的接口,一般都得是用户自己调用一个更新权限的接口才能更新。那么,我们能不能在后台悄悄地就把权限更新,而不需要用户自己访问呢?
目前网络上一搜,关于这个的内容很少,能搜到的也只是简单的处理方法,例如:
- 修改权限后强制踢出用户,这个的用户体验并不好;
- 修改权限后用Http客户端携带对应SessionID的Cookie伪装成用户请求更新权限接口(或者是前端不定时访问一下更新权限的接口);
- 添加拦截器,管理员更新了权限后,将这些用户的用户名或SessionID作为key,要修改的权限作为value放入一个HashMap中,用户每次请求后端都会经过这个拦截器,拦截器中判断是否需要更新权限。
以上2、3都有一个问题,就是更新权限并不是经常发生的事情,可能用户用了几个月,管理员才会修改一次权限,如果只是为了确认权限有没有更新,就多一个步骤,那属实是有点浪费性能。
接下来,我就带大家看看我是怎么操作的。
首先,我们的项目采用的是Spring Security + Spring Session Redis搭建的。
我们建立了一个测试用的Controller,我们首先要注入SessionRepository
@Autowired
private SessionRepository sessionRepository;
SessionRepository是一个Spring Session的接口,下面有许多实现类。
然后,例如我们如果想要根据SessionID获取一个Session,非常简单,只需要:
Session byId = sessionRepository.findById(session.getId());
Session到手,接下来我们想要获取Spring Security存储在其中的SecurityContext、用户名啥的,只需要:
SecurityContextImpl springSecurityContext = byId.getAttribute("SPRING_SECURITY_CONTEXT");
Authentication authentication = springSecurityContext.getAuthentication();
Object principal = authentication.getPrincipal();
那么,这个时候,我们就可以为所欲为了!例如,我编写了如下示例代码来演示具体如何修改
@PutMapping("/add/{cookieId}/{name}")
public String add(@PathVariable String name, @PathVariable String cookieId) {
Session byId = sessionRepository.findById(cookieId);
SecurityContextImpl springSecurityContext = byId.getAttribute("SPRING_SECURITY_CONTEXT");
Authentication authentication = springSecurityContext.getAuthentication();
// 获取现有权限
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
// 放入新的List中
List<GrantedAuthority> grantedAuthorities = new ArrayList<>(authorities);
// 获取要添加的权限列表
List<GrantedAuthority> newAuthorities = AuthorityUtils.commaSeparatedStringToAuthorityList(name);
// 合并
grantedAuthorities.addAll(newAuthorities);
// 必须构造新的AuthenticationToken,因为Spring Security没有给出setAuthorities的方法
springSecurityContext.setAuthentication(new UsernamePasswordAuthenticationToken(authentication.getPrincipal(),authentication.getCredentials(), grantedAuthorities));
// 设置到Session中
byId.setAttribute("SPRING_SECURITY_CONTEXT", springSecurityContext);
// 保存修改
sessionRepository.save(byId);
return "success";
}
当然啦,实际上管理员修改完权限,还需要把修改后的权限持久化到数据库中,这样的话用户以后再登录也是有新的权限了!
那么,可能会有小伙伴问了,我能不能直接通过RedisTemplate获取对应的值,强转为SecurityContext呢?很遗憾,答案是不可以,在写下这篇文章的前一晚我也是这么想的,结果发现类型转换是不会通过的,会报错,不管是用StringRedisTemplate还是RedisTemplate都没用。
@GetMapping("/getRoleV2/{cookieId}")
public String getRoleV2(@PathVariable String cookieId) {
Map<Object, Object> entries = stringRedisTemplate.opsForHash().entries("spring:session:sessions:" + cookieId);
System.out.println(entries);
SecurityContextImpl springSecurityContext = (SecurityContextImpl) entries.get("sessionAttr:SPRING_SECURITY_CONTEXT");
Authentication authentication = springSecurityContext.getAuthentication();
return JSON.toJSONString(springSecurityContext);
}
报错信息:
java.lang.ClassCastException: class java.lang.String cannot be cast to class org.springframework.security.core.context.SecurityContextImpl (java.lang.String is in module java.base of loader 'bootstrap'; org.springframework.security.core.context.SecurityContextImpl is in unnamed module of loader 'app')
at com.dayuhelper.usercenter.controller.TestController.getRoleV2(TestController.java:166)
// 以下报错省略
大家赶紧也去试一试吧~
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/qq_37436987/article/details/128887939