原创|对接三方服务商回调鉴权的程序代码设计

通过系统应用服务总会与三方服务商进行对接,既然有对接,就会有回调。但是此应用服务由于部署在公网访问,为了考虑系统安全系以及防止报文被篡改,这就意味着我们需要跟三方服务商进行鉴权技术方案设计。此文章,就是一个具体典型的案例,由于此应用服务有两个不同的场景,但是鉴权设计上又有不同差异之处,所以在总体程序设计上巧妙的满足场景的需求前提下,又能尽可能做到更好的扩展维护。

一、背景

此次涉及到对接三方的两个不同场景,暂且定位场景1和场景2。场景1的鉴权方案就是通过http接口回调,在请求头+请求报文上做鉴权处理,具体鉴权机制:请求头中的签名=md5(base64(报文)+回调url+私钥+时间戳)。
而场景2的鉴权就是在请求报文中增加鉴权字段,该鉴权字段=md5(秘钥+字段1+字段2+字段3+…)。
总而言之,都是通过md5加密,只不过加密的数据步骤有些区别。
为了考虑减少代码的耦合度,同时尽可能提高后续的扩展性,在程序设计上引入了设计模式。

二、详细设计

1、UML设计

在这里插入图片描述
从上图可以看出,依然采用定义一个上下文对象BaseAuthenticateContext<Request>,该类定义一个泛型,意味着需要子类来继承,并指定请求参数类。通过AbstractAuthenticateHandler它来封装鉴权的共性逻辑,比如鉴权流程,以及相关复用的代码。相关子类来继承它,实现相关抽象方法即可。AuthenticateDispatcher这个类来对外暴露,外部调用无需晓得具体使用哪个Handler来处理,还需要委托给它即可。

2、程序设计

2.1、AuthenticateActionEnum

定义一个枚举,来维护所有的鉴权场景类型,这里把场景抽象成Action

/**
 * 鉴权活动枚举类型
 *
 * @author : Sieg Heil
 * @since 2022/11/25 10:30 AM
 */
@Getter
@ToString
public enum AuthenticateActionEnum {
    /**
     * 企业直播活动变更
     */
    ACTIVITY_STATUS_CHANGE("企业直播活动变更"),
    /**
     * 视频点播事件通知
     */
    VOD_EVENT_NOTIFY("视频点播事件通知");

    /**
     * 构造函数
     *
     * @param desc 描述
     */
    AuthenticateActionEnum(String desc) {
        this.desc = desc;
    }

    /**
     * 描述
     */
    private final String desc;
}

2.2、AuthenticateDispatcher

通过@Autowired这个注解,把AbstractAuthenticateHandler的子类集合自动装配,作为该类的一个成员。同时,提供一个分发的方法。

/**
 * 鉴权处理分发器
 *
 * @author : Sieg Heil
 * @since 2022/11/25 10:28 AM
 */
@Component
public class AuthenticateDispatcher {

    @Autowired
    private List<AbstractAuthenticateHandler> handlerList;

    /**
     * 执行处理
     *
     * @param context 上下文对象
     */
    public void execute(BaseAuthenticateContext context) {
        handlerList.stream()
            .filter(handler -> handler.getAction() == context.getAction())
            .forEach(handler -> handler.execute(context));
    }
}

2.3、BaseAuthenticateContext<Request>

定义一个上下文类。该类,包含一个内部静态类Response,并作为它的成员属性,来封装鉴权执行结果。

/**
 * 回调鉴权上下文对象
 *
 * @author : Sieg Heil
 * @since 2022/11/25 10:08 AM
 */
@ToString
@Getter
@Setter
public abstract class BaseAuthenticateContext<Request> {
    /**
     * 活动类型
     */
    private AuthenticateActionEnum action;
    /**
     * 请求参数
     */
    private Request request;
    /**
     * 响应结果
     */
    private Response response;

    @ToString
    @Getter
    @Setter
    public static class Response {
        /**
         * 静态变量
         */
        public static String SUCCESS = "鉴权成功";
        /**
         * 鉴权是否成功
         */
        private boolean success;
        /**
         * 鉴权结果
         */
        private String result;

        /**
         * 静态方法
         *
         * @param result 鉴权结果
         * @return 响应对象
         */
        public static Response buildSuccess(String result) {
            Response response = new Response();
            response.setResult(result);
            response.setSuccess(Boolean.TRUE);
            return response;
        }

        /**
         * 静态方法
         *
         * @param result 鉴权结果
         * @return 响应对象
         */
        public static Response buildFailure(String result) {
            Response response = new Response();
            response.setResult(result);
            response.setSuccess(Boolean.FALSE);
            return response;
        }
    }
}

2.3.1、ActivityStatusChangeAuthenticateContext

具体的一个场景子类

/**
 * [企业直播活动变更]回调鉴权上下文对象
 *
 * @author : Sieg Heil
 * @since 2022/11/25 10:08 AM
 */
@ToString(callSuper = true)
@Getter
@Setter
public class ActivityStatusChangeAuthenticateContext extends BaseAuthenticateContext<SubscribeLiveActivityStatusChangeRequest> {
}

2.3.2、VodEventNotifyAuthenticateContext

具体的一个场景子类

/**
 * [视频点播事件通知]回调鉴权上下文对象
 *
 * @author : Sieg Heil
 * @since 2022/11/25 10:08 AM
 */
@ToString(callSuper = true)
@Getter
@Setter
public class VodEventNotifyAuthenticateContext extends BaseAuthenticateContext<VolcVodRequestContext> {
}

2.4、AbstractAuthenticateHandler

鉴权处理类的基类,外部暴露的公共方法为public void execute(Context context)。该方法内部封装了具体鉴权的相关步骤,相关子类只需要实现相关抽象方法即可。

三个重要抽象方法:

  • abstract String getTraceId(Context context) :用于获取请求的traceId,便于日志打印,后续方便追踪问题。
  • abstract void doExecute(Context context):用于做具体的鉴权执行逻辑
  • abstract AuthenticateConfig getConfig():获取处理类场景的鉴权配置,该配置可以通过yml配置文件或者apollo实现,管理维护相关鉴权配置参数。
/**
 * 抽象鉴权处理器
 *
 * @author : Sieg Heil
 * @since 2022/11/25 10:14 AM
 */
@Slf4j
public abstract class AbstractAuthenticateHandler<Context extends BaseAuthenticateContext> implements LoggerService {

    @Autowired
    protected VolcAuthenticateApolloConfig volcAuthenticateApolloConfig;

    @PostConstruct
    void init() {
        getLog().info("AuthenticateApolloConfig={}", JsonUtils.toJson(volcAuthenticateApolloConfig));
    }

    /**
     * 鉴权活动类型
     */
    protected AuthenticateActionEnum action;
    /**
     * 活动名称
     */
    protected String actionName;

    /**
     * 构造函数
     *
     * @param action 活动类型
     */
    public AbstractAuthenticateHandler(AuthenticateActionEnum action) {
        this.action = action;
        if (Objects.nonNull(action)) {
            this.actionName = action.name();
        }
    }

    /**
     * 对外部方法
     *
     * @param context
     */
    public void execute(Context context) {
        String traceId = getTraceId(context);
        if (logDebug()) {
            getLog().info("[{}|{}],context={}", traceId, actionName, JsonUtils.toJson(context));
        }
        AuthenticateConfig config = getConfig();
        if (null == config) {
            context.setResponse(BaseAuthenticateContext.Response.buildSuccess(SUCCESS));
            return;
        }
        boolean enableSwitch = Optional.ofNullable(config.getEnableSwitch()).orElse(Boolean.FALSE);
        //如果没有开启鉴权,则不执行鉴权
        if (!enableSwitch) {
            context.setResponse(BaseAuthenticateContext.Response.buildSuccess(SUCCESS));
            return;
        }
        doExecute(context);
        BaseAuthenticateContext.Response response = context.getResponse();
        getLog().info("[{}|{}]{}", traceId, actionName, JsonUtils.toJson(response));
        if (!response.isSuccess()) {
            throw new ForbiddenException("鉴权失败[" + response.getResult() + "]", response.getResult());
        }
    }

    @Override
    public boolean logDebug() {
        Boolean enableLogDebug = volcAuthenticateApolloConfig.getEnableLogDebug();
        Boolean enable = Optional.ofNullable(enableLogDebug).orElse(Boolean.TRUE);
        return enable.booleanValue();
    }

    /**
     * 获取一个traceId,用于问题排查使用
     *
     * @param context 上下文对象
     * @return traceId
     */
    protected abstract String getTraceId(Context context);

    /**
     * 执行鉴权
     * 需要子类实现此方法,完成具体的健全处理
     *
     * @param context 上下文对象
     */
    protected abstract void doExecute(Context context);

    /**
     * 获取鉴权配置
     *
     * @return 鉴权配置
     */
    protected abstract AuthenticateConfig getConfig();

    public AuthenticateActionEnum getAction() {
        return action;
    }
}

2.4.1、ActivityStatusChangeAuthenticateHandler

鉴权场景1的具体鉴权逻辑。

/**
 * [企业直播活动变更]回调鉴权处理器
 *
 * @author : Sieg Heil
 * @since 2022/11/25 10:20 AM
 */
@Component
@Slf4j
public class ActivityStatusChangeAuthenticateHandler extends AbstractAuthenticateHandler<ActivityStatusChangeAuthenticateContext> {

    /**
     * 构造函数
     */
    public ActivityStatusChangeAuthenticateHandler() {
        super(AuthenticateActionEnum.ACTIVITY_STATUS_CHANGE);
    }


    @Override
    public Logger getLog() {
        return log;
    }

    @Override
    protected String getTraceId(ActivityStatusChangeAuthenticateContext context) {
        return context.getRequest().getActivityID();
    }

    @Override
    protected AuthenticateConfig getConfig() {
        return volcAuthenticateApolloConfig.getActivityStatusChange();
    }

    @Override
    protected void doExecute(ActivityStatusChangeAuthenticateContext context) {
        SubscribeLiveActivityStatusChangeRequest request = context.getRequest();
        String sign = request.getSign();
        String signature = getSignature(context);
        if (Objects.equals(sign, signature)) {
            context.setResponse(BaseAuthenticateContext.Response.buildSuccess(SUCCESS));
        } else {
            String traceId = getTraceId(context);
            if (logDebug()) {
                getLog().info("[{}|{}],ts={},encrypted={}", traceId, actionName, request.getTimestamp(), signature);
            }
            String debug = MessageFormat.format("activityId={0},signature={1},md5={2}", traceId, sign, signature);
            context.setResponse(BaseAuthenticateContext.Response.buildFailure(debug));
        }
    }

    /**
     * 获取报文加密后的密文
     *
     * @param context 上下文对象
     * @return 密文
     */
    private String getSignature(ActivityStatusChangeAuthenticateContext context) {
        SubscribeLiveActivityStatusChangeRequest request = context.getRequest();
        String privateKey = volcAuthenticateApolloConfig.getActivityStatusChange().getPrivateKey();
        StringBuilder content = new StringBuilder(privateKey);
        content.append(request.getActivityID()).append(request.getEventType())
            .append(request.getStatus()).append(request.getTimestamp());
        String original = content.toString();
        String encrypted = Md5Util.encrypt(original);
        return encrypted;
    }
}
2.4.2、VodEventNotifyAuthenticateHandler

鉴权场景2的具体鉴权逻辑。

/**
 * [视频点播事件通知]回调鉴权处理器
 *
 * @author : Sieg Heil
 * @since 2022/11/25 10:20 AM
 */
@Component
@Slf4j
public class VodEventNotifyAuthenticateHandler extends AbstractAuthenticateHandler<VodEventNotifyAuthenticateContext> {

    /**
     * 构造函数
     */
    public VodEventNotifyAuthenticateHandler() {
        super(AuthenticateActionEnum.VOD_EVENT_NOTIFY);
    }

    @Override
    public Logger getLog() {
        return log;
    }

    @Override
    protected String getTraceId(VodEventNotifyAuthenticateContext context) {
        return context.getRequest().getRequest().getRequestId();
    }

    @Override
    protected AuthenticateConfig getConfig() {
        return volcAuthenticateApolloConfig.getVodEventNotify();
    }

    @Override
    protected void doExecute(VodEventNotifyAuthenticateContext context) {
        VolcVodRequestContext requestContext = context.getRequest();
        String sign = requestContext.getSignature();
        String original = getMd5Content(context);
        String signature = Md5Util.encrypt(original);
        if (Objects.equals(sign, signature)) {
            context.setResponse(BaseAuthenticateContext.Response.buildSuccess(SUCCESS));
        } else {
            String traceId = getTraceId(context);
            if (logDebug()) {
                getLog().info("[{}|{}],encrypted={}", traceId, actionName, signature);
            }
            String debug = MessageFormat.format("requestId={0},signature={1},md5={2}", traceId, sign, signature);
            context.setResponse(BaseAuthenticateContext.Response.buildFailure(debug));
        }
    }

    private String getMd5Content(VodEventNotifyAuthenticateContext context){
        VolcVodRequestContext requestContext = context.getRequest();
        String requestBody = requestContext.getRequestBody();
        String privateKey = volcAuthenticateApolloConfig.getVodEventNotify().getPrivateKey();
        String callbackUrl = volcAuthenticateApolloConfig.getVodEventNotify().getCallbackUrl();
        String callbackContent = encode(requestBody);
        StringBuilder original = new StringBuilder(callbackUrl).append("|")
            .append(requestContext.getTimestamp()).append("|")
            .append(privateKey).append("|")
            .append(callbackContent);
        return original.toString();
    }

    private String encode(String value) {
        Base64.Encoder encoder = Base64.getEncoder();
        return encoder.encodeToString(value.getBytes(StandardCharsets.UTF_8));
    }
}

2.5、AuthenticateConfig

鉴权配置类

/**
 * 鉴权配置类
 *
 * @author : Sieg Heil
 * @since 2022/11/25 11:47 AM
 */
@ToString(callSuper = true)
@Getter
@Setter
public class AuthenticateConfig {
    /**
     * 鉴权开关
     */
    private Boolean enableSwitch;
    /**
     * 鉴权私钥
     */
    private String privateKey;
    /**
     * 回调url
     */
    private String callbackUrl;
    /**
     * 鉴权策略
     */
    private StrategyEnum strategy;

    /**
     * 鉴权策略类型
     */
    public enum StrategyEnum {
        /**
         * 对报文进行MD5加密,防止报文被篡改
         */
        MD5
    }
}

2.6、VolcAuthenticateApolloConfig

所有回调场景鉴权配置类

/**
 * 回调鉴权配置
 *
 * @author : Sieg Heil
 * @since 2022/11/25 11:55 AM
 */
@Component
@RefreshScope
@ConfigurationProperties(prefix = "xxx.xxx.authenticate.volc")
@ToString
@Getter
@Setter
public class VolcAuthenticateApolloConfig {

    /**
     * 是否启用日志输出,便于追踪问题
     */
    private Boolean enableLogDebug;

    /**
     * 企业直播活动变更
     */
    private AuthenticateConfig activityStatusChange;

    /**
     * 视频点播事件通知
     */
    private AuthenticateConfig vodEventNotify;
}

2.7、yml配置

yml配置文件,可以通过diamond或者apollo,当前应用服务对接了apollo。

xxx:
  xxx:
    # 回调配置 true|false
    callback:
      # 订阅企业直播活动状态变更
      subscribeVolcActivityStatusChange:
        # 启用日志输出
        logDebug: true
        # 启用日志输出
        enableHandle: false
      # 订阅视频点播事件通知
      subscribeVolcVodEventNotify:
        # 启用日志输出
        logDebug: true
        # 启用日志输出
        enableHandle: false   
    # 鉴权配置
    authenticate:
      # 鉴权配置
      volc:
        # 是否启用日志输出,便于追踪问题 true|false
        enableLogDebug: true
        # 企业直播活动变更
        activityStatusChange:
          # 鉴权开关
          enableSwitch: true
          # 鉴权私钥
          privateKey: xxxxx
          # 回调url
          callbackUrl: xxxx
          # 鉴权策略
          strategy: MD5
        # 视频点播事件通知
        vodEventNotify:
          # 鉴权开关
          enableSwitch: true
          # 鉴权私钥
          privateKey: xxxx
          # 回调url
          callbackUrl: xxxx         
          # 鉴权策略
          strategy: MD5          
        

2.8、业务接入

/**
 * Created at 2022/5/24 11:01 AM
 *
 * @author : Sieg Heil
 */
@ThriftService(service = "volcEngineCallback")
@Validated
@Slf4j
public class VolcEngineCallbackServiceImpl implements VolcEngineCallbackService{

    @Autowired
    private VolcEngineCallbackConverter volcEngineCallbackConverter;

    @Autowired
    private CallbackEnableSwitch callbackEnableSwitch;

    @Autowired
    private SubscribeLiveStatusEventDispatcher subscribeLiveStatusEventDispatcher;

    @Autowired
    private SubscribeVodEventDispatcher subscribeVodEventDispatcher;

    @Autowired
    private AuthenticateDispatcher authenticateDispatcher;

    @Override
    public void subscribeLiveActivityStatusChange(SubscribeLiveActivityStatusChangeRequest request) {
        ActivityStatusChangeAuthenticateContext authenticateContext = volcEngineCallbackConverter.convertToActivityStatusChangeAuthenticateContext(request);
        authenticateDispatcher.execute(authenticateContext);
        if (!callbackEnableSwitch.subscribeVolcActivityStatusChange()) {
            String traceId = request.getActivityID();
            log.info("[SubscribeLiveActivityStatusChange|{}]业务处理开关关闭|{}", traceId, callbackEnableSwitch.subscribeVolcActivityStatusChange());
            return;
        }
        SubscribeLiveStatusEventContext context = volcEngineCallbackConverter.convertToSubscribeLiveStatusEventContext(request);
        subscribeLiveStatusEventDispatcher.execute(context);
    }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值