设计接口时,为其添加签名鉴权---详细教程

本文详细介绍了接口签名的重要性,包括原理、组成部分(如appid/appsecret、时间戳和版本号)以及如何在Java代码中实现签名验证。通过Restful风格的示例展示了如何在实际项目中操作和获取请求参数。
摘要由CSDN通过智能技术生成

 一、何为签名

        我们知道无论是restful api还是传统接口、亦或是其他形式接口的调用,接口签名都是非常重要的安全机制,它可以确保请求的发起者是经过认证和授权的客户端,同时也可以防止接口被攻击,请求参数被篡改等等。

        用大白话来解释就是,如果你写的接口没有签名验证,那么不管是谁都可以调用,只要路径、参数是对的就可以调用,这样就会非常不安全,因此我们会对接口添加签名验证。

        首先,我们要了解原理,举个例子来讲,假设有一场线上的会议,而会议室呢并不是谁都可以进去的,光有会议室号是不行的还需要密码,如果更严格一些,可能还需要填写公共的信息内容等,那这些密码信息等,都是实现约定好的,提供给相关人就可以。

       同理,签名也是双方约定好的,客户端(调用接口的用户)需要带着事先约定好的签名去调用,然后服务端(被调用)需要对这个签名进行验证,如果是他约定好的,那么就放行,让其直接调用即可。

二、签名设计

    那么签名该如何设计呢?那就要说道签名的规则了。

签名一般会包括以下几部分:

  1.  appid 和 app\secret
  2.  timestamp 时间戳
  3.  version 版本号
  4.  signature 所有数据的签名信息。

 接下来依次介绍他们的作用:

1) appid & appsecret

  appid :应用的标识

  appsecret:私匙(相当于密码)

    通常我们会线下分配appidappsecret,也就是提前声明好,针对不同的调用方分配不同的appid和appsecret。一般来说appid 和appsecret是一一对应的,用于对接口数据进行加密、解密。

2) timestamp(时间戳)用时间戳可以防止暴力请求

    sign机制可以防止参数被篡改,但无法防ddos攻击(第三方使用正确的参数,不停的请求服务器,使之无法正常提供服务)。因此,还需要引入时间戳机制。

    而服务端则需要根据当前时间和sign值的时间戳进行比较,差值超过一段时间则不予通过客户端的请求,直接给客户端响应某些错误提示等。

   若要求不高,则客户端和服务端可以仅仅使用精确到秒或分钟的时间戳,据此形成sign值来校验有效性。这样可以使一秒或一分钟内的请求是有效的。

   若要求较高,则还需要约定一个解密算法,使服务端可以从sign值中解析出发起请求的时间戳。

3) 版本号version

    防止重复提交,至少为10位。针对查询接口,版本号只用于日志落地,便于后期日志核查。 针对办理类接口需校验流水号在有效期内的唯一性,以避免重复请求。

4)signature

    signature 的规则可以随便定义,按照自己的想法即可,我们以 md5(sort(body)+appSecret)举例,即 先对请求体的字段按照字母a-z顺序进行排序,然后在其尾部拼接appSecret,再经过md5计算出签名,然后用户在请求中添加签名即可。

   正常会将appid、timestamp、nonce、signature这四个字段放入请求头中,但我们这里暂时定义规则为只要将body中的key按照大小顺序排好序以后,在拼接上appsecret即可。

三、代码演示

1、传统的http请求
package com.demo.test;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.jfinal.aop.Clear;
import com.jfinal.core.Controller;
import com.jfinal.kit.Prop;
import com.jfinal.kit.PropKit;
import oracle.jdbc.proxy.annotation.Post;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;

import javax.servlet.ServletInputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;

@Clear
public class DemoController extends Controller {
    
    private static final long EXPIRE_TIME = 10 * 60 * 1000; // 10分钟的毫秒数

    public void save() throws IOException {
        // 检查请求方法是否为 POST
        if (!"POST".equalsIgnoreCase(getRequest().getMethod())) {
            System.out.println("Request method"+ getRequest().getMethod());
            renderText("Only POST requests are allowed for this method.");
            return;
        }
        // 获取请求头信息
        String headerValue = getHeader("sign");
        System.out.println("Header Value: " + headerValue);
        // 获取请求参数
        String paramValue = getPara("paramName");
        System.out.println("Request Parameter Value: " + paramValue);

        // 获取请求 URL
        String requestUrl = getRequest().getRequestURL().toString();
        System.out.println("Request URL: " + requestUrl);

        // 获取请求体信息
        String requestBody = getRawRequestBody(getRequest().getInputStream());
        System.out.println("Request Body: " + requestBody);

        JSONObject pass = validateSign(headerValue,requestBody);
        if(pass.getString("success").equals(false)){
            logger.warn("签名认证失败,请求接口:{},请求IP:{},请求参数:{}"+requestUrl+ JSON.toJSONString(paramValue));
//            String url =request.getRequestURI()+"?"+ getIpAddress(request)+ JSON.toJSONString(request.getParameterMap());
//            CacheKit.put("eam", "url", url);

            renderJson(pass);
        }else{
            //成功逻辑
            renderJson(pass);
        }
//        renderText("POST request processed");
    }
    private JSONObject validateSign(String requestSign,String requestBody) throws IOException {
        JSONObject reponseJson = new JSONObject();
        String msg;
        boolean flag;
        try {
            if (StringUtils.isEmpty(requestSign)) {
                logger.info("签名为空, signature=" + requestSign);
                msg = "签名为空";
                reponseJson.put("msg", msg);
                reponseJson.put("flag", false);
                return reponseJson;
            }

            //时间戳
            JSONObject jsonObject = JSONObject.parseObject(requestBody);
            Long now = System.currentTimeMillis();
            Long requestTimestamp = Long.parseLong( jsonObject.getString("timestamp"));
            if ((now - requestTimestamp) > EXPIRE_TIME) {
                logger.info("请求时间超过规定范围时间10分钟, signature=" + requestSign);
                msg = "请求时间超过规定范围时间10分钟";
                reponseJson.put("msg", msg);
                reponseJson.put("flag", false);
                return reponseJson;
            }

            //排序 requestBody
            String body = sortJsonString(requestBody);

            //获取密钥,这里密钥为了方便,写在了配置文件中,可随意定义一些复杂内容如jgjgro4h3h5b5b5b9nnkx0fke
            Prop p = PropKit.use("config.properties");
            String secret = p.get("emsSecret");
            String ls = body + secret;
            String sign = DigestUtils.md5Hex(ls.getBytes());//混合密钥md5,加密后的sign,用它来和请求传过来的sign对比,如果一致则通过

            if(StringUtils.equals(sign, requestSign)){
                logger.info("请求时间超过规定范围时间10分钟, signature=" + requestSign);
                msg = "请求成功!";
                reponseJson.put("msg", msg);
                reponseJson.put("flag", true);
                return reponseJson;
            }else {
                msg = "请求失败!sign不匹配";
                reponseJson.put("msg", msg);
                reponseJson.put("flag", false);
                return reponseJson;
            }

        }catch (Exception e){
            logger.error("Error in execute EmaController. The wrong message is:"
                    + e.getMessage());
        }
        msg = "请求失败";
        reponseJson.put("msg", msg);
        reponseJson.put("flag", false);
        return reponseJson;
    }
    private static String sortJsonString(String requestBody) throws JsonProcessingException {
        // 将 JSON 字符串转换为 JsonNode
        ObjectMapper objectMapper = new ObjectMapper();
        JsonNode jsonNode = objectMapper.readTree(requestBody);

        // 使用 TreeMap 对属性进行排序
        TreeMap<String, JsonNode> sortedProperties = new TreeMap<>();
        Iterator<Map.Entry<String, JsonNode>> fields = jsonNode.fields();
        while (fields.hasNext()) {
            Map.Entry<String, JsonNode> entry = fields.next();
            sortedProperties.put(entry.getKey(), entry.getValue());
        }

        // 创建新的 JsonNode,按照字母a-z的顺序排列
        JsonNode sortedJsonNode = objectMapper.valueToTree(sortedProperties);

        // 将排序后的 JsonNode 转换回 JSON 字符串
        String sortedJsonString = objectMapper.writeValueAsString(sortedJsonNode);

        // 输出排序后的 JSON 字符串
        return sortedJsonString;
    }


    // 读取请求体的方法
    private String getRawRequestBody(ServletInputStream inputStream) {
        StringBuilder stringBuilder = new StringBuilder();
        BufferedReader bufferedReader = null;

        try {
            bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
            char[] charBuffer = new char[128];
            int bytesRead;
            while ((bytesRead = bufferedReader.read(charBuffer)) != -1) {
                stringBuilder.append(charBuffer, 0, bytesRead);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (bufferedReader != null) {
                try {
                    bufferedReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        return stringBuilder.toString();
    }
}
2、Restful风格

这里由于无法直接获取请求体,因此封装了个方法getRawRequestBody(), 如果是采用restful风格开发,可以直接通过@ReqequstBody Map<String String>params获取请求体,如:

@RestController
public class UserController {
   
    @PostMapping("/test")
    public void createUser(@RequestBody Map<String String> body) {
   
        // 在这里处理
        System.out.pring(body);
    }
}

获取请求参数如:

@RequestMapping(value = "/test", method = RequestMethod.POST)
@ResponseBody
public StringTestUrl(@RequestParam("username")String username,         
                 @RequestParam("pwd")String pwd)  {
  String txt = username + pwd;
  return txt;
}

 暂时先写到这里。。。未完

  • 30
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: 对于您的问题,我可以回答。在Magic-api中,您可以通过添加接口来提高接口的安全性。您可以使用API密钥、JWT Token等方式进行接口,以确保只有授用户才能访问接口。在具体实现上,您可以在请求头中添加相应的授信息,并在服务端对该信息进行验证,以确定用户是否有访问该接口限。 ### 回答2: 是用于验证API请求是否合法的一种安全措施。在使用magic-api添加接口,可以采用以下步骤: 1. 生成密钥:首先,需要生成一个用于的密钥。可以使用常见的加密算法如HMAC-SHA256等来生成密钥。确保密钥具有足够的复杂度和安全性。 2. 将密钥配置到magic-api:在magic-api的配置文件中,添加一个相关的配置项,将生成的密钥配置到该项中。这个配置项可以是一个字符串,也可以是一个文件路径,存储密钥的值。 3. 添加中间件:在接口请求处理流程中,添加一个中间件。该中间件的作用是在请求到达API处理逻辑之前,对请求进行验证。通过读取配置的密钥,对请求的参数、头部信息等进行加密或签名,并与中间件请求中的加密或签名进行比对。 4. 验证结果:中间件会返回一个结果,通常可以是布尔值或者一个带有信息的对象。根据结果,可以决定是否允许继续处理该请求。如果失败,返回一个相应的错误信息,并拒绝该请求。 5. 日志记录:为了追踪和审计API请求的情况,可以在中间件中添加日志记录的功能。记录每个请求的结果、间、请求参数等信息,以便后续的统计和分析。 通过以上步骤,可以在magic-api中添加接口功能,提高API的安全性和可靠性,确保只有经过合法的请求能够被正常处理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿土不土

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值