IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 网络协议 -> 自定义注解+拦截器完成接口IP白名单功能 -> 正文阅读

[网络协议]自定义注解+拦截器完成接口IP白名单功能

作者:token keyword

自定义注解+拦截器完成接口IP白名单功能

从JDK5.0开始Java就增加了对元数据(MateData)的访问支持也就是注解Annotation。就目前而言注解是一种趋势,一定程度上可以说:框架 = 注解 + 反射 + 设计模式。注解其实就是给代码做特殊标记,这些标记信息可以在编译、类加载、运行时被读取到并执行相应的处理。言外之意注解允许开发者在不改变原有的逻辑、代码的情况下,在源文件中嵌入一些补充信息,增强程序的功能。例如配置应用程序的切面信息!处理接口方法的日志、安全、缓存、事务等一系列前置逻辑。

1、回顾JavaSE注解

1.1、注解的使用示例

注解可以像修饰符一样被使用,可以用于修饰包、类、构造器、方法、成员变量、参数、局部变量等

例如常见的注解使用方式有:

1、生成文档相关的注解

/*
@author 标明开发该类模块的作者,多个作者之间使用,分割
    @version 标明该类模块的版本
    @see 参考转向,也就是相关主题
    @since 从哪个版本开始增加的
    @param 对方法中某参数的说明,如果没有参数就不能写
    @return 对方法返回值的说明,如果方法的返回值类型是void就不能写
    @exception 对方法可能抛出的异常进行说明 ,如果方法没有用throws显式抛出的异常就不能写 其中:
        @param @return 和 @exception 这三个标记都是只用于方法的。
        @param的格式要求:@param 形参名 形参类型 形参说明
        @return 的格式要求:@return 返回值类型 返回值说明
        @exception的格式要求:@exception 异常类型 异常说明
        @param和@exception可以并列多个
*/
/**
 * @description:
 * @author: laizhenghua
 * @date: 2022/5/5 17:37
 */
public class Test {
    /**
     * 主程序
     * @param args String[] 命令行参数
     */
    public static void main(String[] args) {

    }
}

2、JDK内置的三个基本注解

  1. @Override:用于修饰方法,表示一个方法声明打算重写超类中的另一个方法声明。
  2. @Deprecated:用于修饰方法、属性、类。表示不鼓励开发者使用这样的元素,通常是因为它很危险或者存在更好的选择。
  3. @SuppressWarnings:抑制编译是的警告信息。
public class Test {
    public static void main(String[] args) {
        @SuppressWarnings(value = "unused") // 定义的变量没有使用 使编译器没有提示
        int a = 10;
    }
}

3、代替配置文件的功能

<!-- 配置事务属性 -->
<tx:advice transaction-manager="dataSourceTransactionManager" id="txAdvice">
    <tx:attributes>
    	<!-- 配置每个方法使用的事务属性-->
    	<tx:method name="buyBook" propagation="REQUIRES_NEW" isolation="READ_COMMITTED" read-only="false" timeout="3" />
	</tx:attributes>
</tx:advice>

以上配置就可以使用@Transactional注解代替

/*
spring框架中关于 事务 的管理
 */
public class AnnotationTest {
    @Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_COMMITTED, readOnly = false, timeout = 3)
    public void buyBook(String username, String isbn){
        //1.查询书的单价
        int price = bookShopDao.findBookPriceByIsbn(isbn);
        //2. 更新库存
        bookShopDao.updateBookStock(isbn);
        //3. 更新用户的余额
        bookShopDao.updateUserAccount(username, price);
    }
}

等等,在JavaEE中注解才被大量使用,占据了更重要的角色,代替了JavaEE旧版中所遗留的冗余代码和繁琐的XML配置等。

1.2、元注解

在了解如何自定义注解时,我们还要先了解元注解。元注解就是对现有的注解进行解释说明的注解。JDK5.0也提供了4个标准的元注解(Meta-Annotation),分别是:

  1. @Retention:指定所修饰的注解的生命周期。例如可以配置SOURCE / CLASS / RUNTIME 。只有声明为RUNTIME生命周期的注解,才能通过反射获取
  2. @Target:用于修饰注解的使用范围(修饰注解可用在类、属性、方法等那些程序元素)
  3. @Documented:所修饰的注解在被javadoc解析时,保留下来
  4. @Inherited:被它修饰的注解将具有继承性

1、@Retention的使用

/*
@Retention: 只能用于修饰一个 Annotation 定义, 用于指定该 Annotation 的生命 周期, @Rentention 包含一个 RetentionPolicy 类型的成员变量, 使用 @Rentention 时必须为该 value 成员变量指定值:
    1 RetentionPolicy.SOURCE:在源文件中有效(即源文件保留),编译器直接丢弃这种策略的 注释
    2 RetentionPolicy.CLASS:在class文件中有效(即class保留) , 当运行 Java 程序时, JVM 不会保留注解。 这是默认值
    3 RetentionPolicy.RUNTIME:在运行时有效(即运行时保留),当运行 Java 程序时, JVM 会 保留注释。程序可以通过反射获取该注释
 */
public enum RetentionPolicy {
    SOURCE, CLASS, RUNTIME
}
@Retention(RetentionPolicy.SOURCE) 
@interface MyAnnotation1{
    
}
@Retention(RetentionPolicy.RUNTIME) 
@interface MyAnnotation2{
    
}

在这里插入图片描述
2、@Target的使用

取值类型 / ElementType描述
CONSTRUCTOR用于修饰构造器
LIELD用于修饰域
LOCAL_VARIABLE用于修饰局部变量
METHOD用于修饰方法
PACKAGE用于修饰包
PARAMETER用户修饰参数
TYPE用于描述类、接口、注解类型或enum
/**
 * @description: IP白名单注解
 * @author: laizhenghua
 * @date: 2022/5/15 11:08
 */
@Target({ElementType.METHOD}) // 指定注解可以用在什么地方
@Retention(RetentionPolicy.RUNTIME) // 指定运行时有效
public @interface IPWhiteList {
}

1.3、自定义注解

前面知识了解后,自定义注解也很简单

/*
自定义注解:
    1 注解声明为 @interface
    2 自定义注解自动继承了java.lang.annotation.Annotation接口
    3 Annotation 的成员变量在 Annotation 定义中以无参数方法的形式来声明。其 方法名和返回值定义了该成员的名字和类型。我们称为配置参数。类型只能 是八种基本数据类型、String类型、Class类型、enum类型、Annotation类型、 以上所有类型的数组。
    4 可以在定义 Annotation 的成员变量时为其指定初始值, 指定成员变量的初始 值可使用 default 关键字
    5 如果只有一个参数成员,建议使用参数名为value
    6 如果定义的注解含有配置参数,那么使用时必须指定参数值,除非它有默认 值。格式是 参数名 = 参数值 ,如果只有一个参数成员,且名称为value, 可以省略 value= 
    7 没有成员定义的 Annotation 称为标记; 包含成员变量的 Annotation 称为元数 据 Annotation
    注意:自定义注解必须配上注解的信息处理流程才有意义
 */
 
 /**
 * @description: IP白名单注解
 * @author: laizhenghua
 * @date: 2022/5/15 11:08
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface IPWhiteList {
    /**
     * 详细白名单ip列表配置
     */
    String[] whiteIpList();
    /**
     * *号匹配配置 例如 127.0.0.*
     */
    String regex() default "127.0.0.*";
    /**
     * ip范围配置 例如 127.0.0.[1-10]
     */
    String range();
}

2、拦截器开发

2.1、功能点描述

自定好注解后,我们就要来完成拦截器的编码工作。想要实现的效果为在某个controller层的请求方法上添加我们自定义的注解,自定义的注解配置可访问的IP地址白名单。然后我们通过拦截器,获取客户端的真实IP地址与注解配置信息。进行相关校验(校验规则也是自定义),如果校验通过则允许访问或执行请求方法否则不予访问。

例如:当检测到客户端IP地址与注解配置的IP地址没有在规则允许范围内,就返回如下信息。
在这里插入图片描述

2.2、ip白名单拦截器编写

/**
 * @description: IP白名单拦截器
 * @author: laizhenghua
 * @date: 2022/5/15 11:43
 */
public class IPWhiteListInterceptor implements HandlerInterceptor {
    private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(IPWhiteListInterceptor.class);
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1.获取自定义注解
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        IPWhiteList ipWhiteList = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), IPWhiteList.class);
        if (ipWhiteList == null) {
            return true;
        }
        // 2.获取客户端真实ip地址
        String clientIp = getClientIpAddr(request);
        // 3.校验ip地址
        String[] whiteIpList = ipWhiteList.whiteIpList();
        for (String ip : whiteIpList) {
            if (ip.equals(clientIp)) {
                return true;
            }
        }
        fallback(clientIp, response);
        return false;
    }
    private void fallback(String clientIp, HttpServletResponse response) {
        response.setCharacterEncoding("UTF-8");
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        PrintWriter writer = null;
        try {
            String message = String.format("您的IP地址为[%s],已被系统禁止访问,请联系管理员处理!", clientIp);
            R error = R.error(403, message);
            JSONObject json = new JSONObject(error);
            writer = response.getWriter();
            writer.append(json.toString());
        } catch (IOException e) {
            log.error(e);
        } finally {
            if (writer != null) {
                writer.close();
            }
        }
    }
    private String getClientIpAddr(HttpServletRequest request) {
        String ip = request.getHeader("X-Forwarded-For");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        // 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
        if (ip != null && ip.length() > 15) { // "***.***.***.***".length() = 15
            if (ip.indexOf(",") > 0) {
                ip = ip.substring(0, ip.indexOf(","));
            }
        }
        return ip;
    }
}

2.3、注册拦截器并指定拦截路径规则

拦截器注册到容器中(实现 WebMvcConfigureraddInterceptors() 方法),并制定拦截规则。如:

/**
 * @description:
 * @author: laizhenghua
 * @date: 2022/5/15 13:54
 */
@Configuration
public class IPWhiteListWebMvcConfiguration implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    	// registry 拦截器注册中心
        registry.addInterceptor(new IPWhiteListInterceptor())
                .addPathPatterns("/user/**"); // 只拦截 /user 下的所有请求
    }
}

2.4、编写测试方法进行测试

注意看注解@IPWhiteList的使用。

/**
 * @description:
 * @author: laizhenghua
 * @date: 2022/5/4 17:54
 */
@RestController
@RequestMapping(value = "/user")
public class UserController {
    @Autowired
    private UserService userService;

    @IPWhiteList(whiteIpList = {"127.0.0.2"}, range = "")
    @RequestMapping(value = "/getList", method = RequestMethod.GET)
    public R getList() {
        return R.ok().put("data", userService.getList());
    }
}

我们本地的客户端IP地址是127.0.0.1,而接口http://127.0.0.1:8080/user/getList配置的允许访问的IP地址为127.0.0.2。我们自定义的校验规则也只是判断是否相等,因此本地访问是校验不通过的。
在这里插入图片描述

3、总结

到这里自定义注解配合拦截器完成业务方法的功能增强已经结束,其实也很简单就是简单的处理注解信息。重要的是要学会这种开发模式,去满足特定的应用场景!例如应用系统的日志、安全、缓存、事务等一些场景。这里也只是用一个简单的小例子介绍注解在实际开发中的应用方式。当然IP地址白名单的配置规则我们也只考虑了相等的情况,后面还可以配置范围和*正则匹配,可自行下去思考。

END

THANK YOU

  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2022-05-18 17:59:21  更:2022-05-18 18:00:14 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/19 10:56:59-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码