商城首页欢迎来到中国正版软件门户

您的位置: 首页 > 文章列表 > 编程开发 > 基于Go语言实现安全的分享链接功能(AES加密+SHA256签名+过期防重放)

基于Go语言实现安全的分享链接功能(AES加密+SHA256签名+过期防重放)

  发布于2026-07-15 阅读(0)

扫一扫,手机访问

问题描述

业务线涵盖了PC端、H5、小程序、App这四种终端,而测评类型则有五种。由于时间紧张,App和小程序侧的测评页面统一采用iframe嵌入H5的方式。现在,打算对原有的分享报告功能进行重构,核心焦点在于解决多端通信的兼容性与安全性问题

说到这儿,先画张流程图理理思路——思路这东西,对程序员来说,比代码本身还重要。一个问题,从理解到理清脉络,再到分层次表达出来,其实已经解决了大半。对于架构设计,更是如此。流程如下:

基于Go语言实现安全的分享链接功能(AES加密+SHA256签名+过期防重放)

思路清晰之后,轮到AI出场了。给它的提示词是这样的:

我是一名Go语言后端开发工程师,现在要实现PC端 、H5 、小程序 、App 有分享测评报告的功能 ,要求是对分享的url链接进行加密  要求不能篡改 分享在加密串在答题提交后失效 生成新的加密串查看报告详情 并且兼容之前的用户校验逻辑 被分享人使用分享人的token  
需要注意的点:
1、各端 Http Header头信息 是不同的 
2、App中使用iframe方式嵌入的都是H5
3、报告信息分属于2个不同的服务项
帮我出一套程序架构的设计思路

AI给出的方案虽然详尽,但最终还是要根据实际业务场景来裁剪。我把那些真正贴合需求的思路摘出来,开始了编码与设计。

核心需求梳理

  1. 多端差异:PC、H5、小程序、App各端的Header不同,但App内嵌的H5可以共享一套逻辑。
  2. 安全要求:URL必须加密,且防篡改,报告ID、用户ID这类核心参数不能被人轻易修改。
  3. 业务规则:分享签名只能用一次,答题后查看报告时,需要生成新的加密串。
  4. 兼容要求:不破坏原有的用户校验逻辑,直接复用分享人的Token。
  5. 业务拆分:之前报告分属两个服务,现在是时候统一分享、答题和查看报告的入口了。

设计原则

  1. 无侵入兼容:原有的鉴权和业务逻辑完全不动,只新增一个分享模块。
  2. 统一中间层:屏蔽掉多端的差异,所有终端共用一套加密/解密逻辑。
  3. 安全闭环:从加密、签名、过期、防重放到防篡改,做到全链路防护。
  4. 服务解耦:双服务的报告通过统一的标识来区分,不互相耦合。
  5. Go 语言适配:基于Go标准库实现,不依赖重型组件,保持高性能。

核心模块详细设计

简单复述一下核心业务逻辑:

机构端在PC上对学生档案进行分享,生成一次性的加密串和签名。被分享人凭借这个加密串和签名来完成身份鉴权,接着获取题目、提交答题。答题完成后,旧签名回收作废(如果一直没人答题,15天后自动过期),同时生成新签名用于查看报告。之前的测评中心分散在两个服务中,现在对业务代码进行了整合和优化,统一处理。整个设计的核心,就在于加密串和签名串的传递,以及加密、解密结构体的一致性。结构体解析完,思路也就彻底清晰了,剩下的就是跟前端同学联调测试。

签名逻辑的流转路径如下:
分享人生成签名 → 被分享人打开链接 → 获取题目 → 提交答题 → 后端使旧签名失效 → 生成新签名用于查看报告

1、全局配置

配置文件中需要添加AES加密密钥、签名密钥以及分享链接的默认过期时间。这一步非常重要,每个服务的配置必须保持一致,否则无法正确反解出加密结构体。

# 测评报告
ShareReportAuth:
  AesSecretKey: "xxxxxxxxxxxxxxxxxxxxxxxxxxxx" # AES 加密密钥
  SignSecretKey: "xxxxxxxxxxxxxxxxxxxxxxxxxxxx" # 签名密钥
  ShareExpireHours: 3 # 分享链接默认过期时间(3小时)

2、设计规则

  • URL加密:核心参数全部进行AES加密,并附带SHA256签名。任何修改都会导致验签失败。
  • 携带分享人的StrId,被分享人打开链接后直接复用分享人的权限和信息。
  • 配置过期时间和随机串,防重放,防止链接被恶意利用。
  • 参数还原后,转发到具体的业务逻辑进行处理。

3、生成加密串与签名

加密的步骤很明确:先拼接好业务需要的参数结构体,再序列化成JSON,接着进行AES加密,然后用Base64(统一使用StdEncoding)转成适合放在URL中的字符串,最后生成防篡改签名。全局统一使用`encrypted_string`和`sign`这两个名字,方便参数传递。

type ShareReportRequest struct {
  //业务参数略...
  ExpireAt     int64  `json:"exp,optional"`   // 过期时间戳(防永久有效)
  Nonce        string `json:"nc,optional"`    // 随机串(防重放攻击)
}

// AES 加密 + SHA256 签名
func (l *EvaluationService) SetEncryptSign(payload *ShareReportRequest) (encryptStr string, signStr string, err error) {
  expireHours := l.svcCtx.Config.ShareReportAuth.ShareExpireHours
  AesSecretKey := l.svcCtx.Config.ShareReportAuth.AesSecretKey
  SignSecretKey := l.svcCtx.Config.ShareReportAuth.SignSecretKey
  mergedPayload := &ShareReportRequest{
    //业务参数略...
    ExpireAt:     time.Now().Add(time.Duration(expireHours) * time.Hour).Unix(),
    Nonce:        utils.GenerateUniqueRandomString(16),
  }

  //2. 序列化成 JSON
  jsonBytes, err := json.Marshal(mergedPayload)
  if err != nil {
    return "", "", errors.New("JSON序列化失败")
  }
  // 3. AES 加密
  encryptBytes, err := aesEncrypt(jsonBytes, []byte(AesSecretKey))
  if err != nil {
    return "", "", errors.New("AES加密失败")
  }
  // 4. Base64 转成可放在 URL 中的字符串(统一使用 StdEncoding)
  encryptStr = base64.StdEncoding.EncodeToString(encryptBytes)
  //5. 生成防篡改签名
  signStr = generateSign(encryptStr, SignSecretKey)

  return
}

4、解密步骤

解密时,需要解析出业务所需的结构体。这里有一个关键点:参数必须保持一致,并且要清理掉空字节,否则解密会失败。具体步骤是:首先对加密串和签名进行验签,接着Base64解码(同样使用StdEncoding),然后AES解密,清理空字节,检查是否为有效的JSON,最后校验过期时间和必要字段。

func (l *EvaluationService) GetReportSign(encryptStr, signStr string) (sign *ShareReportRequest, err error) {
    AesSecretKey := l.svcCtx.Config.ShareReportAuth.AesSecretKey
    SignSecretKey := l.svcCtx.Config.ShareReportAuth.SignSecretKey
    var payload ShareReportRequest

    // 先验签
    expectedSign := utils.GenerateSign(encryptStr, SignSecretKey)
    if !verifySign(encryptStr, signStr, SignSecretKey) {
       return nil, errors.New("分享链接已被篡改,验签失败")
    }

    // Base64 解码(统一使用 StdEncoding)
    var encryptBytes []byte
    encryptBytes, err = base64.StdEncoding.DecodeString(encryptStr)
    if err != nil {
       return nil, fmt.Errorf("base64解码失败: %v", err)
    }

    // 4. AES 解密
    jsonBytes, err := utils.AesDecrypt(encryptBytes, []byte(AesSecretKey))
    if err != nil {
       return nil, fmt.Errorf("解密失败: %v", err)
    }

    // 5. 清理空字节
    jsonBytes = removeNullBytes(jsonBytes)

    // 6. 检查是否为有效JSON
    if len(jsonBytes) == 0 {
       return nil, errors.New("解密后数据为空,请检查加密密钥是否正确")
    }

    if err := json.Unmarshal(jsonBytes, &payload); err != nil {
       return nil, fmt.Errorf("json解析失败: %v, 原始数据: %s", err, string(jsonBytes))
    }

    // 7. 校验过期
    if payload.ExpireAt > 0 && payload.ExpireAt < time.Now().Unix() {
       return nil, errors.New("分享链接已过期")
    }

    // 8. 校验必要字段
    if payload.StudentId == 0 || payload.CorpId == 0 {
       return nil, errors.New("分享信息不完整")
    }
    return &payload, nil
}

5、完成

最终生成的URL格式如下,所有的信息与参数都在这个字符串中传递、解码,完成服务间的通信。

# https://你的域名/share/{加密字符串}.{签名}
本文转载于:https://www.jb51.net/jiaoben/362660amy.htm 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。

热门关注