自定义注解+拦截器完成接口IP白名单功能
从JDK5.0开始Java就增加了对元数据(MateData)的访问支持也就是注解Annotation。就目前而言注解是一种趋势,一定程度上可以说:框架 = 注解 + 反射 + 设计模式。注解其实就是给代码做特殊标记,这些标记信息可以在编译、类加载、运行时被读取到并执行相应的处理。言外之意注解允许开发者在不改变原有的逻辑、代码的情况下,在源文件中嵌入一些补充信息,增强程序的功能。例如配置应用程序的切面信息!处理接口方法的日志、安全、缓存、事务等一系列前置逻辑。
1、回顾JavaSE注解
1.1、注解的使用示例
注解可以像修饰符一样被使用,可以用于修饰包、类、构造器、方法、成员变量、参数、局部变量等。
例如常见的注解使用方式有:
1、生成文档相关的注解
public class Test {
public static void main(String[] args) {
}
}
2、JDK 内置的三个基本注解
@Override :用于修饰方法,表示一个方法声明打算重写超类中的另一个方法声明。@Deprecated :用于修饰方法、属性、类。表示不鼓励开发者使用这样的元素,通常是因为它很危险或者存在更好的选择。@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 注解代替
public class AnnotationTest {
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_COMMITTED, readOnly = false, timeout = 3)
public void buyBook(String username, String isbn){
int price = bookShopDao.findBookPriceByIsbn(isbn);
bookShopDao.updateBookStock(isbn);
bookShopDao.updateUserAccount(username, price);
}
}
等等,在JavaEE 中注解才被大量使用,占据了更重要的角色,代替了JavaEE 旧版中所遗留的冗余代码和繁琐的XML 配置等。
1.2、元注解
在了解如何自定义注解时,我们还要先了解元注解 。元注解就是对现有的注解进行解释说明的注解。JDK5.0也提供了4个标准的元注解(Meta-Annotation ),分别是:
@Retention :指定所修饰的注解的生命周期。例如可以配置SOURCE / CLASS / RUNTIME 。只有声明为RUNTIME生命周期的注解,才能通过反射获取@Target :用于修饰注解的使用范围(修饰注解可用在类、属性、方法等那些程序元素)@Documented :所修饰的注解在被javadoc 解析时,保留下来@Inherited :被它修饰的注解将具有继承性
1、@Retention 的使用
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 |
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface IPWhiteList {
}
1.3、自定义注解
前面知识了解后,自定义注解也很简单
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface IPWhiteList {
String[] whiteIpList();
String regex() default "127.0.0.*";
String range();
}
2、拦截器开发
2.1、功能点描述
自定好注解后,我们就要来完成拦截器的编码工作。想要实现的效果为在某个controller 层的请求方法上添加我们自定义的注解,自定义的注解配置可访问的IP地址白名单。然后我们通过拦截器,获取客户端的真实IP地址与注解配置信息。进行相关校验(校验规则也是自定义),如果校验通过则允许访问或执行请求方法否则不予访问。
例如:当检测到客户端IP地址与注解配置的IP地址没有在规则允许范围内,就返回如下信息。
2.2、ip白名单拦截器编写
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 {
HandlerMethod handlerMethod = (HandlerMethod) handler;
IPWhiteList ipWhiteList = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), IPWhiteList.class);
if (ipWhiteList == null) {
return true;
}
String clientIp = getClientIpAddr(request);
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();
}
if (ip != null && ip.length() > 15) {
if (ip.indexOf(",") > 0) {
ip = ip.substring(0, ip.indexOf(","));
}
}
return ip;
}
}
2.3、注册拦截器并指定拦截路径规则
拦截器注册到容器中(实现 WebMvcConfigurer 的 addInterceptors() 方法),并制定拦截规则。如:
@Configuration
public class IPWhiteListWebMvcConfiguration implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new IPWhiteListInterceptor())
.addPathPatterns("/user/**");
}
}
2.4、编写测试方法进行测试
注意看注解@IPWhiteList 的使用。
@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
|