satoken签名(sign)插件支持多应用改造

Python3.8

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

背景

satoken签名对多应用支持不是很友好, 我在这里记录一下我改造的过程

代码结构

  • config
    • CustomerSaSignConfig
  • handle
    • CustomerSaCheckSignHandler
  • template
    • CustomerSaSignMany
    • CustomerSaSignTemplate

具体代码

  • CustomerSaSignConfig
import cn.dev33.satoken.exception.SaTokenException;
import cn.dev33.satoken.secure.SaSecureUtil;
import cn.dev33.satoken.sign.SaSignManager;
import cn.dev33.satoken.sign.annotation.SaCheckSign;
import cn.dev33.satoken.sign.config.SaSignConfig;
import cn.dev33.satoken.strategy.SaAnnotationStrategy;
import cn.hutool.core.collection.CollUtil;
import com.xm.common.saSign.handle.CySaCheckSignHandler;
import com.xm.common.saSign.template.CySaSignTemplate;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.cloud.context.scope.refresh.RefreshScopeRefreshedEvent;
import org.springframework.context.annotation.*;
import org.springframework.context.event.EventListener;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Map;

@Slf4j
@Configuration
public class CustomerSaSignConfigimplements SmartInitializingSingleton {



    // 1. 启动完成后执行
    @Override
    public void afterSingletonsInstantiated() {
        initCustomSignConfig();
    }

    // 2. 监听 Nacos 配置刷新事件 (关键)
    @EventListener(RefreshScopeRefreshedEvent.class)
    public void onRefresh(RefreshScopeRefreshedEvent event) {
        log.info("检测到配置刷新,重新注册自定义签名配置...");
        initCustomSignConfig();
    }

    private void initCustomSignConfig() {
        // 移除 & 注册 Handler
        SaAnnotationStrategy.instance.removeAnnotationHandler(SaCheckSign.class);
        SaAnnotationStrategy.instance.registerAnnotationHandler(new CySaCheckSignHandler());

        // 重新设置 Template
        SaSignManager.setSaSignTemplate(new CySaSignTemplate());

        // 核心:重新绑定 Digest 算法
        SaSignConfig config = SaSignManager.getConfig();
        config.setDigestMethod((fullStr) -> customDigest(fullStr, config));

        // 处理 signMany (多 AppId 配置)
        Map<String, SaSignConfig> signMany = SaSignManager.getSignMany();
        if (CollUtil.isNotEmpty(signMany)) {
            signMany.forEach((appId, subConfig) -> {
                log.info("重新绑定 appId: {} 的签名函数", appId);
                subConfig.setDigestMethod((fullStr) -> customDigest(fullStr, subConfig));
            });
        }
    }

    /**
     * 抽取的自定义摘要算法方法
     * @param fullStr 待摘要的字符串
     * @param config  SaSign 配置对象
     * @return 摘要后的结果
     */
    private static String customDigest(String fullStr, SaSignConfig config) {
        String digestAlgo = config.getDigestAlgo();
        String secretKey = config.getSecretKey();

        // 1. 处理需要密钥的 HMAC 算法
        if ("hmacSha256".equalsIgnoreCase(digestAlgo)) {
            try {
                Mac mac = Mac.getInstance("HmacSHA256");
                SecretKeySpec secret_key = new SecretKeySpec(
                        secretKey.getBytes(StandardCharsets.UTF_8), "HmacSHA256"
                );
                mac.init(secret_key);
                byte[] binaryData = mac.doFinal(fullStr.getBytes(StandardCharsets.UTF_8));
                return Base64.encodeBase64String(binaryData);
            } catch (NoSuchAlgorithmException | InvalidKeyException e) {
                throw new SaTokenException("HMAC 签名异常", e);
            }
        }
        return switch (digestAlgo.toLowerCase()) {
            case "md5" -> SaSecureUtil.md5(fullStr);
            case "sha1" -> SaSecureUtil.sha1(fullStr);
            case "sha256" -> SaSecureUtil.sha256(fullStr);
            case "sha384" -> SaSecureUtil.sha384(fullStr);
            case "sha512" -> SaSecureUtil.sha512(fullStr);
            default -> throw new SaTokenException("不支持的摘要算法:" + digestAlgo);
        };
    }
}

  • CustomerSaCheckSignHandler
import cn.dev33.satoken.annotation.handler.SaAnnotationHandlerInterface;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.context.model.SaRequest;
import cn.dev33.satoken.sign.annotation.SaCheckSign;
import com.xm.common.saSign.template.CySaSignMany;

import java.lang.reflect.AnnotatedElement;

public class CySaCheckSignHandler implements SaAnnotationHandlerInterface<SaCheckSign> {
    @Override
    public Class<SaCheckSign> getHandlerAnnotationClass() {
        return SaCheckSign.class;
    }

    @Override
    public void checkMethod(SaCheckSign at, AnnotatedElement element) {
        _checkMethod(at.appid(), at.verifyParams());
    }

    public static void _checkMethod(String appid, String[] verifyParams) {
        SaRequest req = SaHolder.getRequest();
        // 如果 appid 为 #{} 格式,则从请求参数中获取
        if(appid.startsWith("#{") && appid.endsWith("}")) {
            String reqParamName = appid.substring(2, appid.length() - 1);
            appid = req.getParam(reqParamName);
        }
        CySaSignMany.getSignTemplate(appid).checkRequest(req, verifyParams);
    }
}

  • CustomerSaSignMany

import cn.dev33.satoken.fun.SaParamRetFunction;
import cn.dev33.satoken.sign.SaSignManager;
import cn.dev33.satoken.sign.config.SaSignConfig;
import cn.dev33.satoken.sign.error.SaSignErrorCode;
import cn.dev33.satoken.sign.exception.SaSignException;
import cn.dev33.satoken.sign.template.SaSignTemplate;
import cn.dev33.satoken.util.SaFoxUtil;

public class CySaSignMany {

    /**
     * 根据 appid 获取 SaSignConfig,允许自定义
     */
    public static SaParamRetFunction<String, SaSignConfig> findSaSignConfigMethod = (appid) -> SaSignManager.getSignMany().get(appid);

    /**
     * 获取 SaSignTemplate,根据 appid
     * @param appid /
     * @return /
     */
    public static SaSignTemplate getSignTemplate(String appid) {

        // appid 为空,返回全局默认 SaSignTemplate
        if(SaFoxUtil.isEmpty(appid)){
            return SaSignManager.getSaSignTemplate();
        }

        // 获取 SaSignConfig
        SaSignConfig config = findSaSignConfigMethod.run(appid);
        if(config == null){
            throw new SaSignException("未找到签名配置,appid=" + appid).setCode(SaSignErrorCode.CODE_12211);
        }

        // 创建 SaSignTemplate 并返回
        return new CySaSignTemplate(config);
    }
}

  • CustomerSaSignTemplate

import cn.dev33.satoken.context.model.SaRequest;
import cn.dev33.satoken.sign.config.SaSignConfig;
import cn.dev33.satoken.sign.template.SaSignTemplate;
import cn.hutool.core.util.StrUtil;

import java.util.Map;
import java.util.TreeMap;


public class CySaSignTemplate extends SaSignTemplate {

    public CySaSignTemplate() {
        super();
    }

    public CySaSignTemplate(SaSignConfig signConfig) {
        super(signConfig);
    }

    @Override
    public void checkRequest(SaRequest request, String... paramNames) {
        if (paramNames.length == 0) {
            checkParamMap(takeRequestParam(request, new String[]{}));
        } else {
            checkParamMap(takeRequestParam(request, paramNames));
        }
    }

    @Override
    protected  Map<String, String> takeRequestParam(SaRequest request, String [] paramNames) {
        Map<String, String> paramMap = new TreeMap<>();

        // 此三个参数是必须获取的
        paramMap.put(timestamp, getParam(request, timestamp));
        paramMap.put(nonce, getParam(request, nonce));
        paramMap.put(sign, getParam(request, sign));

        // 获取指定的参数
        for (String paramName : paramNames) {
            paramMap.put(paramName, request.getParam(paramName));
        }
        // 返回
        return paramMap;
    }

    private String getParam(SaRequest request, String paramName){
        if (StrUtil.isBlank(paramName)){
            return null;
        }
        // 优先从请求头中获取
        String header = request.getHeader(paramName);
        if (StrUtil.isNotBlank(header)){
            return header;
        }
        // 从请求参数中获取
        return request.getParam(paramName);
    }

}


您可能感兴趣的与本文相关的镜像

Python3.8

Python3.8

Conda
Python

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值