微服务开发中,使用AOP和自定义注解实现对权限的校验

一、背景

微服务开发中,暴露在外网的接口,为了访问的安全,都是需要在http请求中传入登录时颁发的token。这时候,我们需要有专门用来做校验token并解析用户信息的服务。如下图所示,http请求先经过api网关,网关会去调用认证服务进行token解析(因为token是认证服务所颁发),反解析出token中包含的用户信息,最后经过http header透传给业务服务(供业务服务直接使用)。

在这里插入图片描述

本文主要是描述业务服务中,如何对api网关透传过来的报文进行权限的校验。

这里重申一下,建议每个服务自己去实现权限的校验。虽然工作量有的时候会重复,但是适用于中小公司没有统一权限管理的实际情况。

本文会涉及到的几个知识点:

  • AOP切面编程
  • 自定义注解

二、自定义注解

  • 权限开关
  • 用户ID,需读取注解所在方法的入参值
  • 角色列表,限定方法访问所需的角色列表,这里默认是教师-teacher,就是说登录用户的角色必须含有教师角色。
import java.lang.annotation.*;/*** 权限限制.** @author xxx*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface PermissionLimit {/*** 权限校验(默认true)*/boolean limit() default true;/*** 入参-用户ID** @return*/String userId();/*** 角色列表(默认teacher-教师)** @return*/String[] roles() default {Constants.RoleType.TEACHER};}

允许访问的角色列表,这里使用数组的方式, 因为一个用户可能有多个角色,而一个方法也可能被多个角色所允许访问。

本系统为了简单讲解,角色只有以下2个:

public static class RoleType {/*** 学生*/public static final String STUDENT = "student";/*** 老师*/public static final String TEACHER = "teacher";}

三、EL表达式

使用@Aspect对自定义注解PermissionLimit进行拦截,读取注解中的userId,和透传参数进行对比。

要读取注解中的userId,就需要支持el表达式,可能有下面两种情况:

  • 对象.属性
    @PostMapping("/order/copy")@PermissionLimit(userId = "#request.userId")public ResponseEntity<?> copy(@Validated @RequestBody OrderCopyRequest request) {}
  • 变量
    @PostMapping("/order/create")@PermissionLimit(userId = "#userId")public ResponseEntity<?> create(@RequestParam Long userId) {}

Java中有对el表达式支持解析:

import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;private final ExpressionParser expressionParser = new SpelExpressionParser();private LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();// elExpression 即#request.userId 或者 #userId// method 注解所在的方法// args 方法的参数值private Object evaluateExpression(String elExpression, Method method, Object[] args) {Expression expression = expressionParser.parseExpression(elExpression);EvaluationContext context = this.bindParam(method, args);return expression.getValue(context);}private EvaluationContext bindParam(Method method, Object[] args) {// 获取方法的参数名String[] params = discoverer.getParameterNames(method);EvaluationContext context = new StandardEvaluationContext();for (int i = 0; i < params.length; i++) {// 把方法的参数值赋给EvaluationContextcontext.setVariable(params[i], args[i]);}return context;}

四、HttpServletRequest

自定义注解只能修饰controller层的方法,它需要读取http header的透传字段。
所以,前提是获得HttpServletRequest对象,具体语句见下:

import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;private HttpServletRequest getRequest() {RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();if (requestAttributes instanceof ServletRequestAttributes) {return ((ServletRequestAttributes) requestAttributes).getRequest();}return null;}

接下来,读取http header中的透传字段userId,实现语句如下:

   HttpServletRequest request = this.getRequest();if (null != request) {//2.当前登录用户的userIdfinal String authUserId = request.getHeader(JwtAuthHeaders.AUTH_USER_ID);}

五、AOP切面

  • PermissionLimit是我们的自定义注解
@Component
@Aspect
public class PermissionAspect {@Autowiredprivate CommonConfig commonConfig;@Pointcut("@annotation(permissionLimit)")public void pointcut(PermissionLimit permissionLimit) {}@Around("pointcut(permissionLimit)")public Object around(ProceedingJoinPoint joinPoint, PermissionLimit permissionLimit) throws Throwable {// 1.开关是否开启(全局开关和注解的开关)if (!commonConfig.getEnabledPermission() || !permissionLimit.limit()) {return joinPoint.proceed();}Method method = this.getMethod(joinPoint);Object[] args = joinPoint.getArgs();HttpServletRequest request = this.getRequest();if (null != request) {//2.从token中解析出当前登录用户的userIdfinal String authUserIdStr = request.getHeader(JwtAuthHeaders.AUTH_USER_ID);Precondition.isTrue(StrUtil.isNotBlank(authUserIdStr), "用户未登录");//3.是否一致String userId = this.evaluateExpression(permissionLimit.userId(), method, args).toString();Precondition.isTrue(authUserIdStr.equals(userId), "用户不一致");//4.角色校验final String userRoles = request.getHeader(JwtAuthHeaders.AUTH_USER_ROLE);Precondition.isTrue(StrUtil.isNotBlank(userRoles), "未获取到登录用户的角色");String[] authorityRoleArray = permissionLimit.roles();Set<String> authorityRoleSet = Arrays.stream(authorityRoleArray).collect(Collectors.toSet());if (!CollectionUtils.isEmpty(authorityRoleSet)) {boolean hasAuthority = false;String[] userRoleArray = userRoles.split(",");for (String role : userRoleArray) {// 用户的任意一个角色被包含在里面,则说明拥有此方法的权限hasAuthority = authorityRoleSet.contains(role);if (hasAuthority) {break;}}Precondition.isTrue(hasAuthority, "用户没有此操作的权限");}}return joinPoint.proceed();}
}

六、总结

本文总结下整个的权限校验流程:

  • 全局开关, 是针对整个项目而言,在不同的环境下,开或关,方便调试。(如果是本地就需要关闭,而生产环境才打开。)
  • 方法开关,多少有点鸡肋了,好在它有默认值,不会增加你使用的复杂度。
    在这里插入图片描述

权限项的校验

本文实现了角色的校验,如果要细到权限项的话,需要查询业务服务中用户配置的权限项列表。

下面仅给出其伪代码实现,以供参考。

// 避免每次都查库,可以适当缓存一定时间
String[] authorityArray = permissionLimit.authority();
Set<String> authoritySet = Arrays.stream(authorityArray).collect(Collectors.toSet());if (!CollectionUtils.isEmpty(authorityRoleSet)) {boolean hasAuthority = false;List<String> authorities = userService.getUser(userId);for (String authority : authorities) {// 用户的任意一个权限项被包含在里面,则说明拥有此方法的权限hasAuthority = authoritySet.contains(authority);if (hasAuthority) {break;}}Precondition.isTrue(hasAuthority, "用户没有此操作的权限");
}

可以说, 它的实现和角色的校验如出一辙,不同的是,往往权限项会更细致,也就是比角色的记录数更多罢了。

如果你采用的是权限项的校验,而非角色,那么请减少每次的查库操作,可以对缓存做一个恰当有效期。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/159213.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[点云分割] 欧式距离分割

效果&#xff1a; 代码&#xff1a; #include <iostream> #include <chrono>#include <pcl/ModelCoefficients.h> // 模型系数的定义 #include <pcl/io/pcd_io.h> #include <pcl/point_types.h> // 各种点云数据类型 #include <pcl/sample_c…

java“贪吃蛇”小游戏

基于java实现贪吃蛇小游戏&#xff0c;主要通过绘制不同的图片并以一定速度一帧一帧地在窗体上进行展示。 我是在javaSwing项目下创建了一个包 名字叫做&#xff1a;Snakes包 包下有一个启动类和一个设置代码的主界面两个类 代码主界面&#xff1a; 代码主界面主要讲解的是 …

window文件夹下python脚本实现批量删除无法预览的图片

你是否遇到过下载的图片会发现有些图片会无法预览情况&#xff1f; 有几种原因可能导致一些图片在预览时无法正常显示&#xff1a; 损坏的图片文件&#xff1a; 图片文件可能损坏或者部分损坏&#xff0c;导致无法被正常解析和预览。这种情况可能是因为文件在传输过程中损坏、…

机器学习入门(第二天)——感知机

概述 每个算法都是为了解决一类问题&#xff0c;或者说解决之前的问题所创造出来的&#xff0c;而感知机&#xff0c;在解决一类问题的时候也暴露了很多问题&#xff0c;变相的推动了以后的算法的改进方向。 知识树 苹果表示相对重要的 直观介绍 现在有一盘红豆和绿豆&#…

2014年8月20日 Go生态洞察:Go在OSCON的精彩亮相

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

网络异常检测

随着社交网络、视频流、点对点技术、云计算和 SaaS 的出现&#xff0c;可以肯定地说&#xff0c;现代企业的好坏取决于他们的网络&#xff0c;尤其是在它们提供的带宽和安全性方面。无论是银行保护其数据免遭盗窃&#xff0c;还是商业组织保护其网络免受安全威胁和攻击&#xf…

ChatGLM-6B下载安装

ChatGLM-6B下载安装 项目指向 想把模型下载本地微调 通过官网指引需要先下载git-lfs #Linux 下载安装 curl -s https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh | sudo bash sudo apt-get install git-lfs git lfs install如果是docker中的虚拟机…

如何隐藏自己的代码(很酷)

1.引入 幻想当我们成为一名优秀的程序员&#xff0c;有着各大公司想要买我们的代码&#xff0c;但我们并不想要让他们知道我们代码的实现&#xff0c;毕竟一复制便可以解决&#xff0c;这里我们希望有一种方法可以把我们的核心代码给隐藏掉&#xff0c;那我们又应该怎么去实现呢…

官宣!代理IP品牌「一连IP」正式上线

&#x1f482; 个人网站:【 海拥】【神级代码资源网站】【办公神器】&#x1f91f; 基于Web端打造的&#xff1a;&#x1f449;轻量化工具创作平台&#x1f485; 想寻找共同学习交流的小伙伴&#xff0c;请点击【全栈技术交流群】 今天&#xff0c;企业级代理IP供应商【一连IP】…

通过AX6000路由器,实现外部访问内网的任意主机

概述 这里遇到一个场景,就是需要外部的人员,访问我内网的一台设备,进行内外部的设备联调。 这也是实际环境中,很常见的一种场景。 之前的做法是子设备上运行edge节点,可以直接访问。 但有的设备无法运行edge节点,那么可以参考一下这个方案来实现。 此方案可以摒弃了…

系列九、Entry的key为什么要设计成弱引用

一、Entry的key为什么要设计成弱引用 1.1、四大引用类型 Java中的四种引用 1.2、Entry源码 1.3、为什么设计为弱引用 1.3.1、官网 To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys。 1.3.2、ThreadLocal引用示意…

每日一练 | 华为认证真题练习Day135

1、如果一个以太网数据帧的Length/Tyme0z8100&#xff0c;那么这个数据帧的载荷可能是&#xff1f;&#xff08;多选&#xff09; A. TCP数据段 B. UDP数据 C. ICMP报文 D. ARP报文 2、如图所示&#xff0c;路由器R1上部署了静态NAT命令&#xff0c;当PC访问互联网时&#…

【iOS】知乎日报

文章目录 前言一、首页1.网络的异步请求2.避免同一网络请求执行多次3.下拉刷新与上拉加载的实现下拉刷新上拉加载 二、网页1.webView的实现2.webView的滑动加载3.网页与首页内容的同步更新 三、评论区Masonory实现行高自适应 四、收藏中心通过FMDB实现数据持久化1.创建或打开数…

python中的exec()、eval()以及complie()

嗨喽~大家好呀&#xff0c;这里是魔王呐 ❤ ~! python更多源码/资料/解答/教程等 点击此处跳转文末名片免费获取 1.eval函数 函数的作用&#xff1a; 计算指定表达式的值。 也就是说它要执行的python代码只能是单个表达式&#xff08;注意eval不支持任何形式的赋值操作&…

git命令 cherry-pick

参考&#xff1a;https://blog.csdn.net/weixin_42585386/article/details/128256149 https://blog.csdn.net/weixin_44799217/article/details/128279250 merge和cherry-pick的区别&#xff1a; merge&#xff1a;是把某一个代码分支完全合并到当前的代码分支。完全合并的意…

TensorFlow实战教程(一)-TensorFlow环境部署

从本篇文章开始,作者正式开始研究Python深度学习、神经网络及人工智能相关知识。第一篇文章主要讲解神经网络基础概念,同时讲解TensorFlow2.0的安装过程及基础用法,主要结合作者之前的博客和"莫烦大神"的视频介绍,后面随着深入会讲解具体的项目及应用。基础性文章…

代码随想录刷题】Day17 二叉树04

文章目录 1.【110】平衡二叉树&#xff08;优先掌握递归&#xff09;1.1 题目描述1.2 解题思路1.3 java代码实现 2.【257】二叉树的所有路径&#xff08;优先掌握递归&#xff09;2.1 题目描述2.2 解题思路2.3 java代码实现 3.【404】左叶子之和&#xff08;优先掌握递归&#…

Spring 配置

配置文件最主要的目的 : 解决硬编码的问题(代码写死) SpringBoot 的配置文件,有三种格式 1.properties 2.yaml 3.yml(是 yaml 的简写) SpringBoot 只支持三个文件 1.application.properties 2.application.yaml 3.application.yml yaml 和 yml 是一样的,学会一个就行…

一文让你上手Linux常用命令(考前十分钟快速突击+零基础阅读)

文章目录 前言Linux 常用命令1. 基本操作lscdpwd 2. 对文件的操作touchcatechovim 3. 对目录的操作mkdirrm 4. 移动文件 / 目录的操作cpmv 5. 总结基本操作6. 必不可少的实用操作mangreppsnetstat 总结 前言 本文内容为 Linux 的一些超常用命令, 内容不多且十分实用, 这些命令…

三、Keil安装芯片包、下载固件库、建立STM32工程模板

目录 一、首先在Keil软件上安装好芯片包 二、下载官方固件库 三、建立基于固件库的Keil5工程模板 一、首先在Keil软件上安装好芯片包 STM32有很多系列的芯片&#xff0c;我们平常用的最多的是STM32F1系列的&#xff0c;因此安装F1系列的芯片包在我们初学时&#xff0c;只按照…