
美团CPS开放平台对接中Java服务的API权限控制设计技巧在对接美团CPS开放平台时第三方ISV独立软件开发商通过API调用获取订单、佣金、活动等数据。为防止越权访问、接口滥用和数据泄露必须实现细粒度的API权限控制体系。本文基于OAuth2.0、自定义注解、Spring AOP与Redis提供一套可扩展、高性能的权限校验方案。1. 自定义权限注解与元数据定义通过注解声明接口所需权限packagebaodanbao.com.cn.cps.auth;importjava.lang.annotation.ElementType;importjava.lang.annotation.Retention;importjava.lang.annotation.RetentionPolicy;importjava.lang.annotation.Target;Target(ElementType.METHOD)Retention(RetentionPolicy.RUNTIME)publicinterfaceRequirePermission{Stringvalue();// 权限码如 ORDER_READ, COMMISSION_WRITEbooleancheckMerchantScope()defaulttrue;// 是否校验商户归属}使用示例RestControllerpublicclassCommissionController{RequirePermission(COMMISSION_QUERY)GetMapping(/commission/list)publicListCommissionlistCommissions(RequestParamStringmerchantId){returncommissionService.getByMerchant(merchantId);}RequirePermission(valueORDER_SYNC,checkMerchantScopefalse)PostMapping(/order/push)publicResponseEntity?receiveOrder(RequestBodyOrderPushDTOdto){orderService.handlePush(dto);returnResponseEntity.ok().build();}}2. 基于AOP的权限拦截器解析注解并执行校验逻辑AspectComponentpublicclassPermissionAspect{AutowiredprivateAuthServiceauthService;Around(annotation(requirePermission))publicObjectcheckPermission(ProceedingJoinPointjoinPoint,RequirePermissionrequirePermission)throwsThrowable{StringaccessTokenextractToken();AuthContextcontextauthService.parseToken(accessToken);// 1. 校验权限码if(!authService.hasPermission(context.getAppId(),requirePermission.value())){thrownewAccessDeniedException(Insufficient API permission);}// 2. 校验商户范围防跨商户访问if(requirePermission.checkMerchantScope()){StringrequestMerchantIdextractMerchantId(joinPoint);if(!context.getAuthorizedMerchants().contains(requestMerchantId)){thrownewAccessDeniedException(Merchant scope mismatch);}}returnjoinPoint.proceed();}privateStringextractToken(){HttpServletRequestrequest((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest();StringauthHeaderrequest.getHeader(Authorization);if(authHeadernull||!authHeader.startsWith(Bearer )){thrownewIllegalArgumentException(Missing or invalid Authorization header);}returnauthHeader.substring(7);}privateStringextractMerchantId(ProceedingJoinPointjoinPoint){Object[]argsjoinPoint.getArgs();for(Objectarg:args){if(arginstanceofStringisMerchantId((String)arg)){return(String)arg;}// 支持从DTO中提取if(arginstanceofMerchantScoped){return((MerchantScoped)arg).getMerchantId();}}thrownewIllegalArgumentException(Merchant ID not found in request);}privatebooleanisMerchantId(Stringstr){returnstr!nullstr.matches(M\\d{8,12});}}3. 权限与商户授权数据缓存Redis避免每次请求查DBServicepublicclassAuthService{privatefinalRedisTemplateString,ObjectredisTemplate;privatestaticfinalStringAPP_PERMISSIONS_KEYcps:app:permissions:%s;privatestaticfinalStringAPP_MERCHANTS_KEYcps:app:merchants:%s;publicAuthContextparseToken(Stringtoken){// 假设token为JWT解析出appIdStringappIdJwtUtil.getSubject(token);SetStringpermissionsgetPermissions(appId);SetStringmerchantsgetAuthorizedMerchants(appId);returnnewAuthContext(appId,permissions,merchants);}privateSetStringgetPermissions(StringappId){StringkeyString.format(APP_PERMISSIONS_KEY,appId);SetStringperms(SetString)redisTemplate.opsForValue().get(key);if(permsnull){permsbaodanbao.com.cn.cps.mapper.AppPermissionMapper.selectPermissionsByAppId(appId);redisTemplate.opsForValue().set(key,perms,10,TimeUnit.MINUTES);}returnperms;}privateSetStringgetAuthorizedMerchants(StringappId){StringkeyString.format(APP_MERCHANTS_KEY,appId);SetStringmerchants(SetString)redisTemplate.opsForValue().get(key);if(merchantsnull){merchantsbaodanbao.com.cn.cps.mapper.AppMerchantMapper.selectMerchantsByAppId(appId);redisTemplate.opsForValue().set(key,merchants,10,TimeUnit.MINUTES);}returnmerchants;}publicbooleanhasPermission(StringappId,StringrequiredPerm){returngetPermissions(appId).contains(requiredPerm);}}4. 应用授权模型设计数据库表结构示例-- 应用信息CREATETABLEcps_app(app_idVARCHAR(32)PRIMARYKEY,app_secretVARCHAR(64)NOTNULL,statusTINYINTDEFAULT1);-- 应用-权限关联CREATETABLEcps_app_permission(app_idVARCHAR(32),permission_codeVARCHAR(50),PRIMARYKEY(app_id,permission_code));-- 应用-商户授权CREATETABLEcps_app_merchant(app_idVARCHAR(32),merchant_idVARCHAR(20),PRIMARYKEY(app_id,merchant_id));5. 接口调用频控联动权限校验后叠加限流Around(annotation(requirePermission))publicObjectcheckPermissionAndRateLimit(ProceedingJoinPointjoinPoint,RequirePermissionrequirePermission)throwsThrowable{// ... 权限校验同上 ...// 基于appId接口做限流StringrateLimitKeyrl:context.getAppId():joinPoint.getSignature().getName();if(!baodanbao.com.cn.cps.ratelimit.TokenBucketLimiter.tryAcquire(rateLimitKey,100,60)){thrownewTooManyRequestsException(API rate limit exceeded);}returnjoinPoint.proceed();}6. 权限变更实时生效通过发布/订阅清除缓存ServicepublicclassPermissionUpdateService{AutowiredprivateRedisTemplateString,ObjectredisTemplate;publicvoidupdateAppPermissions(StringappId,SetStringnewPerms){baodanbao.com.cn.cps.mapper.AppPermissionMapper.update(appId,newPerms);// 删除缓存redisTemplate.delete(String.format(cps:app:permissions:%s,appId));// 可选发送MQ通知其他节点}}本文著作权归 俱美开放平台 转载请注明出处