自定义注解加 AOP 实现服务接口鉴权以及内部认证

注解


何谓注解?


在Java中,注解(Annotation)是一种特殊的语法,用@符号开头,是 Java5 开始引入的新特性,可以看作是一种特殊的注释,主要用于修饰类、方法或者变量,提供某些信息供程序在编译或者运行时使用。

拿熟悉 的@Override 注解来看。

package java.lang;
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

JDK 内置了很多注解(比如 @Override、@Deprecated),其他框架如 Spring 也内置了不少注解,我们也可以自定义注解。

注解的作用

注解的主要作用是提供元数据,具体可以用于以下几个方面:

  • 编译时检查:如@Override可以帮助编译器检查该方法是否正确重写了父类的方法。
  • 代码生成:如@Entity可以告诉框架生成对应的数据库表。
  • 运行时处理:如@Deprecated可以在运行时提醒开发者某个方法或类已经不建议使用。

注解的解析方法有哪几种?


注解只有被解析之后才会生效,常见的解析方法有两种:

  • 编译期直接扫描:编译器在编译 Java 代码的时候扫描对应的注解并处理,比如某个方法使用@Override 注解,编译器在编译的时候就会检测当前的方法是否重写了父类对应的方法。
  • 运行期通过反射处理:像框架中自带的注解(比如 Spring 框架的 @Value、@Component)都是通过反射来进行处理的。也是我们自定义注解中使用最多的。

如何自定义注解?

自定义注解主要包括以下几个步骤:

  • 1.定义注解:使用@interface关键字定义注解。
  • 2.注解元素:在注解中定义元素,就像在接口中定义方法。
  • 3.元注解:使用元注解(如@Retention、@Target等)来描述注解的行为。

1. 定义注解
可以使用@interface关键字来定义一个注解。下面是一个简单的例子:

public @interface MyAnnotation {    
String value();    
int number() default 0;
}

在上面的例子中, MyAnnotation 注解有两个元素: value 和 number 。其中, number 有一个默认值 0 。

2. 元注解
元注解是注解的注解,用来描述注解本身的行为。常见的元注解有:

  • @Retention:指明注解的保留策略。
  • @Target:指明注解的使用目标。

@Retention
@Retention指定了注解的生命周期,它有三个取值:

  • RetentionPolicy.SOURCE:注解只在源代码中存在,编译后就不存在了。
  • RetentionPolicy.CLASS:注解在编译后会存在于.class文件中,但在运行时不会存在。
  • RetentionPolicy.RUNTIME:注解在运行时依然存在,可以通过反射读取。

@Target
@Target指定了注解可以使用的地方,如类、方法、字段等。常见的取值有:

  • ElementType.TYPE:用于类、接口、枚举、注解类型。
  • ElementType.FIELD:用于字段或属性。
  • ElementType.METHOD:用于方法。
  • ElementType.PARAMETER:用于参数。
  • ElementType.CONSTRUCTOR:用于构造函数。
  • ElementType.LOCAL_VARIABLE:用于局部变量。

3. 完整的自定义注解示例
下面是一个包含@Retention和@Target元注解的完整自定义注解示例:

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
// 定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyAnnotation {    
String value();    
int number() default 0;
}

4. 使用自定义注解
定义完注解后,可以在代码中使用它:

public class Test {@MyAnnotation(value = "Test method", number = 42)    
public void testMethod() {        
// 方法的具体实现    
}
}

5. 通过反射读取注解


可以使用反射机制读取并处理注解(本项目中的 AOP 切面原理就是如此):

import java.lang.reflect.Method;
public class Main {    
public static void main(String[] args) throws Exception {        
Method method = Test.class.getMethod("testMethod");if (method.isAnnotationPresent(MyAnnotation.class)) {            
MyAnnotation annotation = method.getAnnotation(MyAnnotation.class);            
System.out.println("Value: " + annotation.value());            
System.out.println("Number: " + annotation.number());        
}    
}
}

Spring 特性

八股中的 Spring 的两大特性熟的不能再熟了吧?


●IoC(控制反转)
●AOP 面向切面编程

面向切面编程 AOP

这里我们重点介绍下 AOP,因为项目中使用到。

面向切面编程是一种编程范式,它允许在不改变业务逻辑代码的情况下,将横切关注点(如日志记录、事务管理、安全检查等)模块化。AOP通过定义切面(Aspect)和切点(Pointcut)来实现这一点。

Spring AOP 提供了多种方式来定义和使用切面,包括:

●注解:使用@Aspect和相关注解(如@Before、@After、@Around等)来定义切面和切点。
●XML配置:在Spring配置文件中定义切面和切点(较少使用,现代开发中更常用注解)。

微服务架构

微服务简而言之就是单个独立的服务,可以独立开发部署维护。而微服务架构是指的多个微服务聚合起来的系统,这个系统涵盖多个微服务,服务与服务之间的通讯、服务监控、服务熔断降级、服务注册、分布式配置、分布式事务等各种解决方案聚合而成的架构体系。

微服务架构有如下优点:

  • 提高开发效率:团队可以并行开发不同的微服务,减少了开发和发布的时间。
  • 增强可维护性:小而专注的代码库更易于理解和维护,降低了技术债务。
  • 灵活的技术选型:不同的微服务可以根据需要使用最合适的技术栈,而无需在整个系统中保持一致。
  • 持续交付和部署:微服务架构支持持续集成和持续交付(CI/CD),使得新功能和修复能够快速上线。
  • 更好的故障隔离:一个微服务的故障不会影响其他微服务的正常运行,从而提高系统的可靠性。
  • 按需扩展:可以独立地扩展需要高负载处理的微服务,优化资源使用和成本。

鉴权基础

鉴权顾名思义就是需要进行权限认证和授权控制,你写好的系统不希望谁都可以访问吧?你写的牛逼的接口也不希望哪个人都可以来蹭一下访问吧?那就需要认证和授权。

专业做这块的有 Spring Security 和 Shiro 这两哥们,当然还有一些其他的框架也是可以做的,但无非核心都在做两件事:

  • 认证
  • 授权



认证,说白了就是登录,传统 web 登录是通过用户名和密码用 Cookie+Session 的方式,这种依赖于服务器本地内存,微服务中,显然不合适。

常见的鉴权方式有以下几种:

用户名和密码

是最传统和常见的鉴权方式,用户通过输入预先设置的用户名和密码进行登录,需要注意密码的存储和传输安全,如使用加盐哈希存储和HTTPS 传输。



多因素认证(MFA)

这是一种增强安全性的方法,通过要求多种不同类型的验证因素来确认用户身份,常见的因素包括:知识因子(密码)、拥有因子(手机验证码)、生物因子(指纹、面部识别)。



OAuth(开放授权)

这是一种一种授权协议,允许第三方应用以有限的权限访问用户资源,而无需暴露用户的凭证。常用于社交登录和API访问控制。


JWT(JSON Web Token)

 
一种基于 JSON 的开放标准(RFC 7519),用于在各方之间传递声明。JWT包含用户信息和签名,可用于鉴权和授权。我们这次也是采用的这种方式进行的鉴权。


 

项目实战中如何做鉴权认证



项目中的架构

微服务架构中,通常有多个独立服务组成,这些服务可能部署在不同的服务器或数据中心, 鉴权机制需要在分布式环境中有效运作,确保各个服务能安全通信,且需要有统一认证中心,我们先来看一张 PmHub 的架构图:

PmHub 中有一个单独的微服务来做认证,也就是认证服务 pmhub-auth,对于 PmHub 而言,请求一般分为 2 种:

  • 通过 API 网关的请求
  • 微服务内部请求


对于这两种请求,都需要进行鉴权,但方式是不一样的

PmHub 中如何做认证

微服务中的认证最多的方式是通过 JWT 令牌的方式,但 JWT 实际上是无状态的,也就是没法确定登录的用户啥时候过期,所以大部分情况下会需要结合 Redis 来设置状态。

将生成的 JWT 字符串在 Redis 上也保存一份,并设置过期时间,判断用户是否登录时,需要先去 Redis 上查看 JWT 字符串是否存在,存在的话再对 JWT 字符串做解析操作,如果能成功解析,就没问题,如果不能成功解析,就说明令牌不合法。

PmHub 也是采取的这个逻辑,这是一个简单的流程图:

在认证服务中,检查用户名密码的正确性,正确的话就生成 JWT 字符串,同时再把数据存入到 Redis 上,然后返回 token 信息。登录请求先经过网关,网关再转发到认证服务,下面是一个具体的流程:

放在系统层面上流程用例比较复杂,为了方便大家理解,可以看如下图:

可以看到用户登录逻辑其实是涉及到多服务交互的,大家可以对着代码看流程图,理解起来会更深入一些。

PmHub 中如何做鉴权

鉴权(或者说是授权)是请求到达每个微服务后,需要对请求进行权限判定,看是否有权限访问,通常不会放在网关中来做。还是在微服务中自己来做。

我们说过,在 PmHub 中,请求主要分为 2 种,外部请求和内部请求,下面是不同的授权思路。


外部请求

PmHub 的做法是:请求到达网关后,通过微服务的自定义请求头拦截器(可以放在公共包下面,每个服务都可以引用),配合自定义注解和 AOP,拦截请求头,获取用户和权限信息,然后进行比对,有权限则放行,没权限则抛出异常。

内部请求

对于内部的请求来说,正常是不需要鉴权的,内部请求可以直接处理。问题是如果使用了 OpenFeign,数据都是通过接口暴露出去的,不鉴权的话,又会担心从外部来的请求调用这个接口,对于这个问题,我们也可以自定义注解+AOP,然后在内部请求调用的时候,额外加一个头字段加以区分。

我采用的是自定义内部请求注解,然后 AOP 控制拦截。

内部请求注解

/*** 内部认证注解* * @author canghe*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface InnerAuth
{    
/*** 是否校验用户信息*/    
boolean isUser() default false;
}

AOP 的切面控制请求是否携带有内部请求的标识:

内部请求切面
 

/*** 内部服务调用验证处理** @author canghe*/
@Aspect
@Component
public class InnerAuthAspect implements Ordered {@Around("@annotation(innerAuth)")    
public Object innerAround(ProceedingJoinPoint point, InnerAuth innerAuth) throws Throwable {        
String source = ServletUtils.getRequest().getHeader(SecurityConstants.FROM_SOURCE);        
// 内部请求验证        
if (!StringUtils.equals(SecurityConstants.INNER, source)) {            
throw new InnerAuthException("没有内部访问权限,不允许访问");        
}String userid = ServletUtils.getRequest().getHeader(SecurityConstants.DETAILS_USER_ID);        
String username = ServletUtils.getRequest().getHeader(SecurityConstants.DETAILS_USERNAME);        
// 用户信息验证        
if (innerAuth.isUser() && (StringUtils.isEmpty(userid) || StringUtils.isEmpty(username))) {            
throw new InnerAuthException("没有设置用户信息,不允许访问 ");        
}        
return point.proceed();    
}

因为使用的是 OpenFeign,请求通过 OpenFeign 调用也需要鉴权,所以我实现了 feign.RequestInterceptor 接口来定义一个 OpenFeign 的请求拦截器,在拦截器中,统一为 OpenFeign 请求设置请求头信息。

/*** feign 请求拦截器** @author canghe*/
@Component
public class FeignRequestInterceptor implements RequestInterceptor {@Override    
public void apply(RequestTemplate requestTemplate) {        
HttpServletRequest httpServletRequest = ServletUtils.getRequest();        
if (StringUtils.isNotNull(httpServletRequest)) {            
Map<String, String> headers = ServletUtils.getHeaders(httpServletRequest);            
// 传递用户信息请求头,防止丢失            
String userId = headers.get(SecurityConstants.DETAILS_USER_ID);            
if (StringUtils.isNotEmpty(userId)) {                
requestTemplate.header(SecurityConstants.DETAILS_USER_ID, userId);            
}            
String userKey = headers.get(SecurityConstants.USER_KEY);            
if (StringUtils.isNotEmpty(userKey)) {                
requestTemplate.header(SecurityConstants.USER_KEY, userKey);            
}            
String userName = headers.get(SecurityConstants.DETAILS_USERNAME);            
if (StringUtils.isNotEmpty(userName)) {                
requestTemplate.header(SecurityConstants.DETAILS_USERNAME, userName);            
}            
String authentication = headers.get(SecurityConstants.AUTHORIZATION_HEADER);            
if (StringUtils.isNotEmpty(authentication)) {                
requestTemplate.header(SecurityConstants.AUTHORIZATION_HEADER, authentication);            
}// 配置客户端IP            
requestTemplate.header("X-Forwarded-For", IpUtils.getIpAddr());        
}    
}

以上,是 PmHub 中的认证鉴权逻辑。

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

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

相关文章

基于定制开发与2+1链动模式的商城小程序搭建策略

摘要&#xff1a;本文探讨商城小程序的搭建策略&#xff0c;对比自主组建团队和第三方开发两种方式&#xff0c;强调以第三方开发模式为主的优势。阐述在第三方开发模式下&#xff0c;结合定制开发和21链动模式&#xff0c;如何搭建一款有助于企业商业模式创新与智能商业升级的…

【韩顺平Java笔记】第5章:程序控制结构

文章目录 102. 回顾上一章节103. 顺序控制103.1 顺序控制 104. 单分支使用104.1 分支控制 if-else 介绍104.2 单分支 105. 单分支流程图106. 双分支使用107. 双分支流程图108. 双分支练习题109. 多分支使用109.1 多分支的流程图 110. 多分支练习1111. 多分支练习2112. 嵌套分支…

大数据-151 Apache Druid 集群模式 配置启动【上篇】 超详细!

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…

网络通信——OSPF协议(基础篇)

这里基础是因为没有讲解OSPF中的具体算法过程&#xff0c;以及其中很多小细节。后续会更新。 目录 一.OSPF的基础信息 二.认识OSPF中的Router ID 三.OSPF中的三张表 四.OSPF中的度量方法&#xff08;计算开销值&#xff09; 五. OSPF选举DR和BDR&#xff08;就是这个区域…

Leetcode3295. 举报垃圾信息

Every day a Leetcode 题目来源&#xff1a;3295. 举报垃圾信息 解法1&#xff1a;哈希 将字符串数组 bannedWords 的字符串保存在一个哈希表里。 遍历字符串数组 message 的每一个字符串 mes&#xff0c;如果 mes 在哈希表中出现&#xff0c;count。 如果 count > 2&a…

剪刀面的做法

1 面和水称重准备好&#xff0c;鸡蛋敲孔流入面粉中两个蛋清。 2 边下水边和面&#xff0c;每次下水不要太多&#xff0c;这是和硬面要注意的&#xff0c;下水多&#xff0c;水用完了&#xff0c;面还没和起来&#xff0c;一边揉面一边搓面盆周围&#xff0c;这样可以使盆光&a…

WebRTC入门

主要参考资料&#xff1a; WebRTC 在 ESP32 系列硬件平台上的实现: https://www.bilibili.com/video/BV1AEHseWEda/?spm_id_from333.337.search-card.all.click&vd_sourcedd284033cd0c4d1f3f59a2cd40ae4ef9 火山 RTC豆包大模型&#xff0c;给用户体验装上银色子弹: https:…

python的内存管理机制

python的内存管理机制主要分为三个部分&#xff1a;引用计数、垃圾回收和内存池机制。 引用计数机制&#xff1a; python通过维护每个对象的引用计数来跟踪内存中的对象。当对象被创建时就会有一个引用计数&#xff0c;当对象不再被使用时&#xff0c;引用计数为0&#xff0c…

SSE的使用

文章目录 SSE的使用前提需求SSE简介使用demo客户端&#xff08;HTML&#xff09;服务端 SSE API常用方法&#xff1a;事件&#xff1a;自定义关联事件&#xff1a; 服务端数据处理 SSE的使用 前提需求 web开发过程中需要前后端进行实时数据或者定时数据推送的需求中&#xff…

WebSocket消息防丢ACK和心跳机制对信息安全性的作用及实现方法

WebSocket消息防丢ACK和心跳机制对信息安全性的作用及实现方法 在现代即时通讯&#xff08;IM&#xff09;系统和实时通信应用中&#xff0c;WebSocket作为一种高效的双向通信协议&#xff0c;得到了广泛应用。然而&#xff0c;在实际使用中&#xff0c;如何确保消息的可靠传输…

websocket集群部署遇到的一些事

最近刚好有个场景&#xff0c;业务处理一份报告需要关注实时处理的进度。 本来打算使用前端轮训方式&#xff0c;但是考虑到这样效率比较低&#xff0c;也无法精确知道处理进度&#xff0c;就想到用websocket和前端实时交互&#xff0c;进度有更新就通知前端&#xff0c;避免了…

2022年6月 Frontier 获得性能第一的论文翻译

为百万兆级加速架构做高性能 Linpack 优化 摘要 我们详细叙述了在 rocHPL 中做的性能优化&#xff0c;rocHPL 是 AMD 对 HPL 基准的开源实现&#xff0c;主要是针对节点进行优化的架构&#xff0c;是为百万兆级系统而设计的&#xff0c;比如&#xff1a;Frontier suppercomput…

SpringCloud源码:服务端分析(二)- EurekaServer分析

背景 从昨日的两篇文章&#xff1a;SpringCloud源码&#xff1a;客户端分析&#xff08;一&#xff09;- SpringBootApplication注解类加载流程、SpringCloud源码&#xff1a;客户端分析&#xff08;二&#xff09;- 客户端源码分析。 我们理解了客户端的初始化&#xff0c;其实…

windows 录音编码为flv格式时,pcm采样格式

这里使用的是0x3e&#xff0c;转换为二进制&#xff1a; 0 0 1 1 1 1 1 0 前四个字节为3&#xff0c;表示Linear Pcm, 后4个字节1 1 1 0 表示44100HZ采样&#xff0c; 16个bit&#xff0c;单声道。 故&#xff0c;windows 音频采样不支持48000HZ频率

c++11~c++20 内联命名空间

在工作&#xff0c;我们经常会引入第三方库&#xff0c;偶尔会碰到同名的函数和类型&#xff0c;造成编译冲突的问题。一般我们可以使用命名空间&#xff0c;例如 #include <iostream> #include <iostream> using namespace std;namespace S1 {void foo(){cout &l…

css3-----2D转换、动画

2D 转换&#xff08;transform&#xff09; 转换&#xff08;transform&#xff09;是CSS3中具有颠覆性的特征之一&#xff0c;可以实现元素的位移、旋转、缩放等效果 移动&#xff1a;translate旋转&#xff1a;rotate缩放&#xff1a;scale 二维坐标系 2D 转换之移动 trans…

VR视频怎样进行加密和一机一码的使用?--加密(一)

在视频加密领域&#xff0c;我们常见接触的就是在普通设备上使用的加密视频&#xff0c;如电脑、手机、平板等。Vr的发展和兴起给人们带来最真实的体验感受&#xff0c;不仅在游戏行业应用较广&#xff0c;在一些影院或者元宇宙文旅、展厅等视频场景也备受青睐。 随着VR视频场景…

使用 Llama 3.1 和 Qdrant 构建多语言医疗保健聊天机器人的步骤

长话短说&#xff1a; 准备好深入研究&#xff1a; 矢量存储的复杂性以及如何利用 Qdrant 进行高效数据摄取。掌握 Qdrant 中的集合管理以获得最佳性能。释放上下文感知响应的相似性搜索的潜力。精心设计复杂的 LangChain 工作流程以增强聊天机器人的功能。将革命性的 Llama …

【LeetCode HOT 100】详细题解之二叉树篇

【LeetCode HOT 100】详细题解之二叉树篇 94 二叉树的中序遍历方法一&#xff1a;递归方法二&#xff1a;迭代 104 二叉树的最大深度方法一&#xff1a;递归方法二&#xff1a;迭代 226 翻转二叉树方法一&#xff1a;递归方法二&#xff1a;迭代 101 对称二叉树方法一&#xff…

【数据结构】---图

图 前言 本篇作为图的基础概念篇&#xff0c; 了解图的离散数学定义&#xff0c; 图的分类&#xff0c; 图模型解决的问题&#xff08;图的应用&#xff09;&#xff0c; 图的相关算法&#xff08;仅仅介绍&#xff0c;具体不在此篇展开&#xff09;。 学习基本路线&#xff…