我们经常会遇到这样的场景,动态权限的调整,例如张三是个部门主管,本身没有删除员工资料这个权限,但是上级比较信任张三,就想将这个功能交给张三去做。
我们首先使用张三登录(xsc001):
我们看后台打印出来的张三的角色权限:
当前角色为SALE_ADMIN,权限为新增和修改
这时候我们需要给他新增员工删除的权限,这里有个问题是,不管直接修改数据库还是使用代码给他新增这些权限和角色,如果张三已经是登录状态的话,需要退出系统重新登录才会加载最新权限(因为张三已经登录了,spring security缓存中加载了他的登录信息,权限是登录的时候就加载到了缓存中的,中间再去新增的权限数据库修改了,但是当前登录的用户没有加载)
显然上述是非常不平滑的权限赋值,我们需要的是当用户动态被新增权限的时候,立即可以使用!!!
解决方案:(参考了阿里巴巴前高级研发工程师大佬的解决方案)
我们还是首先用张三的账户访问删除接口:
我们看到被我们权限系统拦截了,因为del方法是他没有权限的
我们的解决方案为:
使用拦截器进行拦截请求处理被标记为需要处理的用户,刷新当前用户的权限信息。
详细的思路就是,我们在拦截器中设置一个用户列表,当我们在代码中为某个用户设置权限的时候将用户信息添加在这个用户列表中,这个用户列表中的所有用户(如果有)我们将着里边的用户重新加载权限(从数据库中查询最近的权限列表),然后刷新当前用户的的令牌
我们首先看拦截器:
package com.mysecurity.liuyf.conf.security.filter;
import com.mysecurity.liuyf.pojo.MyUserDetails;
import com.mysecurity.liuyf.pojo.User;
import com.mysecurity.liuyf.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@Component
public class RoleCheckInterceptor implements HandlerInterceptor {
@Autowired
private UserService userService;
//存放username
public static Set<String> usersToUpdateRoles = new HashSet<>();
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("RoleCheckInterceptor");
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if(auth != null) {
// 需要排除anonymousUser
if(auth.getPrincipal() instanceof MyUserDetails) {
MyUserDetails userDetails = (MyUserDetails)auth.getPrincipal();
String username = userDetails.getUsername();
if(usersToUpdateRoles.size()>0 && usersToUpdateRoles.contains(username)) {
updateRoles(auth,userDetails);
usersToUpdateRoles.remove(username);
}
}
}
return true;
}
private void updateRoles(Authentication auth, MyUserDetails user) {
User u=userService.findUserByName(user.getUsername());
List<GrantedAuthority> list = new ArrayList<>();
List<String> roles=userService.findUserRoleByUserId(u.getId());
for (String role:roles){
list.add(new SimpleGrantedAuthority("ROLE_"+role));
}
//加载权限表的东西进来
List<String> permission=userService.findUserPermissionByUserId(u.getId());
for (String per:permission){
list.add(new SimpleGrantedAuthority(per));
}
Authentication newAuth = new UsernamePasswordAuthenticationToken(auth.getPrincipal(), auth.getCredentials(),list);
SecurityContextHolder.getContext().setAuthentication(newAuth);
System.out.println("RoleCheckInterceptor.updateRoles 刷新权限信息");
System.out.println(newAuth);
}
}
1:在方法updateRoles中动态查询用户权限,然后调用
SecurityContextHolder.getContext().setAuthentication(newAuth);
经行刷新
2:在usersToUpdateRoles这个列表中保存修改过用户权限的用户(用户名记得去重)
最后将这个连接器记得放到配置文件中去生效:
package com.mysecurity.liuyf.conf;
import com.mysecurity.liuyf.Interceptor.TokenInterceptor;
import com.mysecurity.liuyf.conf.security.filter.RoleCheckInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.annotation.Resource;
@Configuration
public class WebConfig implements WebMvcConfigurer {
/**
* 拦截器用于动态刷新用户权限
*/
@Resource
private RoleCheckInterceptor roleCheckInterceptor ;
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(roleCheckInterceptor).addPathPatterns("/**");
}
}
这样就可以了,最后我们模拟一个修改用户权限的方法:
/**
*测试赋值一个新得角色
* @param
* @return
*/
@GetMapping("/upAuthForUpRole")
public JsonResult upAuthForUpRole(HttpServletResponse response) throws IOException {
//参数1:接收用户ID
//参数2:接收需要新增/去除的权限
//操作1:数据库操作,将需要修改的数据存储到数据中
//将用户姓名存储到上一步的列表中用于刷新用户权限
RoleCheckInterceptor.usersToUpdateRoles.add("xs001");
return new JsonResult(ResultCode.SUCCESS);
}
这里都是写的伪代码,数据库操作自己补上,这里最主要的就是将用户列明放到列表中去
注意:这个位置需要注意我们的权限拦截器会优先于自定义用于刷新权限的拦截器,也就是说出去访问下其他请求(直接访问del还是无权限),调用下自定义拦截器才能完成刷新权限的操作
最终实验:
我们直接在数据库中修改用户角色(因为前边写的是伪代码),这时候我们之前登录的用户是没有退出的,也就是说依然没有删除的权限
将张三的角色id也修改为1(超管)
这时候调用upAuthForUpRole方法刷新授权
成功的重定向到另外一个方法中去了
后台也成功的打印出授权方法中的提示信息:
然后不需要退出登录,访问del方法:
我们看到不需要退出登录,不需要重启服务器,已经完成了动态授权