欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

java安全入门篇之接口验签

程序员文章站 2023-10-31 19:24:28
文章大纲 一、加密与验签介绍二、接口验签实操三、项目源码下载 一、加密与验签介绍 大多数公共网络是不安全的,一切基于HTTP协议的请求/响应(Request or Response)都是可以被截获的、篡改、重放(重发)的。因此我们需要考虑以下几点内容: 防伪装攻击(案例:在公共网络环境中,第三方 有 ......

文章大纲

一、加密与验签介绍
二、接口验签实操
三、项目源码下载

 
java安全入门篇之接口验签

一、加密与验签介绍

  大多数公共网络是不安全的,一切基于http协议的请求/响应(request or response)都是可以被截获的、篡改、重放(重发)的。因此我们需要考虑以下几点内容:

  1. 防伪装攻击(案例:在公共网络环境中,第三方 有意或恶意 的调用我们的接口)
  2. 防篡改攻击(案例:在公共网络环境中,请求头/查询字符串/内容 在传输过程被修改)
  3. 防重放攻击(案例:在公共网络环境中,请求被截获,稍后被重放或多次重放)
  4. 防数据信息泄漏(案例:截获用户登录请求,截获到账号、密码等)

二、接口验签实操

1. 实操说明

  接口加密与验签的方法有非常多,比如rsa(后期进行讲解),基于token等方式,而对于普通项目,我认为最重要的是防伪装攻击、防篡改攻击、防重放攻击。因为接下来的实操,主要围绕以下几点进行。

2. 逻辑讲解

客户端操作
(1)用户登录成功后,会接收到对应的key值和key过期时间,该key是经过32位小写加密,且编码格式为utf-8
(2)接口请求时,将请求的参数,通过key-value方式进行字典升序,且编码成utf-8形式
(3)将key值拼接在升序且编码后的字符串前面,进行md32位小写加密,其编码成utf-8形式形成签名,连同请求参数一同发送至后台
(4)退出登录时,需要通知后台失效该用户的key
(5)补充说明1:对于登录接口,如果检测到用户账号密码错误,则判断错误次数后,在一定时间内进行登录禁止,如果登录禁止解除后,用户再次出现错误,则延长限制时间
(6)补充说明2:对于无需登录接口,需要限制客户端请求次数,进行接口防刷保护

服务端操作
(1)当用户登录成功时,生成与该用户对应的key值返回给用户端,同时将id与key缓存在redis中
(2)当接收到请求时,根据请求id去redis查询对应key是多少,查不到则代表没有请求权限,将该用户系统信息,请求项目名、接口名,请求时间、错误类型(用户信息不正确/参数与签名不一致)存进redis(缓存进磁盘),当ip错误次数超过一定次数后,限制ip访问项目
(3)将key和请求参数按客户端同样方式进行签名,与请求的sign进行比较
(4)如果验签不一致,将该用户系统信息,请求项目名、接口名,请求时间、错误类型(用户信息不正确/参数与签名不一致)存进redis,当ip错误次数超过一定次数时,限制ip访问所有项目,若验签通过,则进行接口放行,且将用户系统信息,请求项目名、接口名,请求时间缓存进日志中(存进磁盘)

redis参数需记录信息
1.用户信息:id,用户key,客户端请求系统信息
2.验签错误信息:用户系统信息,请求项目名、接口名、请求时间、错误类型(用户信息不正确/参数与签名不一致)

日志缓存信息
接口请求成功信息:用户系统信息,请求项目名、接口名,请求时间

3.代码讲解

用户登录成功、生成key参数

//模拟用户登录成功
public string getmd5key() {

        return "de456878b58568e29773e6a53b39d6ef";
    }

获取客户端信息

/**
 * 获取客户端的信息
 * @author 吴晓畅
 *
 */
public final class systemutils {
    /**
     * 获取访问者ip
     * 在一般情况下使用request.getremoteaddr()即可,但是经过nginx等反向代理软件后,这个方法会失效。
     *
     * 本方法先从header中获取x-real-ip,如果不存在再从x-forwarded-for获得第一个ip(用,分割),
     * 如果还不存在则调用request .getremoteaddr()。
     * @param request
     * @return
     */
    public  string getipaddr(httpservletrequest request) {
        string ip = request.getheader("x-real-ip");
        if (ip!= null && !"".equals(ip) && !"unknown".equalsignorecase(ip)) {
            return ip;
        }
        ip = request.getheader("x-forwarded-for");
        if (ip!= null && !"".equals(ip)  && !"unknown".equalsignorecase(ip)) {
            // 多次反向代理后会有多个ip值,第一个为真实ip。
            int index = ip.indexof(',');
            if (index != -1) {
                return ip.substring(0, index);
            } else {
                return ip;
            }
        } else {
            return request.getremoteaddr();
        }
    }

    /**
     * 获取来访者的浏览器版本
     * @param request
     * @return
     */
    public  string getrequestbrowserinfo(httpservletrequest request){
        string browserversion = null;
        string header = request.getheader("user-agent");
        if(header == null || header.equals("")){
            return "";
        }
        if(header.indexof("msie")>0){
            browserversion = "ie";
        }else if(header.indexof("firefox")>0){
            browserversion = "firefox";
        }else if(header.indexof("chrome")>0){
            browserversion = "chrome";
        }else if(header.indexof("safari")>0){
            browserversion = "safari";
        }else if(header.indexof("camino")>0){
            browserversion = "camino";
        }else if(header.indexof("konqueror")>0){
            browserversion = "konqueror";
        }
        return browserversion;
    }

    /**
     * 获取系统版本信息
     * @param request
     * @return
     */
    public  string getrequestsysteminfo(httpservletrequest request){
        string systeninfo = null;
        string header = request.getheader("user-agent");
        if(header == null || header.equals("")){
            return "";
        }
        //得到用户的操作系统
        if (header.indexof("nt 6.0") > 0){
            systeninfo = "windows vista/server 2008";
        } else if (header.indexof("nt 5.2") > 0){
            systeninfo = "windows server 2003";
        } else if (header.indexof("nt 5.1") > 0){
            systeninfo = "windows xp";
        } else if (header.indexof("nt 6.0") > 0){
            systeninfo = "windows vista";
        } else if (header.indexof("nt 6.1") > 0){
            systeninfo = "windows 7";
        } else if (header.indexof("nt 6.2") > 0){
            systeninfo = "windows slate";
        } else if (header.indexof("nt 6.3") > 0){
            systeninfo = "windows 9";
        } else if (header.indexof("nt 5") > 0){
            systeninfo = "windows 2000";
        } else if (header.indexof("nt 4") > 0){
            systeninfo = "windows nt4";
        } else if (header.indexof("me") > 0){
            systeninfo = "windows me";
        } else if (header.indexof("98") > 0){
            systeninfo = "windows 98";
        } else if (header.indexof("95") > 0){
            systeninfo = "windows 95";
        } else if (header.indexof("mac") > 0){
            systeninfo = "mac";
        } else if (header.indexof("unix") > 0){
            systeninfo = "unix";
        } else if (header.indexof("linux") > 0){
            systeninfo = "linux";
        } else if (header.indexof("sunos") > 0){
            systeninfo = "sunos";
        }
        return systeninfo;
    }

    /**
     * 获取来访者的主机名称
     * @param ip
     * @return
     */
    public  string gethostname(string ip){
        inetaddress inet;
        try {
            inet = inetaddress.getbyname(ip);
            return inet.gethostname();
        } catch (unknownhostexception e) {
            e.printstacktrace();
        }
        return "";
    }

    /**
     * 命令获取mac地址
     * @param cmd
     * @return
     */
    private  string callcmd(string[] cmd) {
        string result = "";
        string line = "";
        try {
            process proc = runtime.getruntime().exec(cmd);
            inputstreamreader is = new inputstreamreader(proc.getinputstream());
            bufferedreader br = new bufferedreader (is);
            while ((line = br.readline ()) != null) {
                result += line;
            }
        }catch(exception e) {
            e.printstacktrace();
        }
        return result;
    }
    /**
     *
     *
     *
     * @param cmd
     *            第一个命令
     *
     * @param another
     *            第二个命令
     *
     * @return 第二个命令的执行结果
     *
     */

    private  string callcmd(string[] cmd,string[] another) {
        string result = "";
        string line = "";
        try {
            runtime rt = runtime.getruntime();
            process proc = rt.exec(cmd);
            proc.waitfor(); // 已经执行完第一个命令,准备执行第二个命令
            proc = rt.exec(another);
            inputstreamreader is = new inputstreamreader(proc.getinputstream());
            bufferedreader br = new bufferedreader (is);
            while ((line = br.readline ()) != null) {
                result += line;
            }
        }catch(exception e) {
            e.printstacktrace();
        }
        return result;
    }

    /**
     *
     *
     *
     * @param ip
     *            目标ip,一般在局域网内
     *
     * @param sourcestring
     *            命令处理的结果字符串
     *
     * @param macseparator
     *            mac分隔符号
     *
     * @return mac地址,用上面的分隔符号表示
     *
     */

    private  string filtermacaddress(final string ip, final string sourcestring,final string macseparator) {
        string result = "";
        string regexp = "((([0-9,a-f,a-f]{1,2}" + macseparator + "){1,5})[0-9,a-f,a-f]{1,2})";
        pattern pattern = pattern.compile(regexp);
        matcher matcher = pattern.matcher(sourcestring);
        while(matcher.find()){
            result = matcher.group(1);
            if(sourcestring.indexof(ip) <= sourcestring.lastindexof(matcher.group(1))) {
                break; // 如果有多个ip,只匹配本ip对应的mac.
            }
        }
        return result;
    }

    /**
     * @param ip
     *            目标ip
     * @return mac address
     *
     */

    private  string getmacinwindows(final string ip){
        string result = "";
        string[] cmd = {"cmd","/c","ping " + ip};
        string[] another = {"cmd","/c","arp -a"};
        string cmdresult = callcmd(cmd,another);
        result = filtermacaddress(ip,cmdresult,"-");
        return result;
    }
    /**
     *
     * @param ip
     *            目标ip
     * @return mac address
     *
     */
    private  string getmacinlinux(final string ip){
        string result = "";
        string[] cmd = {"/bin/sh","-c","ping " +  ip + " -c 2 && arp -a" };
        string cmdresult = callcmd(cmd);
        result = filtermacaddress(ip,cmdresult,":");
        return result;
    }

    /**
     * 获取mac地址
     *
     * @return 返回mac地址
     */
    public  string getmacaddress(string ip){
        string macaddress = "";
        macaddress = getmacinwindows(ip).trim();
        if(macaddress==null||"".equals(macaddress)){
            macaddress = getmacinlinux(ip).trim();
        }
        return macaddress;
    }

    public  string getsystemmessage(httpservletrequest request)
    {

        string ip = getipaddr(request);

        string messsge = "ip地址为:" + ip + "&浏览器版本为:" + getrequestbrowserinfo(request) + "&系统版本为:" + getrequestsysteminfo(request)
                + "&主机名称为:" + gethostname(ip) + "&mac地址为:" + getmacaddress(ip);

        return messsge;
    }
}

排序算法

/**
 * 对参数按key进行字典升序排列
 */
public class sortutils {

    /**
     * @param paramap 参数
     * @param encode 编码
     * @param islower 是否小写
     * @return
     */
    public static string formaturlparam(map<string, string> paramap, string encode, boolean islower) {
        string params = "";
        map<string, string> map = paramap;

        try {
            list<entry<string, string>> itmes = new arraylist<entry<string, string>>(map.entryset());

            //对所有传入的参数按照字段名从小到大排序
            //collections.sort(items); 默认正序
            //可通过实现comparator接口的compare方法来完成自定义排序
            collections.sort(itmes, new comparator<entry<string, string>>() {
                @override
                public int compare(entry<string, string> o1, entry<string, string> o2) {
                    // todo auto-generated method stub
                    return (o1.getkey().tostring().compareto(o2.getkey()));
                }
            });

            //构造url 键值对的形式
            stringbuffer sb = new stringbuffer();
            for (entry<string, string> item : itmes) {
                if (stringutils.isnotblank(item.getkey())) {
                    string key = item.getkey();
                    string val = item.getvalue();
                    val = urlencoder.encode(val, encode);
                    if (islower) {
                        sb.append(key.tolowercase() + "=" + val);
                    } else {
                        sb.append(key + "=" + val);
                    }
                    sb.append("&");
                }
            }

            params = sb.tostring();
            if (!params.isempty()) {
                params = params.substring(0, params.length() - 1);
            }
        } catch (exception e) {
            return "";
        }
        return params;
    }
}

md5加密算法

public class md5utils {

    private static final string hexdigits[] = {"0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"};

    /**
     * md5加密
     * @param origin 字符
     * @param charsetname 编码
     * @return
     */
    public static string md5encode(string origin, string charsetname){
        string resultstring = null;
        try{
            resultstring = new string(origin);
            messagedigest md = messagedigest.getinstance("md5");
            if(null == charsetname || "".equals(charsetname)){
                resultstring = bytearraytohexstring(md.digest(resultstring.getbytes()));
            }else{
                resultstring = bytearraytohexstring(md.digest(resultstring.getbytes(charsetname)));
            }
        }catch (exception e){
        }
        return resultstring;
    }


    public static string bytearraytohexstring(byte b[]){
        stringbuffer resultsb = new stringbuffer();
        for(int i = 0; i < b.length; i++){
            resultsb.append(bytetohexstring(b[i]));
        }
        return resultsb.tostring();
    }

    public static string bytetohexstring(byte b){
        int n = b;
        if(n < 0){
            n += 256;
        }
        int d1 = n / 16;
        int d2 = n % 16;
        return hexdigits[d1] + hexdigits[d2];
    }

}

对客户端请求参数进行加签

/**
     * 进行加签
     *
     * @param key 用户的key
     *
     * @param valuemap 需要签名的集合,未处理前的
     * @return 处理后,返回的签名值
     */
    public string getsign(string key, map<string, string>  valuemap)
    {

        string sorevaluemap = sortutils.formaturlparam(valuemap, "utf-8", true);//对参数按key进行字典升序排列

        string signvlue = key + sorevaluemap;//将key拼接在请求参数的前面

        string md5signvlues = md5utils.md5encode(signvlue, "utf8");//形成md5加密后的签名

        return md5signvlues;

    }

进行验签

 /**
     * 进行验签操作
     *
     * @param valuemap 请求参数
     *
     * @param sign 接口调用方传过来的sign
     *
     * @return 验签成功返回true  否则返回false
     */
    public boolean verifysign(map<string, string>  valuemap, string sign)
    {

        system.out.println("服务器接收签名为:"+sign);

        string sorevaluemap = sortutils.formaturlparam(valuemap, "utf-8", true);//对参数按key进行字典升序排列

        string signvlue = getmd5key() + sorevaluemap;//将key拼接在请求参数的前面

        string md5signvlues = md5utils.md5encode(signvlue, "utf8");//形成md5加密后的签名

        system.out.println("服务端处理得到签名为:"+md5signvlues);

        if(md5signvlues.equals(sign))
        {
            return true;
        }

        return false;
    }

测试签名算法

@test
//    public void testsigm(httpservletrequest request)
    public void testsigm()
    {

//        systemutils systemutils = new systemutils();
//
//        system.out.println(systemutils.getsystemmessage(request));

        map<string, string> map = new hashmap<string, string>();

        map.put("a", "200");

        map.put("title", "测试标题");

        map.put("content", "测试内容");

        map.put("order_no","1807160812023");

        map<string, string> map2 = new hashmap<string, string>();

        map2.put("a", "200");

        map2.put("title", "测试标题");

        map2.put("content", "测试内容");

        map2.put("order_no","1807160812023");

        string sign = getsign(getmd5key(), map);

        system.out.println(verifysign(map2, sign));

    }

运行结果如下所示:

 
java安全入门篇之接口验签

三、项目源码下载

链接:https://pan.baidu.com/s/1vguxjtry-v5tlqhjtckdbw
提取码:qy38