SpringCloud Alibaba二十五 | 网关Restful接口拦截

前言

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

具体实现方式在上面文章中有阐述,若是有不清楚的能够再次查阅。 文章地址:

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

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

例如一个微服务有以下API
GET     /v1/pb/user
POST   /v1/pb/user
PUT    /v1/pb/userweb

这样在网关经过 request.getURI().getPath()方法获取到用户请求路径的时候都是同一个地址,给一个用户授予 /v1/pb/user权限后他就拥有了 GETPUTPOST三种不一样权限,很显然这样不能知足精细权限控制。本章内容咱们就来解决这个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

解决方案

看到这里大部分同窗应该想到了,要想实现Restful风格的精细化权限管理单单经过URL路径是不行的,须要搭配Method一块儿使用。架构

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

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

  • loadUserByUsername()方法构建用户权限的时候将权限对应的Method也拼接在权限上,关键代码以下:
@Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
 //获取本地用户
 SysUser sysUser = sysUserMapper.selectByUserName(userName);
 if(sysUser != null){
  //获取当前用户的全部角色
  List<SysRole> roleList = sysRoleService.listRolesByUserId(sysUser.getId());
  sysUser.setRoles(roleList.stream().map(SysRole::getRoleCode).collect(Collectors.toList()));
  List<Integer> roleIds = roleList.stream().map(SysRole::getId).collect(Collectors.toList());
  //获取全部角色的权限
  List<SysPermission> permissionList = sysPermissionService.listPermissionsByRoles(roleIds);
  //拼接method
  List<String> permissionUrlList = permissionList.stream()
                    .map(item -> "["+item.getMethod()+"]"+item.getUrl())
                    .collect(Collectors.toList());
  sysUser.setPermissions(permissionUrlList);
  //构建oauth2的用户
  return buildUserDetails(sysUser);
 }else{
  throw  new UsernameNotFoundException("用户["+userName+"]不存在");
 }
}

经过上面的代码构建的用户权限以下:

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

[POST]/account-service/blog/user

能够经过代码调试查看:

  • 权限校验方法 AccessManager#check(),校验 [MEHOTD]RequestPath 格式
@Override
public Mono<AuthorizationDecision> check(Mono<Authentication> authenticationMono, AuthorizationContext authorizationContext) {
 ServerWebExchange exchange = authorizationContext.getExchange();
 ServerHttpRequest request = exchange.getRequest();
 //请求资源
 String requestPath = request.getURI().getPath();

 //拼接method
 String methodPath = "["+request.getMethod()+"]" + requestPath;

 // 1. 对应跨域的预检请求直接放行
 if(request.getMethod() == HttpMethod.OPTIONS){
  return Mono.just(new AuthorizationDecision(true));
 }

 // 是否直接放行
 if (permitAll(requestPath)) {
  return Mono.just(new AuthorizationDecision(true));
 }

 return authenticationMono.map(auth -> new AuthorizationDecision(checkAuthorities(auth, methodPath)))
   .defaultIfEmpty(new AuthorizationDecision(false));

}

校验方法 checkAuthorities()

private boolean checkAuthorities(Authentication auth, String requestPath) {
 if(auth instanceof OAuth2Authentication){
  OAuth2Authentication authentication = (OAuth2Authentication) auth;
  String clientId = authentication.getOAuth2Request().getClientId();
  log.info("clientId is {}",clientId);
  //用户的权限集合
  Collection<? extends GrantedAuthority> authorities = auth.getAuthorities();

  return authorities.stream()
    .map(GrantedAuthority::getAuthority)
    //ROLE_开头的为角色,须要过滤掉
    .filter(item -> !item.startsWith(CloudConstant.ROLE_PREFIX))
    .anyMatch(permission -> ANT_PATH_MATCHER.match(permission, requestPath));
 }

 return true;
}


  • 这样当请求Delete方法时就会提示没有权限

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

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

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

@Data
public class MethodGrantedAuthority implements GrantedAuthority {

    private String method;
    private String url;

    public MethodGrantedAuthority(String method, String url){
        this.method = method;
        this.url = url;
    }

    @Override
    public String getAuthority() {
        return "["+method+"]" + url;
    }
}

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

网关层校验的方法仍是须要跟上面同样,既校验Method 又 校验 URL。

以上就解决了在网关层校验Restful风格的用户权限校验,但愿对你有所帮助!


End




干货分享



这里为你们准备了一份小小的礼物,关注公众号,输入以下代码,便可得到百度网盘地址,无套路领取!

001:《程序员必读书籍》
002:《从无到有搭建中小型互联网公司后台服务架构与运维架构》
003:《互联网企业高并发解决方案》
004:《互联网架构教学视频》
006:《SpringBoot实现点餐系统》
007:《SpringSecurity实战视频》
008:《Hadoop实战教学视频》
009:《腾讯2019Techo开发者大会PPT》

010: 微信交流群






近期热文top



一、关于JWT Token 自动续期的解决方案

二、SpringBoot开发秘籍-事件异步处理

三、架构师之路-服务器硬件扫盲

四、基于Prometheus和Grafana的监控平台 - 环境搭建

五、RocketMQ进阶 - 事务消息



我就知道你“在看”





本文分享自微信公众号 - JAVA日知录(javadaily)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索