设计一个安全的对外开放接口+实现案例

前言
设计一个安全的对外开放接口

接口安全问题
1、请求身份是否合法?
2、请求参数是否被篡改?
3、请求是否唯一?

AccessKey&SecretKey (开放平台)
请求身份
为开发者分配appid(开发者标识,确保唯一)和secret(用于接口加密,确保不易被穷举,生成算法不易被猜测)。

防止篡改
参数签名

1、生成请求时候的时间戳
数据是很容易被抓包的,但是经过如上的加密,加签处理,就算拿到数据也不能看到真实的数据;但是有不法者不关心真实的数据,而是直接拿到抓取的数据包进行恶意请求;这时候可以使用时间戳机制,在每次请求中加入当前的时间,服务器端会拿到当前时间和消息中的时间相减,看看是否在一个固定的时间范围内比如5分钟内;这样恶意请求的数据包是无法更改里面时间的,所以5分钟后就视为非法请求了;

2、拼接参数,带上时间戳
按照请求参数名的字母升序排列非空请求参数,使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串stringA;
例如:

appid=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8&home=world&name=hello&nonce=d567bcd5-6a04-4e3c-aa56-4774822a390e&secret=AsQa6fqKXkwafq4fr6laZXz7DC0PEBHcVk0&timestamp=1655630951933&work=java

3、对stringA进行MD5运算,并将得到的字符串所有字符转换为大写,得到sign值。

3、发送请求时参数中需要携带sign,不需要携带secret;
例如

http://127.0.0.1:8080/hello?&work=java&appid=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8&name=hello&sign=bf5753e61597122754e920ee86223c80&nonce=1437d89f-0809-496f-81df-506111bb58af&timestamp=1655631068339&home=world

案例

一、客户端 :

    public static void main(String[] args) throws Exception{

        Map<String, String> map = new HashMap<>();
        map.put("appid", "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8");
        map.put("secret", "AsQa6fqKXkwafq4fr6laZXz7DC0PEBHcVk0");
        map.put("timestamp", System.currentTimeMillis() + "");
        map.put("name", "hello");
        map.put("home", "world");
        map.put("work", "java");
        map.put("nonce", UUID.randomUUID().toString());

        // 按map的key字母排序
        Map<String, String> sortMap = MapSort.sortMapByKey(map);
        StringBuilder sb = new StringBuilder();
        for (String key : sortMap.keySet()) {
            sb.append(key).append("=").append(sortMap.get(key)).append("&");
        }
        // 计算签名
        System.out.println("======================: " + sb.toString());
        String sign = MD5Util.string2MD5(sb.toString());
        map.put("sign", sign);
        // 移除密钥
        map.remove("secret");

        // 生成URL
        StringBuilder URL = new StringBuilder("http://127.0.0.1:8080/hello?");
        for (String key : map.keySet()) {
            URL.append("&");
            URL.append(key).append("=").append(map.get(key));
        }

        String url = URL.toString();
        System.out.println(url);
        //Thread.sleep(15000);
        HttpClientUtils.doGet(url);
    }

二、服务端:

package com.test.controller;

import com.test.bean.Result;
import com.test.httpclient.HttpClientUtils;
import com.test.utils.MD5Util;
import com.test.utils.MapSort;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import java.util.*;

/**
 * @Auther: sai
 * @Date: 2022/6/15 0015 21:21
 * @ClassName: HelloController
 * @Version: 1.0
 * @Description:
 */
@Controller
public class HelloController {

    private static final Map<String, String> map = new HashMap<String, String>() {
    };

    static {
        map.put("MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8", "AsQa6fqKXkwafq4fr6laZXz7DC0PEBHcVk0");
    }

    @RequestMapping(value = "/hello", method = RequestMethod.GET)
    @ResponseBody
    public Result hello(HttpServletRequest request) {
        HashMap<String, String> mapParam = new HashMap<>();
        mapParam.put("appid", request.getParameter("appid"));
        mapParam.put("timestamp", request.getParameter("timestamp"));
        mapParam.put("name", request.getParameter("name"));
        mapParam.put("home", request.getParameter("home"));
        mapParam.put("work", request.getParameter("work"));
        mapParam.put("nonce", request.getParameter("nonce"));
        mapParam.put("sign", request.getParameter("sign"));

        // 校验sign是否存在
        if (StringUtils.isEmpty(request.getParameter("sign"))) {
            return new Result("1", "sign参数不正确", "failed");
        }

        //查询数据库appi对应的secret
        String secret = map.get(request.getParameter("appid"));
        if (StringUtils.isEmpty(secret)) {
            return new Result("0", "appid不存在", "OK...");
        }

        // 校验时间
        long newTimeMillis = System.currentTimeMillis();
        if (newTimeMillis - Long.parseLong(request.getParameter("timestamp")) > 15000) {
            return new Result("1", "超时", "failed");
        }

        //计算sign
        mapParam.remove("sign");
        for (String key : mapParam.keySet()) {
            System.out.println(key + " ================== " + mapParam.get(key));
        }
        Map<String, String> sortMap = MapSort.sortMapByKey(mapParam);
        sortMap.put("secret", secret);
        StringBuilder sb = new StringBuilder();
        for (String key : sortMap.keySet()) {
            sb.append(key).append("=").append(sortMap.get(key)).append("&");
        }
        
        // 计算签名
        String newSign = MD5Util.string2MD5(sb.toString());
        if (!newSign.equals(request.getParameter("sign"))) {
            return new Result("1", "签名不正确", "failed");
        }
        return new Result("0", "hello", "OK...");
    }
}

MD5工具类

package com.test.utils;

import java.security.MessageDigest;

/**
 * @Auther: sai
 * @Date: 2022/6/15 0015 22:35
 * @ClassName: MD5Util
 * @Version: 1.0
 * @Description: 用MD5加密解密
 */
public class MD5Util {

    /***
     * MD5加码 生成32位md5码
     */
    public static String string2MD5(String inStr){
        MessageDigest md5 = null;
        try{
            md5 = MessageDigest.getInstance("MD5");
        }catch (Exception e){
            System.out.println(e.toString());
            e.printStackTrace();
            return "";
        }
        char[] charArray = inStr.toCharArray();
        byte[] byteArray = new byte[charArray.length];

        for (int i = 0; i < charArray.length; i++)
            byteArray[i] = (byte) charArray[i];
        byte[] md5Bytes = md5.digest(byteArray);
        StringBuffer hexValue = new StringBuffer();
        for (int i = 0; i < md5Bytes.length; i++){
            int val = ((int) md5Bytes[i]) & 0xff;
            if (val < 16)
                hexValue.append("0");
            hexValue.append(Integer.toHexString(val));
        }
        return hexValue.toString();

    }

    /**
     * 加密解密算法 执行一次加密,两次解密
     */
    public static String convertMD5(String inStr){

        char[] a = inStr.toCharArray();
        for (int i = 0; i < a.length; i++){
            a[i] = (char) (a[i] ^ 't');
        }
        String s = new String(a);
        return s;

    }
    // 测试主函数
    public static void main(String args[]) {
        String s = new String("tangfuqiang");
        System.out.println("原始:" + s);
        System.out.println("MD5后:" + string2MD5(s));  // 20b75697d8bf931a6730662ae117c3bf
        System.out.println("加密的:" + convertMD5(s));
        System.out.println("解密的:" + convertMD5(convertMD5(s)));
    }
}

HttpClientUtils工具类

package com.test.httpclient;

import org.apache.commons.io.IOUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.*;
import java.util.logging.Logger;

/**
 * @Auther: sai
 * @Date: 2022/6/14 0014 22:24
 * @ClassName: HttpClientUtils
 * @Version: 1.0
 * @Description:
 */
public class HttpClientUtils {

    Logger log = Logger.getLogger("com.test.httpclient.HttpClientUtils");

    public static void doGet(String url) {
        try {
            CloseableHttpClient client = HttpClients.createDefault();
            // 发送get请求
            HttpGet request = new HttpGet(url);
            // 发送请求
            HttpResponse httpResponse = client.execute(request);


            // 验证请求是否成功
            if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
                // 得到请求响应信息
                String str = EntityUtils.toString(httpResponse.getEntity(), "utf-8");
                // 返回json
                System.out.println(str);
            }
        } catch (ClientProtocolException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

pom.xml

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.test</groupId>
    <artifactId>oop_Interface</artifactId>
    <version>1.0-SNAPSHOT</version>

    <name>oop_Interface</name>
    <url>http://www.example.com</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.0.RELEASE</version>
    </parent>

    <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
            <version>1.10</version>
        </dependency>

        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.4</version>
        </dependency>

        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.2</version>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.9</version>
        </dependency>


        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.2</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
对外开放接口,需要在自定义的 QTreeWidget 类中添加公共函数或信号槽,这样其他类就可以通过调用这些函数或连接这些信号槽来与该 QTreeWidget 进行交互。 例如,在自定义的 QTreeWidget 类中添加一个公共函数 `addItem(QString text)`,用于向树形控件中添加一个新的节点: ```cpp class MyTreeWidget : public QTreeWidget { Q_OBJECT public: explicit MyTreeWidget(QWidget *parent = nullptr); void addItem(QString text); // 添加新节点 private: // ... }; ``` 然后在实现文件中实现该函数: ```cpp void MyTreeWidget::addItem(QString text) { QTreeWidgetItem* item = new QTreeWidgetItem(); item->setText(0, text); this->addTopLevelItem(item); } ``` 这样其他类就可以通过以下方式调用该函数: ```cpp MyTreeWidget* treeWidget = new MyTreeWidget(this); treeWidget->addItem("new item"); ``` 同样,也可以在自定义的 QTreeWidget 类中添加信号槽来实现与外部类的交互。例如,在 MyTreeWidget 类中添加一个信号 `itemClicked(QTreeWidgetItem* item)`,表示当用户单击某个节点时,将会发出该信号: ```cpp class MyTreeWidget : public QTreeWidget { Q_OBJECT public: explicit MyTreeWidget(QWidget *parent = nullptr); void addItem(QString text); // 添加新节点 signals: void itemClicked(QTreeWidgetItem* item); // 节点被单击时发出信号 private: // ... }; ``` 然后在实现文件中,当用户单击某个节点时,发出该信号: ```cpp MyTreeWidget::MyTreeWidget(QWidget *parent) : QTreeWidget(parent) { connect(this, &QTreeWidget::itemClicked, [this](QTreeWidgetItem* item, int column) { emit itemClicked(item); }); } ``` 这样其他类就可以连接该信号槽来响应用户单击节点的操作: ```cpp MyTreeWidget* treeWidget = new MyTreeWidget(this); connect(treeWidget, &MyTreeWidget::itemClicked, [this](QTreeWidgetItem* item) { qDebug() << "item clicked: " << item->text(0); }); ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值