聊聊网关Restful接口拦截

网络 通信技术
本章内容我们就来解决这个Restful接口拦截的问题,使其能支持精细化的权限控制。

[[375844]]

本文转载自微信公众号「JAVA日知录」,作者单一色调。转载本文请联系JAVA日知录公众号。

前言

之前在 集成RBAC授权 的文章中提到了SpringCloud可以「基于路径匹配器授权」在网关层进行用户权限校验,这种方式的实现原理是Springcloud Gateway接受到请求后根据 ReactiveAuthorizationManager#check(Mono authenticationMono, AuthorizationContext authorizationContext) 方法基于 AntPathMatcher校验当前访问的URL是否在用户拥有的权限URL中,如果能匹配上则说明拥有访问权限并放行到后端服务,否则提示用户无访问权限。

具体实现方式在上面文章中有阐述,如果有不清楚的可以再次查阅。文章地址:

http://javadaily.cn/articles/2020/08/07/1596772909329.html

不过之前的实现方式有个问题,就是不支持restful风格的url路径。

例如一个微服务有如下API

  • GET /v1/pb/user
  • POST /v1/pb/user
  • PUT /v1/pb/user

这样在网关通过 request.getURI().getPath()方法获取到用户请求路径的时候都是同一个地址,给一个用户授予 /v1/pb/user权限后他就拥有了 GET、PUT、POST三种不同权限,很显然这样不能满足精细权限控制。本章内容我们就来解决这个Restful接口拦截的问题,使其能支持精细化的权限控制。

场景演示

我们看下实际的案例,演示下这种场景。在 account-service模块下增加一个博客用户管理功能,有如下的接口方法:

接口URL HTTP方法 接口说明
/blog/user POST 保存用户
/blog/user/{id} GET 查询用户
/blog/user/{id} DELETE 删除用户
/blog/user/{id} PUT 更新用户信息

然后我们在 sys_permission表中添加2个用户权限,再将其授予给用户角色

在网关层的校验方法中可以看到已经增加了2个权限

由于DELETE 和 PUT对应的权限路径都是 /blog/user/{id},这样就是当给用户授予了查询权限后此用户也拥有了删除和更新的权限。

解决方案

看到这里大部分同学应该想到了,要想实现Restful风格的精细化权限管理单单通过URL路径是不行的,需要搭配Method一起使用。

最关键的点就是「需要给权限表加上方法字段,然后在网关校验的时候即判断请求路径又匹配请求方法。」 实现步骤如下:

修改权限表,新增方法字段

  • 在loadUserByUsername()方法构建用户权限的时候将权限对应的Method也拼接在权限上,关键代码如下:
  1. @Override 
  2. public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException { 
  3.  //获取本地用户 
  4.  SysUser sysUser = sysUserMapper.selectByUserName(userName); 
  5.  if(sysUser != null){ 
  6.   //获取当前用户的所有角色 
  7.   List<SysRole> roleList = sysRoleService.listRolesByUserId(sysUser.getId()); 
  8.   sysUser.setRoles(roleList.stream().map(SysRole::getRoleCode).collect(Collectors.toList())); 
  9.   List<Integer> roleIds = roleList.stream().map(SysRole::getId).collect(Collectors.toList()); 
  10.   //获取所有角色的权限 
  11.   List<SysPermission> permissionList = sysPermissionService.listPermissionsByRoles(roleIds); 
  12.   //拼接method 
  13.   List<String> permissionUrlList = permissionList.stream() 
  14.                     .map(item -> "["+item.getMethod()+"]"+item.getUrl()) 
  15.                     .collect(Collectors.toList()); 
  16.   sysUser.setPermissions(permissionUrlList); 
  17.   //构建oauth2的用户 
  18.   return buildUserDetails(sysUser); 
  19.  }else
  20.   throw  new UsernameNotFoundException("用户["+userName+"]不存在"); 
  21.  } 

通过上面的代码构建的用户权限如下:

  • [GET]/account-service/blog/user/{id}
  • [POST]/account-service/blog/user

可以通过代码调试查看:

  • 权限校验方法AccessManager#check(),校验[MEHOTD]RequestPath 格式

@Override

  1. @Override 
  2. public Mono<AuthorizationDecision> check(Mono<Authentication> authenticationMono, AuthorizationContext authorizationContext) { 
  3.  ServerWebExchange exchange = authorizationContext.getExchange(); 
  4.  ServerHttpRequest request = exchange.getRequest(); 
  5.  //请求资源 
  6.  String requestPath = request.getURI().getPath(); 
  7.  
  8.  //拼接method 
  9.  String methodPath = "["+request.getMethod()+"]" + requestPath; 
  10.  
  11.  // 1. 对应跨域的预检请求直接放行 
  12.  if(request.getMethod() == HttpMethod.OPTIONS){ 
  13.   return Mono.just(new AuthorizationDecision(true)); 
  14.  } 
  15.  
  16.  // 是否直接放行 
  17.  if (permitAll(requestPath)) { 
  18.   return Mono.just(new AuthorizationDecision(true)); 
  19.  } 
  20.  
  21.  return authenticationMono.map(auth -> new AuthorizationDecision(checkAuthorities(auth, methodPath))) 
  22.    .defaultIfEmpty(new AuthorizationDecision(false)); 
  23.  

校验方法 checkAuthorities():

  1. private boolean checkAuthorities(Authentication auth, String requestPath) { 
  2.  if(auth instanceof OAuth2Authentication){ 
  3.   OAuth2Authentication authentication = (OAuth2Authentication) auth; 
  4.   String clientId = authentication.getOAuth2Request().getClientId(); 
  5.   log.info("clientId is {}",clientId); 
  6.   //用户的权限集合 
  7.   Collection<? extends GrantedAuthority> authorities = auth.getAuthorities(); 
  8.  
  9.   return authorities.stream() 
  10.     .map(GrantedAuthority::getAuthority) 
  11.     //ROLE_开头的为角色,需要过滤掉 
  12.     .filter(item -> !item.startsWith(CloudConstant.ROLE_PREFIX)) 
  13.     .anyMatch(permission -> ANT_PATH_MATCHER.match(permission, requestPath)); 
  14.  } 
  15.  
  16.  return true
  • 这样当请求Delete方法时就会提示没有权限

这里还有另外一种方案,实现的原理跟上面差不多,只简单提一下。

首先还是得在权限表中新增METHOD字段,这是必须的。

然后项目中使用的权限类是 SimpleGrantedAuthority,这个只能存储一个权限字段,我们可以自定义一个权限实体类,让其可以存储url 和 method。

  1. @Data 
  2. public class MethodGrantedAuthority implements GrantedAuthority { 
  3.  
  4.     private String method; 
  5.     private String url; 
  6.  
  7.     public MethodGrantedAuthority(String method, String url){ 
  8.         this.method = method; 
  9.         this.url = url; 
  10.     } 
  11.  
  12.     @Override 
  13.     public String getAuthority() { 
  14.         return "["+method+"]" + url; 
  15.     } 

在 UserDetailServiceImpl中构建用户权限时使用自定义的 MethodGrantedAuthority

网关层校验的方法还是需要跟上面一样,既校验Method 又 校验 URL。

 

责任编辑:武晓燕 来源: JAVA日知录
相关推荐

2024-03-05 10:09:16

restfulHTTPAPI

2021-03-16 06:55:49

Server4认证网关

2020-07-07 07:54:01

API网关微服务

2023-07-26 07:13:55

函数接口Java 8

2020-05-27 08:05:33

MybatisMapper接口

2023-11-20 08:01:38

并发处理数Tomcat

2021-09-18 09:45:33

前端接口架构

2021-04-02 12:37:53

RestfulAPI接口架构

2022-02-08 23:59:12

USB接口串行

2022-01-26 00:05:00

接口Spring管理器

2022-11-17 07:43:13

2021-05-25 11:13:48

SSL证书过期微软Microsoft E

2018-04-24 09:05:09

容器存储接口

2024-07-30 09:35:00

2022-06-10 13:03:44

接口重试while

2021-11-18 08:20:22

接口索引SQL

2020-05-06 22:07:53

UbuntuLinux操作系统

2022-02-21 13:27:11

接口性能优化索引命令

2023-11-29 09:04:00

前端接口

2022-09-01 08:17:15

Gateway微服务网关
点赞
收藏

51CTO技术栈公众号