回复
3 个注解,优雅的实现微服务鉴权(三)
love374
发布于 2022-7-8 17:29
浏览
0收藏
3. 注解切面定义
注解有了,那么如何去拦截呢?这里陈某定义了一个切面进行拦截,关键代码如下:
/**
* @author 公众号:码猿技术专栏
* @url: www.java-family.cn
* @description @RequiresLogin,@RequiresPermissions,@RequiresRoles 注解的切面
*/
@Aspect
@Component
public class PreAuthorizeAspect {
/**
* 构建
*/
public PreAuthorizeAspect() {
}
/**
* 定义AOP签名 (切入所有使用鉴权注解的方法)
*/
public static final String POINTCUT_SIGN = " @annotation(com.mugu.blog.common.annotation.RequiresLogin) || "
+ "@annotation(com.mugu.blog.common.annotation.RequiresPermissions) || "
+ "@annotation(com.mugu.blog.common.annotation.RequiresRoles)";
/**
* 声明AOP签名
*/
@Pointcut(POINTCUT_SIGN)
public void pointcut() {
}
/**
* 环绕切入
*
* @param joinPoint 切面对象
* @return 底层方法执行后的返回值
* @throws Throwable 底层方法抛出的异常
*/
@Around("pointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
// 注解鉴权
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
checkMethodAnnotation(signature.getMethod());
try {
// 执行原有逻辑
Object obj = joinPoint.proceed();
return obj;
} catch (Throwable e) {
throw e;
}
}
/**
* 对一个Method对象进行注解检查
*/
public void checkMethodAnnotation(Method method) {
// 校验 @RequiresLogin 注解
RequiresLogin requiresLogin = method.getAnnotation(RequiresLogin.class);
if (requiresLogin != null) {
doCheckLogin();
}
// 校验 @RequiresRoles 注解
RequiresRoles requiresRoles = method.getAnnotation(RequiresRoles.class);
if (requiresRoles != null) {
doCheckRole(requiresRoles);
}
// 校验 @RequiresPermissions 注解
RequiresPermissions requiresPermissions = method.getAnnotation(RequiresPermissions.class);
if (requiresPermissions != null) {
doCheckPermissions(requiresPermissions);
}
}
/**
* 校验有无登录
*/
private void doCheckLogin() {
LoginVal loginVal = SecurityContextHolder.get();
if (Objects.isNull(loginVal))
throw new ServiceException(ResultCode.INVALID_TOKEN.getCode(), ResultCode.INVALID_TOKEN.getMsg());
}
/**
* 校验有无对应的角色
*/
private void doCheckRole(RequiresRoles requiresRoles){
String[] roles = requiresRoles.value();
LoginVal loginVal = OauthUtils.getCurrentUser();
//该登录用户对应的角色
String[] authorities = loginVal.getAuthorities();
boolean match=false;
//and 逻辑
if (requiresRoles.logical()==Logical.AND){
match = Arrays.stream(authorities).filter(StrUtil::isNotBlank).allMatch(item -> CollectionUtil.contains(Arrays.asList(roles), item));
}else{ //OR 逻辑
match = Arrays.stream(authorities).filter(StrUtil::isNotBlank).anyMatch(item -> CollectionUtil.contains(Arrays.asList(roles), item));
}
if (!match)
throw new ServiceException(ResultCode.NO_PERMISSION.getCode(), ResultCode.NO_PERMISSION.getMsg());
}
/**
* TODO 自己实现,由于并未集成前端的菜单权限,根据业务需求自己实现
*/
private void doCheckPermissions(RequiresPermissions requiresPermissions){
}
}
其实这中间的逻辑非常简单,就是解析的Token中的权限、角色然后和注解中的指定的进行比对。
“@RequiresPermissions这个注解的逻辑陈某并未实现,自己根据业务模仿着完成,算是一道思考题了....
”
4. 注解使用
比如《Spring Cloud Alibaba 实战》项目中有一个添加文章的接口,只有超管和管理员的角色才能添加,那么可以使用@RequiresRoles注解进行标注,如下:
@RequiresRoles
@AvoidRepeatableCommit
@ApiOperation("添加文章")
@PostMapping("/add")
public ResultMsg<Void> add(@RequestBody @Valid ArticleAddReq req){
.......
}
效果这里就不演示了,实际的效果:非超管和管理员角色用户登录访问,将会直接被拦截,返回无权限。
注意:这里仅仅解决了下游服务鉴权的问题,那么feign调用是否也适用?
当然适用,这里使用的是切面方式,feign内部其实使用的是http方式调用,对于接口来说一样适用。
比如《Spring Cloud Alibaba 实战》项目中获取文章列表的接口,其中会通过feign的方式调用评论服务中的接口获取文章评论总数,这里一旦加上了@RequiresRoles,那么调用将会失败,代码如下:
@RequiresRoles
@ApiOperation(value = "批量获取文章总数")
@PostMapping(value = "/list/total")
public ResultMsg<List<TotalVo>> listTotal(@RequestBody @Valid List<CommentListReq> param){
....
}
总结
本文主要介绍了微服务中如何将鉴权下放到微服务中,也是为了解决读者的疑惑,实际生产中除非业务需要,陈某还是建议将鉴权统一放到网关中。
文章转自公众号:码猿技术专栏
标签
已于2022-7-8 17:29:53修改
赞
收藏
回复
相关推荐