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

news2024/12/22 10:18:53

注解


何谓注解?


在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.coloradmin.cn/o/2183641.html

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

相关文章

中英翻译神器!轻松搞定跨文化沟通

大家好&#xff01;今天咱们来聊聊那些你生活中不可或缺的翻译小助手&#xff1b;不论你是个英语小白&#xff0c;还是希望更快地了解外国文献、掌握外媒信息&#xff0c;或者是从事需要大量翻译工作的小伙伴&#xff0c;总有一款翻译工具能帮你省时省力&#xff0c;让你的生活…

DBCP数据库连接池以及在Tomcat中配置JNDI数据源

前言 数据库连接 数据库连接是指在计算机系统中建立起应用程序与数据库之间的连接通道&#xff0c;用于进行数据的读取和写入操作。通过数据库连接&#xff0c;应用程序可以与数据库进行交互&#xff0c;执行各种数据库操作&#xff0c;如查询数据、插入数据、更新数据和删除数…

算法题总结(四)——螺旋矩阵

螺旋矩阵 59、螺旋矩阵二 给你一个正整数 n &#xff0c;生成一个包含 1 到 n2 所有元素&#xff0c;且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。 示例 1&#xff1a; 输入&#xff1a;n 3 输出&#xff1a;[[1,2,3],[8,9,4],[7,6,5]]示例 2&#xff1a; 输…

2.点位管理开发(续)及设计思路——帝可得后台管理系统

目录 前言一、页面原型二、修改1、页面展示2、新增 3 、总结思路 前言 提示&#xff1a;本篇继续点位管理的改造 一、页面原型 页面展示新增 二、修改 1、页面展示 页面修改&#xff1a;修改标签换行、顺序顺序、地址过长时换行问题&#xff1b; <el-table v-loading…

七,MyBatis-Plus 扩展功能:乐观锁,代码生成器,执行SQL分析打印(实操详细使用)

七&#xff0c;MyBatis-Plus 扩展功能&#xff1a;乐观锁&#xff0c;代码生成器&#xff0c;执行SQL分析打印&#xff08;实操详细使用&#xff09; 文章目录 七&#xff0c;MyBatis-Plus 扩展功能&#xff1a;乐观锁&#xff0c;代码生成器&#xff0c;执行SQL分析打印&#…

愿祖国富强!肌肉水凝胶的奥秘,自协调与光驱动,运动模式大揭秘

大家好&#xff0c;在这个国庆佳节&#xff0c;我们一同感受科技的魅力。今天来了解一种特殊的肌肉样水凝胶&#xff0c;它通过自协调形状变化和摩擦调节&#xff0c;能在光的引导下实现多样运动。这一成果为软机器人发展带来新契机——《Light-steered locomotion of muscle-l…

基于ScriptableObject设计游戏数据表

前言 本篇文章是针对之前对于ScriptableObject概念讲解的实际应用之一&#xff0c;在游戏开发中&#xff0c;我们可以使用该类来设计编辑器时的可读写数据表或者运行时的只读数据表。本文将针对运行时的只读数据表的应用进行探索&#xff0c;并且结合自定义的本地持久化存储方式…

一级建造师备考攻略及一建各科老师推荐(各科四大金刚)

吐血整理:真正的实战派一建名师推荐! 考过的同学一定都知道推荐的老师YYDS! 一级建造师各科老师推荐: 《法规》名师:王欣、王竹梅、陈印、关涛 其他老师:房超、蔡恒、刘丹、武海峰、陈洁、安国庆、桂林 《管理》名师:宿吉南、龙炎飞、李向国、朱俊文 其他老师:缴广才、陈晨…

跟《经济学人》学英文:2024年09月28日这期 The curse of the Michelin star

The curse of the Michelin star Restaurants awarded the honour are more likely to close, research finds 原文&#xff1a; The twelve new restaurants added to the New York Michelin Guide this month, serving up cuisine ranging from “haute French” to “eco…

9.数据结构与算法-单链表,循环链表和双向链表的比较////顺序表和链表的比较

单链表&#xff0c;循环链表和双向链表的时间效率比较 顺序表和链表的区别 存储密度

HarmonyOS Next应用开发——自定义组件的使用

自定义组件的使用 在ArkUI中&#xff0c;UI显示的内容均为组件&#xff0c;由框架直接提供的称为系统组件&#xff0c;由开发者定义的称为自定义组件。在进行 UI 界面开发时&#xff0c;通常不是简单的将系统组件进行组合使用&#xff0c;而是需要考虑代码可复用性、业务逻辑与…

达梦数据库开启归档模式

目录 一、什么是归档模式&#xff1f; 二、开启归档模式的步骤 1、创建归档目录 2、进入dm数据库bin目录 3、登录数据库 4、关闭数据库 5、启动数据库到Mount状态 6、增加本地归档日志文件 7、开启归档 8、启动数据库 9、验证是否开启成功 三、开启归档模式的优…

Lj视频下载器 1.1.37 简洁高效的视频下载工具

Lj视频下载器是一个功能强大的视频下载器&#xff0c;支持直接添加视频地址或 m3u8 资源地址&#xff0c;可以从网页中自动提取视频进行下载。支持多种视频格式&#xff0c;包括 m3u8&#xff0c;并能自动检测并移除广告片段。 大小&#xff1a;19M 百度网盘&#xff1a;https…

Linux CentsOS定时删除一个目录下(包含子目录)的改动时间大于12小时的文件

Shell脚本 文件目录如下图 ** 查找/ai/img/目录下的所有文件** find /ai/img/ -type f查找/ai/img/目录下的所有上次改动时间大于720分钟(12小时)的文件 12 小时&#xff0c;也就是 720 分钟。所以&#xff0c;我们可以使用 -mmin 720 来查找修改时间超过 720 分钟&#xff08;…

uniapp 微信小程序 微信支付

本章的内容我尽量描述的细致一些&#xff0c;哪里看不懂给我评论就可以&#xff0c;我看到进行回复 微信支付大致分为4步&#xff0c;具体看后端设计 1. 获取code 2. 根据code获取openid 3. 根据openid&#xff0c;以及部分订单相关数据&#xff0c;生成prepayId (预支付交易会…

免费 Oracle 各版本 离线帮助使用和介绍

文章目录 Oracle 各版本 离线帮助使用和介绍概要在线帮助下载离线文档包&#xff1a;解压离线文档&#xff1a;访问离线文档&#xff1a;导航使用&#xff1a;目录介绍Install and Upgrade&#xff08;安装和升级&#xff09;&#xff1a;Administration&#xff08;管理&#…

交通场景多目标检测系统源码分享

交通场景多目标检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Comput…

Qt界面优化——绘图API

文章目录 绘图核心API绘制各种形状绘制线段绘制矩形绘制圆形绘制文本设置画笔设置画刷 绘制图片 绘图核心API Qt的各种控件&#xff0c;本质上都是画出来的&#xff0c;这不过这些都是提前画好了&#xff0c;我们拿过来直接使用即可。 实际开发中&#xff0c;可能现有控件无法…

面了智谱大模型算法岗,效率贼高!

最近这一两周不少互联网公司都已经开始秋招提前批面试了。 不同以往的是&#xff0c;当前职场环境已不再是那个双向奔赴时代了。求职者在变多&#xff0c;HC 在变少&#xff0c;岗位要求还更高了。 最近&#xff0c;我们又陆续整理了很多大厂的面试题&#xff0c;帮助一些球友…

使用容器启动的zk无法暴露3888问题解决

1. 问题描述 zk配置如下&#xff1a; 我通过容器启动了一个zk&#xff0c;通过-p 参数暴露了2181和3888端口&#xff0c;容器启动脚本如下&#xff1a; #!/bin/shdocker rm -f myzookeeper1docker run -p 12181:2181 -p 13888:3888 --name myzookeeper1 --restart always …