关于接口保护的一些想法

目录

模块介绍

父模块

entity模块和utils模块

eureka模块

feign模块

mysql模块和redis模块

 User模块和Order模块

GateWay模块

security模块

接口安全的实现

token验证

黑名单校验

拦截器注册

注解使用

接口保护测试

示例代码 


最近搞到一个关于接口保护的题目,主要是对开放接口进行保护,拦截恶意流量,不能影响正常用户的使用。

 总体的思路:

 

于是就搭建了一个springcloud的项目,来实现对接口的保护。项目结构如下:

模块介绍

父模块

这里使用了maven多模块管理的能力,parent模块为父模块,删除了src目录,将pom文件中的打包方式改为pom。

主要功能是为子模块提供统一的依赖

下图是pom文件的内容:

entity模块和utils模块

提供统一的实体类(User、Order等)和工具类(TokenUtil等)。把项目所需的实体类都写在这里,其它需要实体类的模块依赖这个模块。

eureka模块

eureka是注册中心,没什么好说的。

feign模块

feign模块主要是一些接口,对应了项目的远程接口,mysql接口,redis接口。

mysql模块和redis模块

是远程提供者,提供了项目所需的mysql能力和redis能力。

 User模块和Order模块

提供用户和订单相关的业务接口。通过feign远程调用mysql和redis接口。

GateWay模块

zuul网关,对外提供项目的统一入口。

security模块

安全模块,实现了token验证和黑名单功能。是接口保护的主要实现模块。

接口安全的实现

接口安全的功能都是通过注解实现的。

token验证

1.创建了一个RequireLogin注解。被这个注解修饰的方法需要通过登录验证后才能被调用。 在这个接口中可以配置所需的权限和角色,以便做接口角色控制和权限控制。

Symbol是一个枚举类,为了实现多个权限之间、多个角色之间,角色和权限之间是使用“&”还是“|”连接创建的。

2.使用HandlerInterceptorAdapter来拦截请求

/**
 * 登录拦截
 * @author Mr.Wan
 * @date 2022/10/12 - 20:17
 */
public class LoginFilter extends HandlerInterceptorAdapter {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HandlerMethod handlerMethod = (HandlerMethod)handler;
        RequireLogin requireLgoinAnnotion = handlerMethod.getMethodAnnotation(RequireLogin.class);
        //不需要登录
        if (null == requireLgoinAnnotion){
            return true;
        }else{
            String token = JWTUtil.getToken(request);
            if (null == token){
                System.out.println("未登录");
                //没有登录
                return false;
            }
            if (!JWTUtil.verify(token)){
                //登录失效
                System.out.println("登录失效");
                return false;
            }
            ResultInfo<User> resultInfo = RedisUtil.getUser(token);
            if (!resultInfo.isFlag()){
                //redis中的信息失效
                System.out.println("登录失效");
                return false;
            }
            User user = resultInfo.getData();
            //获取角色、权限、连接符
            String[] authorities = requireLgoinAnnotion.authorities();
            String[] roles = requireLgoinAnnotion.roles();
            Symbol authoritiesRolesConnection = requireLgoinAnnotion.authoritiesRolesConnection();
            Symbol authoritiesConnection = requireLgoinAnnotion.authoritiesConnection();
            Symbol rolesConnection = requireLgoinAnnotion.rolesConnection();

            boolean authoritiesMeet = isMeet(user.getAuthorities(),authorities,authoritiesConnection);
            boolean rolesMeet = isMeet(user.getRoles(),roles,rolesConnection);

            //返回权限验证结果
            if (authoritiesRolesConnection.isAnd()){
                return authoritiesMeet & rolesMeet;
            }else{
                return authoritiesMeet | rolesMeet;
            }
        }

    }

    /**
     * 检查角色或权限是否满足
     * @param src 用户的权限或角色
     * @param target 需求的权限或角色
     * @param symbol 连接符
     * @return 如果满足要求返回true,反之返回false
     */
    private boolean isMeet(List<String> src, String[] target, Symbol symbol){
        //没有要求直接返回true
        if (null == target || 0 == target.length){
            return true;
        }
        if (symbol.isAnd() && target.length > src.size()){
            return false;
        }
        for (String t : target){
            //找到这个字符,要求为or,返回true
            if (src.contains(t)){
                if (!symbol.isAnd()){
                    return true;
                }
            }else{
                //没找到字符,要求全部满足,返回false;
                if (symbol.isAnd()){
                    return false;
                }
            }
        }
        //要求是and,到现在还没返回说明全部找到了,要求是or反之
        return symbol.isAnd();
    }
}

黑名单校验

使用redis进行黑名单校验,当有请求被拦截时,先去获取到ip,并在redis中对ip的访问次数加1,当ip访问次数达到被认为是注水时,就将这个ip加入黑名单,加入黑名单的ip无法访问项目中的接口。

1.实现一个RestrictCalls注解。注解中的count表示一秒内到达count次访问就将这个ip加入黑名单。其实可以的话,能做的更加精细,对时间、访问失败次数等都可以做限制。

/**
 * @author Mr.Wan
 * @date 2022/10/12 - 21:23
 */
public class RestrictFilter extends HandlerInterceptorAdapter {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String ip = getIp(request);
        if (!RedisUtil.isInBlack(ip)) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            RestrictCalls restrictCallsAnnotion = handlerMethod.getMethodAnnotation(RestrictCalls.class);
            if (null != restrictCallsAnnotion) {
                return addRequsetCount(ip, restrictCallsAnnotion);
            }
            return true;
        }
        //打日志
        System.out.println(ip + "在黑名单中");
        //抛异常也拦截
        return false;
    }


    /**
     * 添加访问记录
     *
     * @param ip                    ip地址
     * @param restrictCallsAnnotion 限制注解
     * @return 是否被加入黑名单
     */
    public boolean addRequsetCount(String ip, RestrictCalls restrictCallsAnnotion) {
        long max = restrictCallsAnnotion.count();
        long count = RedisUtil.blackCount(ip);
        if (max == count){
            RedisUtil.blackAdd(ip);
            return false;
        }
        return true;

    }


    /**
     * 获取ip
     *
     * @param request 请求对象
     * @return 返回ip地址
     */
    private String getIp(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");
        if (null == ip || 0 == ip.length() || "unknown".equals(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (null == ip || 0 == ip.length() || "unknown".equals(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (null == ip || 0 == ip.length() || "unknown".equals(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }
}

拦截器注册

为了方便设置为全部路径都要拦截

 

注解使用

user模块和order模块对security模块依赖。将注解添加到需要被保护的方法。(这边提一嘴,为了使用的方便,可以将注解修改成可以修饰类的,使用HandlerMethod的getBean()和getBeanType()方法可以得到被调用方法所在的类和类的类对象,从而实现对整个类中的方法都进行保护,简化注解的使用)。

这里我在Order模块里对获取订单的方法进行了保护,为了方便测试,设置为一秒访问3次就认为是注水,需要登录后才能访问。设置需要的权限为order-get和order-delete,需要的角色为normal。并且权限至少满足一个并且角色至少满足一个的情况下才允许访问。

接口保护测试

 

试一下看看好不好使。启动需要的服务:

postman没有携带登录信息,直接去访问订单获取接口(被拦截) 

 postman携带登录信息,用户没有权限访问接口,去访问订单获取接口(被拦截) 

用户ls只有nromal角色,没有任何权限,不满足接口权限要求。

postman携带登录信息,用户满足权限和角色要求,成功调用接口

用户zs角色为nromal,权限有order-get和order-delete,满足((order-get | order-delete) & nromal)的要求。

当快速发送请求后,ip被加入黑名单,查看控制台打印信息(代码中使用了控制台打印代替日志打印)

示例代码 

以上就是我对接口保护做的全部工作。代码上传到了gitee仓库,仓库地址:https://gitee.com/syyrjx/open-interface-protection.git

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值