原创|对接三方服务商回调鉴权的程序代码设计
创始人
2024-03-14 13:27:02
0

文章目录

  • 一、背景
  • 二、详细设计
    • 1、UML设计
    • 2、程序设计
      • 2.1、AuthenticateActionEnum
      • 2.2、AuthenticateDispatcher
      • 2.3、BaseAuthenticateContext``
        • 2.3.1、ActivityStatusChangeAuthenticateContext
        • 2.3.2、VodEventNotifyAuthenticateContext
      • 2.4、AbstractAuthenticateHandler
        • 2.4.1、ActivityStatusChangeAuthenticateHandler
        • 2.4.2、VodEventNotifyAuthenticateHandler
      • 2.5、AuthenticateConfig
      • 2.6、VolcAuthenticateApolloConfig
      • 2.7、yml配置
      • 2.8、业务接入

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

一、背景

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

二、详细设计

1、UML设计

在这里插入图片描述
从上图可以看出,依然采用定义一个上下文对象BaseAuthenticateContext,该类定义一个泛型,意味着需要子类来继承,并指定请求参数类。通过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 {@Autowiredprivate List handlerList;/*** 执行处理** @param context 上下文对象*/public void execute(BaseAuthenticateContext context) {handlerList.stream().filter(handler -> handler.getAction() == context.getAction()).forEach(handler -> handler.execute(context));}
}

2.3、BaseAuthenticateContext

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

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

2.3.2、VodEventNotifyAuthenticateContext

具体的一个场景子类

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

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 implements LoggerService {@Autowiredprotected VolcAuthenticateApolloConfig volcAuthenticateApolloConfig;@PostConstructvoid 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());}}@Overridepublic 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 {/*** 构造函数*/public ActivityStatusChangeAuthenticateHandler() {super(AuthenticateActionEnum.ACTIVITY_STATUS_CHANGE);}@Overridepublic Logger getLog() {return log;}@Overrideprotected String getTraceId(ActivityStatusChangeAuthenticateContext context) {return context.getRequest().getActivityID();}@Overrideprotected AuthenticateConfig getConfig() {return volcAuthenticateApolloConfig.getActivityStatusChange();}@Overrideprotected 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 {/*** 构造函数*/public VodEventNotifyAuthenticateHandler() {super(AuthenticateActionEnum.VOD_EVENT_NOTIFY);}@Overridepublic Logger getLog() {return log;}@Overrideprotected String getTraceId(VodEventNotifyAuthenticateContext context) {return context.getRequest().getRequest().getRequestId();}@Overrideprotected AuthenticateConfig getConfig() {return volcAuthenticateApolloConfig.getVodEventNotify();}@Overrideprotected 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|falsecallback:# 订阅企业直播活动状态变更subscribeVolcActivityStatusChange:# 启用日志输出logDebug: true# 启用日志输出enableHandle: false# 订阅视频点播事件通知subscribeVolcVodEventNotify:# 启用日志输出logDebug: true# 启用日志输出enableHandle: false   # 鉴权配置authenticate:# 鉴权配置volc:# 是否启用日志输出,便于追踪问题 true|falseenableLogDebug: true# 企业直播活动变更activityStatusChange:# 鉴权开关enableSwitch: true# 鉴权私钥privateKey: xxxxx# 回调urlcallbackUrl: xxxx# 鉴权策略strategy: MD5# 视频点播事件通知vodEventNotify:# 鉴权开关enableSwitch: true# 鉴权私钥privateKey: xxxx# 回调urlcallbackUrl: 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{@Autowiredprivate VolcEngineCallbackConverter volcEngineCallbackConverter;@Autowiredprivate CallbackEnableSwitch callbackEnableSwitch;@Autowiredprivate SubscribeLiveStatusEventDispatcher subscribeLiveStatusEventDispatcher;@Autowiredprivate SubscribeVodEventDispatcher subscribeVodEventDispatcher;@Autowiredprivate AuthenticateDispatcher authenticateDispatcher;@Overridepublic 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);}
}

相关内容

热门资讯

明阳电气:已制定《市值管理制度... 证券之星消息,明阳电气(301291)12月26日在投资者关系平台上答复投资者关心的问题。 投资者提...
长白法院秉持高效便民理念,以柔... 长白县人民法院诉讼服务中心紧跟时代步伐,扎实做好先行调解工作,牢牢筑起维护社会和谐稳定的前沿防线。该...
安徽含山:政策上门零距离 惠农... 人民网记者 李希蒙 “我都快八十了,从没生过病,每年花400块买医保钱图个啥?”在“联通惠民 合作兴...
鑫铂股份:正在研究市值管理相关... 有投资者在互动平台向鑫铂股份提问:“贵司上市募资4.81亿元,两次增发募资16.6亿元,合计募集资金...
成都一酒店股东因经济纠纷割断天... 封面新闻记者 叶海燕 12月25日,成都一酒店员工田先生向封面新闻反映,因酒店股东间存在经济纠纷,外...
科技部:开展创新积分制政策实施... 据科技部官网12月26日消息,科技部办公厅发布关于开展创新积分制“揭榜挂帅”的通知,借鉴科技计划“揭...
日照中院通报2025年度道交纠... 齐鲁晚报·齐鲁壹点 徐艳 12月25日,日照中院召开全市法院2025年度道交纠纷多元化解工作情况通报...
国家卫健委:“十四五”以来全国... 12月26日,国家卫生健康委召开新闻发布会,介绍2025年全系统为民服务八件实事有关情况。国家卫生健...
“一杯青茶Talk Show”... 近日,团濉溪县委在濉溪古城石板街“街西青年”举办“一杯青茶Talk Show”活动,聚焦婚姻家庭等日...