开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 10 天,点击查看活动详情
每日英语:
Courage,above all things,is the first quality of a warrior.
勇气高于一切是战士的第一品质。 -卡尔·冯·克劳塞维茨
权限校验
权限初始化
按照上一篇分析流程,我们在程序启动的时候,就需要初始化权限数据,也就是需要程序启动就执行相关代码,SpringBoot可以实现ApplicationRunner 或 CommandLineRunner接口,实现程序初始化启动。
初始化执行操作我们要执行2个操作,分别是角色权限初始化和所有权限初始化,所有权限初始化主要用来校验哪些地址需要被校验,而所有权限地址中不存在的地址那一定是不需要校验的,用户没登录也是能访问的。
1)Dao
我们在mall-permission-service的com.xz.mall.permission.mapper.PermissionMapper中创建一个方法,用于查询所有角色的权限
@Select("SELECT * FROM role_permission")
List<Map<Integer, Integer>> allRolePermissions();
2)Service
接口:在com.xz.mall.permission.service.PermissionService创建2个方法,分别查询不同匹配方式的权限和所有角色权限,代码如下:
//不同匹配方式的权限
List<Permission> findByMatch(int i);
//所有角色的权限映射
List<Map<Integer, Integer>> allRolePermissions();
实现类:修改com.xz.mall.permission.service.impl.PermissionServiceImpl添加实现方法,代码如下:
/***
* 根据匹配方式查找
* @param i
* @return
*/
@Override
public List<Permission> findByMatch(int i) {
QueryWrapper<Permission> queryWrapper = new QueryWrapper<Permission>();
queryWrapper.eq("url_match",i);
return permissionMapper.selectList(queryWrapper);
}
/**
* 所有角色的权限
* @return
*/
@Override
public List<Map<Integer, Integer>> allRolePermissions() {
return permissionMapper.allRolePermissions();
}
3)初始化加载
创建com.xz.mall.permission.init.InitPermission实现ApplicationRunner,从而达到SpringBoot工程启动加载的目的,代码如下:
@Component
public class InitPermission implements ApplicationRunner {
@Autowired
private PermissionService permissionService;
@Autowired
private RedisTemplate redisTemplate;
/***
* 初始化
* @param args
* @throws Exception
*/
@Override
public void run(ApplicationArguments args) throws Exception {
//根据匹配方式查找
List<Permission> permissionsMatch0 = permissionService.findByMatch(0);
List<Permission> permissionsMatch1 = permissionService.findByMatch(1);
//查询所有角色权限
List<Map<Integer,Integer>> rolePermissions = permissionService.allRolePermissions();
//角色权限处理
Map<String, Set<Permission>> roleMap = rolePermissionFilter(rolePermissions, permissionsMatch0, permissionsMatch1);
//将所有权限和角色权限存入到Redis缓存
redisTemplate.boundHashOps("RolePermissionAll").put("PermissionListMatch0",permissionsMatch0);
redisTemplate.boundHashOps("RolePermissionAll").put("PermissionListMatch1",permissionsMatch1);
redisTemplate.boundHashOps("RolePermissionMap").putAll(roleMap);
}
/***
* 角色权限过滤
* @param rolePermissions : 角色权限映射关系
* @param permissionsMatch0 :所有完全匹配路径
* @param permissionsMatch1 :所有通配符匹配路径
* @return
*/
public Map<String,Set<Permission>> rolePermissionFilter(List<Map<Integer,Integer>> rolePermissions,
List<Permission> permissionsMatch0,
List<Permission> permissionsMatch1){
//角色权限关系 key=roleid,value=List<Permission>
Map<String, Set<Permission>> rolePermissionMapping = new HashMap<String,Set<Permission>>();
//关系循环处理
for (Map<Integer, Integer> rolePermissionMap : rolePermissions) {
Integer rid = rolePermissionMap.get("rid"); //角色ID
Integer pid = rolePermissionMap.get("pid"); //权限ID
String key0 = "Role_0_"+rid.intValue();
String key1 = "Role_1_"+rid.intValue();
//获取当前角色拥有的权限
Set<Permission> permissionSet0 = rolePermissionMapping.get(key0);
Set<Permission> permissionSet1 = rolePermissionMapping.get(key1);
//防止空指针
permissionSet0=permissionSet0==null? new HashSet<Permission>(): permissionSet0;
permissionSet1=permissionSet1==null? new HashSet<Permission>(): permissionSet1;
//循环完全匹配路径
for (Permission permission : permissionsMatch0) {
if(permission.getId().intValue()==pid.intValue()){
permissionSet0.add(permission);
break;
}
}
//循环通配符匹配路径
for (Permission permission : permissionsMatch1) {
if(permission.getId().intValue()==pid.intValue()){
permissionSet1.add(permission);
break;
}
}
//将数据添加到rolePermissionMapping中
if(permissionSet0.size()>0){
rolePermissionMapping.put(key0,permissionSet0);
}
if(permissionSet1.size()>0){
rolePermissionMapping.put(key1,permissionSet1);
}
}
return rolePermissionMapping;
}
}
如上代码,加载了所有权限以及不同角色的权限,并且所有权限都区分了匹配方式:
1:所有权限完全匹配 key=PermissionListMatch0
2:所有权限通配符匹配 key=PermissionListMatch1
3:角色权限完全匹配 key=Role_0_*,这里的*就是角色ID
4:角色权限通配符匹配 key=Role_1_*,这里的*就是角色ID
拦截校验
我们接下来实现对用户请求的地址进行校验,先校验当前请求地址是否需要执行权限校验,如果执行拦截才进入校验过程,如果不需要进行拦截是不需要校验的。
1)过滤器顺序调整
用户每次请求,地址栏会先经过处理,比如每次提交都会多携带一个/mall路径,此时对权限校验会造成干扰,需要将该路径处理掉,处理该路径SpringCloud Gateway提供了一个过滤器叫RouteToRequestUrlFilter,该过滤器处理完成后会找到处理当前请求的服务,接下来会调用LoadBalancerClientFilter选择一个处理请求的真实服务,我们要想判断权限,应该在这两个过滤器中间执行权限判断,因为此时知道要调用的服务名字也知道用户请求的真实服务地址,拦截器调用如下图:
此时我们把com.xz.mall.api.filter.ApiFilter的执行顺序调整一下:
@Override
public int getOrder() {
//在RouteToRequestFilter之后执行
return 10001;
}
调整后,过滤器执行顺序如下图:
2)是否校验判断
我们在com.xz.mall.api.permission.AuthorizationInterceptor中编写一个方法用于判断当前请求是否需要进行权限拦截,代码如下:
@Autowired
private RedisTemplate redisTemplate;
/****
* 校验是否需要拦截指定请求
*/
public Boolean isIntercept(ServerWebExchange exchange){
//request
ServerHttpRequest request = exchange.getRequest();
//uri
String uri = request.getURI().getPath();
//提交方法
String method = request.getMethodValue();
//路由URI信息
URI routerUri = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR);
//获取所有权限
List<Permission> permissionMatch0 = (List<Permission>) redisTemplate.boundHashOps("RolePermissionAll").get("PermissionListMatch0");
//完全匹配
Permission permission = match0(permissionMatch0, uri, method,routerUri.getHost());
//完全匹配如果没有,则匹配通配符
if(permission==null){
//匹配通配符
List<Permission> permissionMatch1 = (List<Permission>) redisTemplate.boundHashOps("RolePermissionAll").get("PermissionListMatch1");
}
//如果此时permission则表示不需要进行权限校验
if(permission==null){
//不需要权限校验
return false;
}
return true;
}
/***
* 完全匹配
* @param permissionMatch0
* @param uri
* @param method
* @return
*/
public Permission match0(List<Permission> permissionMatch0,String uri,String method,String serviceName){
//循环匹配
for (Permission permission : permissionMatch0) {
String matchUrl = permission.getUrl();
String matchMethod = permission.getMethod();
if(matchUrl.equals(uri)){
//匹配提交方式
if(!matchMethod.equals("*") && matchMethod.equalsIgnoreCase(method) && serviceName.equals(permission.getServiceName())){
return permission;
}
}
}
return null;
}
这里我们会用到RedisTemplate,在mall-service项目中用的都是String进行序列化,我们需要把mall-common中的Redis工具类RedisConfig拷贝到mall-api-gateway中。
运行时一些参数如下:
此时可以通过URI获取要调用的服务名字、用户请求的地址等,如下图:
我们接下来在com.xz.mall.api.filter.ApiFilter中进行调用:
角色权限校验
1)令牌校验
我们先对之前代码进行优化,把令牌校验单独抽出到com.xz.mall.api.permission.AuthorizationInterceptor中,代码如下:
/***
* 令牌校验
*/
public Map<String, Object> tokenIntercept(ServerWebExchange exchange){
//request
ServerHttpRequest request = exchange.getRequest();
//客户端IP
String ip = IPUtil.getIp(request);
//用户令牌
String token = request.getHeaders().getFirst("authorization");
//令牌校验
Map<String, Object> resultMap = AuthorizationInterceptor.jwtVerify(token, ip);
return resultMap;
}
在com.xz.mall.api.filter.ApiFilter中进行调用;
2)权限校验
每个用户存在多种角色,角色权限校验应该对种种角色权限进行校验,我们在com.xz.mall.api.permission.AuthorizationInterceptor中编写方法实现校验,代码如下:
/***
* 角色权限校验
*/
public Boolean rolePermission(ServerWebExchange exchange,Map<String, Object> token){
//request
ServerHttpRequest request = exchange.getRequest();
//uri
String uri = request.getURI().getPath();
//提交方法
String method = request.getMethodValue();
//路由URI信息
URI routerUri = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR);
//获取角色
String[] roles = token.get("roles").toString().split(",");
//当前角色权限
Permission permission = null;
//循环角色,获取角色权限
for (String role : roles) {
//===========完全匹配数据key0==============
String key0 = "Role_0_"+role;
//获取角色权限数据
List<Permission> rolePermissionList0 = (List<Permission>) redisTemplate.boundHashOps("RolePermissionMap").get(key0);
if(rolePermissionList0!=null){
//匹配权限
permission = match0((JSON.parseArray(JSON.toJSONString(rolePermissionList0),Permission.class)), uri, method, routerUri.getHost());
}
if(permission==null){
}
//如果找不到权限,说明无权访问
if(permission!=null){
break;
}
}
return permission!=null;
}
我们接下来在com.xz.mall.api.filter.ApiFilter中进行调用,代码如下:
测试的时候,有权限则顺利通过调用,没有权限的角色会显示如下提示:
总结
本篇主要介绍了一下权限初始化、拦截校验、角色权限校验的实现demo。