前言
之前在 集成RBAC授权 的文章中提到了SpringCloud可以 「基于路径匹配器授权」 在网关层进行用户权限校验,这种方式的实现原理是Springcloud Gateway接受到请求后根据 ReactiveAuthorizationManager#check(Mono<Authentication> 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也拼接在权限上,关键代码如下:
@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风格的用户权限校验,希望对你有所帮助!
Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。
它的内容包括:
- 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
- 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
- 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
- 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
- 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
- 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
- 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
- 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw
目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:
想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询
同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。