自定义注解加 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.xdnf.cn/news/1552565.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

Redis: Sentinel哨兵监控架构及环境搭建

概述 在主从模式下&#xff0c;我们通过从节点只读模式提高了系统的并发能力并发不断增加&#xff0c;只需要扩展从节点即可&#xff0c;只要主从服务器之间&#xff0c;网络连接正常主服务器就会将写入自己的数据同步更新给从服务器&#xff0c;从而保证主从服务器的数据相同…

推送k8s镜像到阿里云服务器

1、服务打包 2、打包后进入Dockerfile的同级目录 运行 docker build -t 镜像名:镜像版本 . (这个点是当前目录的意思&#xff0c;不能忽略)例如 docker build -t trac:v1.0.4 .3、上传镜像到阿里云镜像服务 注意选择区域 例如&#xff1a; docker tag 70743d9bdba3 registr…

jni动态库“%1 不是有效的win32应用程序”问题的解决以及一些windows下dll有关命令的记录

一、前因 在windows下用cmakeVS编译了一个jni动态库&#xff0c;再使用java测试程序调用这个动态库的时候报错&#xff1a;“%1 不是有效的win32应用程序” 对于这类问题&#xff0c;一般从以下几个方面考虑&#xff1a; 动态库文件损坏动态库或者其依赖库文件路径错误导致找…

计算机网络:计算机网络概述:网络、互联网与因特网的区别

文章目录 网络、互联网与因特网的区别网络分类 互联网因特网基于 ISP 的多层次结构的互连网络因特网的标准化工作因特网管理机构因特网的组成 网络、互联网与因特网的区别 若干节点和链路互连形成网络&#xff0c;若干网络通过路由器互连形成互联网 互联网是全球范围内的网络…

十二、磁盘的调度算法

1.先来先服务(FCFS) 思想 优点 缺点 按访问请求到达的先后顺序进行处理 公平; 如果请求访问的磁道比较集中的话&#xff0c;算法性能还算过的去 如果有大量进程竞争使用磁盘&#xff0c;请求访问的磁道很分散&#xff0c;则FCFS在性能上很差&#xff0c;寻道时间长。 2.最…

STM32新建工程-基于库函数

一、创建一个新工程 我这里选择STM32F103C8的型号&#xff0c;然后点击OK。 keil5里面的小助手&#xff0c;暂时不用&#xff0c;叉掉 二、为工程添加文件和路径 在工程模板中还需要添加启动文件、系统头文件、系统时钟文件&#xff0c;创建一个文件夹start&#xff0c;将下面…

wenyan:markdown 一键转换文章排版

介绍 今天给大家介绍一个markdown排版成自媒体文章的工具。 markdown 的重要性和便捷性不用再多说&#xff0c;但是从markdown 转换到文章排版&#xff0c;我换了很多个也都很不满意&#xff0c;尤其在不支持markdown的平台&#xff0c;更是一言难尽。 本次介绍的wenyan的核心…

C++语言学习(1): std::endl 在做什么?

std::endl 是一个函数&#xff08;而不是变量&#xff09;&#xff1a; std::endl 会向控制台写入 \n 字符&#xff0c;并且刷新缓冲。 刷新缓冲肯定比不刷新缓冲慢。 这就是为什么有些 guide 里提到&#xff0c;少用 std::endl, 多用 \n.

HarmonyOS NEXT:实现电影列表功能展示界面

时至今日HarmonyOS NEXT早已发布运行了&#xff0c;等其正式推出并大规模商用后&#xff0c;HarmonyOS的历史使命就完成并将退出历史舞台&#xff0c;为用户提供丰富的应用选择。但是Harmony NEXT是在HarmonyOS基础上剔除安卓&#xff08;AOSP&#xff09;后的产品&#xff0c;…

C++进阶(3): 二叉搜索树

二叉搜索树的概念 二叉搜索树又称二叉排序树&#xff0c;它或者是一颗空树&#xff0c;或者具有以下性质的二叉树&#xff1a; 若它的左子树不为空&#xff0c;则左子树上所有的节点的值都小于等于 根节点的值若它的右子树不为空&#xff0c;则右子树上所有的节点的值都大于等…

嘉立创编辑器中删除自己画的封装

快速创建一个元件及封装可参考 需要删除封装的原因 在添加新的元件时&#xff0c;有时候明明关联了封装和符号&#xff0c;但在原理图中添加元件时会出现封装未添加的问题。可能是这个立创EDA中有些功能问题很少使用&#xff0c;所以没完善。而且发现在封装中可以关联器件&am…

【开源鸿蒙】OpenHarmony 5.0.0 发布了,速来下载最新代码

【开源鸿蒙】OpenHarmony 5.0.0 发布了&#xff0c;速来下载最新代码 一、写在前面二、准备命令工具三、配置用户信息四、下载OpenHarmony源码4.1 使用ssh协议下载&#xff08;推荐&#xff09;4.2 使用https协议下载 五、下载编译工具链六、参考链接 今天是9月30号&#xff0c…

ThreadLocal原理解析及面试

基本使用 讲原理之前&#xff0c;我简单写个demo小程序 public class TestThreadLocal {public static void main(String[] args) throws InterruptedException {ThreadLocal<String> tl new ThreadLocal();/**主线程设置了一个值*/tl.set("SSSSSs");//tl.…

阿里云域名注册购买和备案

文章目录 1、阿里云首页搜索 域名注册2、点击 控制台3、域名控制台 1、阿里云首页搜索 域名注册 2、点击 控制台 3、域名控制台

linux如何与网络时间对齐(雪花算法ID重复)

文章目录 前言一、可能引发什么问题&#xff1f;二、调整步骤1.查看当前系统时间2.修改为中国时区3.同步网络时间4. 雪花id重复 总结 前言 linux服务器是部署服务的不二之选,有个小问题不可忽略&#xff1a; 会发现默认的服务器时间并非中国时区,时间也是相差八小时,中国时区…

8640 希尔(shell)排序

### 思路 希尔排序是一种基于插入排序的排序算法&#xff0c;通过将待排序数组分割成多个子序列分别进行插入排序来提高效率。初始增量d为n/2&#xff0c;之后每次减半&#xff0c;直到d为1。 ### 伪代码 1. 读取输入的待排序关键字个数n。 2. 读取n个待排序关键字并存储在数组…

Git傻傻分不清楚(上)

环境&#xff1a;Idea2022.3.3、Git&#xff08;忘辽~&#xff09; 怎么上传自己的项目到Github上&#xff1f; Idea和Github进行账号关联将项目上传到本地仓库&#xff08;Commit&#xff09;将本地仓库中的项目上传到Github上&#xff08;Push&#xff09; 一、关联账号 …

【Java SE 题库】移除元素(暴力解法)--力扣

&#x1f525;博客主页&#x1f525;&#xff1a;【 坊钰_CSDN博客 】 欢迎各位点赞&#x1f44d;评论✍收藏⭐ 目录 1. 题目 2. 解法(快慢“指针”) 3. 源码 4. 小结 1. 题目 给你一个数组 nums 和一个值 val&#xff0c;你需要原地移除所有数值等于 val 的元素。元素的顺…

YOLOv10改进,YOLOv10改进主干网络为GhostNetV2(华为的轻量化架构)

摘要 摘要:轻量级卷积神经网络(CNN)专为移动设备上的应用而设计,具有更快的推理速度。卷积操作只能在窗口区域内捕捉局部信息,这限制了性能的进一步提升。将自注意力引入卷积可以很好地捕捉全局信息,但会极大地拖累实际速度。本文提出了一种硬件友好的注意力机制(称为 D…

PHP安装后Apache无法运行的问题

问题 按照网上教程php安装点击跳转教程&#xff0c;然后修改Apache的httpd.conf文件&#xff0c;本来可以运行的Apache&#xff0c;无法运行了 然后在"C:\httpd-2.4.62-240904-win64-VS17\Apache24\logs\error.log"&#xff08;就是我下载Apache的目录下的logs中&am…