API Key 生成和鉴权机制:从随机凭证生成到请求拦截校验
文章目录API Key 生成和鉴权机制从随机凭证生成到请求拦截校验一、API Key 生成规则二、过期时间处理三、数据库表结构四、生成接口五、前端生成与首次展示六、列表页脱敏展示七、请求鉴权流程八、用户身份切换九、删除与撤销十、安全实现要点1. API Key 只展示一次2. 脱敏只发生在展示层3. 不在日志中打印完整 Key4. 支持过期和撤销5. 可以扩展权限边界十一、完整调用链路十二、总结API Key 生成和鉴权机制从随机凭证生成到请求拦截校验本文介绍一种数据库查表型 API Key 实现方式核心流程包括API Key 生成、过期时间计算、数据库存储、前端展示、脱敏展示、请求头携带、后端拦截器校验和用户身份切换。用户点击生成 API Key ↓ 前端调用生成接口 ↓ 后端获取当前登录用户 ID ↓ 生成 ak- 无连字符 UUID ↓ 计算过期时间 ↓ 保存完整 Key 到 api_key 表 ↓ 前端弹窗展示完整 API Key ↓ 关闭弹窗后列表接口只返回脱敏 Key ↓ 外部系统通过 Authorization 请求头携带完整 Key ↓ 后端拦截器提取 Bearer Token ↓ 判断是否为 ak- 开头 ↓ 查询 api_key 表 ↓ 校验存在性、删除状态、过期状态 ↓ 切换到 API Key 所属用户身份 ↓ 写入请求上下文 ↓ 放行业务接口一、API Key 生成规则本方案中的 API Key 不是 JWT也不是加密 Token而是一个随机字符串格式如下ak- 32 位无连字符 UUID示例ak-550e8400e29b41d4a716446655440000后端生成逻辑可以简化为publicApiKeygenerateApiKey(LonguserId,Stringremark,IntegerexpireDays){ApiKeyentitynewApiKey();entity.setUserId(userId);entity.setRemark(remark);entity.setApiKey(ak-generateRandomUuidWithoutDash());if(expireDays!nullexpireDays0){entity.setExpiredAt(now().plusDays(expireDays));}save(entity);returnentity;}其中核心代码是entity.setApiKey(ak-generateRandomUuidWithoutDash());ak-用于标识这是 API Key 类型凭证便于和普通登录 Token、JWT 或其他认证凭证区分。二、过期时间处理生成 API Key 时可以传入有效天数expireDaysif(expireDays!nullexpireDays0){entity.setExpiredAt(now().plusDays(expireDays));}规则如下expireDaysexpired_at含义7当前时间 7 天7 天后过期30当前时间 30 天30 天后过期0null永不过期nullnull永不过期如果expired_at为空则表示 API Key 永不过期如果不为空则后续鉴权时需要判断当前时间是否已经超过该时间。三、数据库表结构API Key 最终保存到数据库中一个简化表结构如下CREATETABLEapi_key(idBIGINTPRIMARYKEYAUTO_INCREMENT,user_idBIGINTNOTNULL,api_keyVARCHAR(128)NOTNULL,remarkVARCHAR(255)DEFAULTNULL,expired_atDATETIMEDEFAULTNULL,create_timeDATETIMEDEFAULTCURRENT_TIMESTAMP,update_timeDATETIMEDEFAULTCURRENT_TIMESTAMPONUPDATECURRENT_TIMESTAMP,is_deletedTINYINTNOTNULLDEFAULT0,UNIQUEKEYuk_api_key(api_key),KEYidx_user_id(user_id));关键字段说明字段作用user_idAPI Key 所属用户api_key实际访问凭证remarkKey 的备注说明expired_at过期时间空表示永不过期is_deleted软删除标记uk_api_key保证 API Key 唯一这种设计的特点是Key 本身不携带用户信息、权限信息和过期信息所有状态都通过数据库查询获得。四、生成接口后端提供生成 API Key 的接口简化逻辑如下publicApiResponseApiKeygenerate(ApiKeyGenerateRequestrequest){LongcurrentUserIdgetCurrentLoginUserId();ApiKeyapiKeyapiKeyService.generateApiKey(currentUserId,request.getRemark(),request.getExpireDays());returnApiResponse.success(apiKey);}关键点生成 API Key 的用户必须是当前登录用户前端传入remark和expireDays后端根据当前用户 ID 生成 API Key 并落库生成后将完整 API Key 返回给前端展示。五、前端生成与首次展示前端请求方法可以封装为exportasyncfunctiongenerateApiKey(params:{remark?:string;expireDays?:number;}){returnrequest.post(/api-key/generate,params);}页面调用逻辑可以简化为asyncfunctionhandleGenerate(){if(!form.remark){showError(请输入备注);return;}constdataawaitgenerateApiKey(form);showSuccessModal({title:生成成功,content:请妥善保管您的 API Key关闭后将无法再次查看全文\n\n${data.apiKey},});refreshList();}这里有一个关键设计完整 API Key 只在生成成功时展示一次。生成接口/api-key/generate返回的是ApiKeyEntity其中包含完整的apiKey。前端拿到完整值后通过Modal.success弹窗展示给用户并提示用户妥善保存。用户关闭弹窗后再回到 API Key 列表页时就只能看到脱敏后的 Key。六、列表页脱敏展示API Key 的脱敏展示只发生在返回列表时的 VO 转换阶段也就是展示层处理不影响数据库中的真实存储。数据库中的api_key字段仍然保存完整 API Key后续鉴权时也仍然使用完整 Key 进行查询。后端列表接口/api-key/list返回的是ApiKeyVO在实体转换为 VO 时通过ApiKeyConvertor.maskApiKey()对apiKey字段进行脱敏处理。脱敏逻辑如下Named(maskApiKey)defaultStringmaskApiKey(StringapiKey){if(apiKeynull||apiKey.length()12){returnapiKey;}returnapiKey.substring(0,8)***apiKey.substring(apiKey.length()-4);}脱敏规则是前 8 位 *** 后 4 位例如ak-7ebfc***b045也就是说列表接口拿到的apiKey本身就是脱敏后的值而不是完整 API Key。对应链路可以理解为api_key 表保存完整 Key ↓ /api-key/list 查询数据 ↓ ApiKeyEntity 转 ApiKeyVO ↓ MapStruct 调用 maskApiKey() ↓ 返回脱敏后的 apiKey ↓ 前端列表展示这样可以保证列表页即使被打开也不会直接暴露完整 API Key。前端列表页使用 Ant Design Vue 的Table展示数据并通过自定义单元格#bodyCell处理apiKey列的渲染。在apiKey列中可以使用code标签配合 Tailwind 类展示脱敏后的 Key使其更接近凭证样式code class... {{ record.apiKey }} /code如果前端拿到的是完整 Key也可以通过同样的规则进行二次脱敏拼接apiKey.substring(0,8)***apiKey.substring(apiKey.length-4)不过在当前实现中后端列表接口已经返回脱敏值因此前端主要负责样式渲染。列表页中的时间字段也会在前端格式化展示dayjs(value).format(YYYY-MM-DD HH:mm:ss)其中createTime展示为标准时间格式expiredAt如果为空则显示为“永不过期”expiredAt如果不为空则格式化为YYYY-MM-DD HH:mm:ss。这一设计把“存储”和“展示”分离开数据库保存完整 API Key 生成弹窗展示一次完整 API Key 列表接口返回脱敏 API Key 列表页面展示脱敏值和时间信息 鉴权逻辑仍使用完整 API Key 查询数据库七、请求鉴权流程客户端调用接口时需要在请求头中携带 API KeyAuthorization: Bearer ak-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx后端通过拦截器统一处理 API Key 鉴权逻辑publicbooleanpreHandle(HttpRequestrequest,HttpResponseresponse,Objecthandler){StringauthHeaderrequest.getHeader(Authorization);if(isBlank(authHeader)){returntrue;}StringtokenauthHeader.replace(Bearer ,).trim();if(!token.startsWith(ak-)){returntrue;}ApiKeyapiKeyapiKeyService.getByApiKey(token);if(apiKeynull||apiKey.isDeleted()){thrownewUnauthorizedException(API Key 无效);}if(apiKey.getExpiredAt()!nullapiKey.getExpiredAt().isBefore(now())){thrownewUnauthorizedException(API Key 已过期);}switchToUser(apiKey.getUserId());requestContext.set(API_KEY_AUTH,true);requestContext.set(API_KEY_ID,apiKey.getId());returntrue;}鉴权流程如下读取 Authorization 请求头 ↓ 提取 Bearer 后面的 Token ↓ 判断是否以 ak- 开头 ↓ 不是 API Key则交给后续常规鉴权流程 ↓ 是 API Key则查询数据库 ↓ 判断 Key 是否存在 ↓ 判断 Key 是否被删除 ↓ 判断 Key 是否过期 ↓ 校验通过后切换到所属用户身份 ↓ 写入当前请求上下文 ↓ 放行请求需要注意的是列表页展示的脱敏 Key 不能用于接口调用。外部系统调用接口时必须使用生成时弹窗中展示过的完整 API Key。八、用户身份切换API Key 校验通过后需要将当前请求临时绑定到 API Key 所属用户switchToUser(apiKey.getUserId());这样后续业务逻辑就可以继续复用原有的用户上下文例如查询当前用户资源保存当前用户数据执行用户维度的接口逻辑记录用户维度的操作日志。需要注意的是API Key 认证只是在当前请求上下文中临时切换用户身份不等于创建真实登录会话。九、删除与撤销前端删除 API Key 的请求可以封装为exportasyncfunctiondeleteApiKey(id:number|string){returnrequest.delete(/api-key/${id});}后端可以通过软删除实现撤销apiKey.setDeleted(true);updateById(apiKey);后续请求如果继续携带已删除的 API Key拦截器会在校验阶段拦截if(apiKeynull||apiKey.isDeleted()){thrownewUnauthorizedException(API Key 无效);}这样可以在不物理删除数据的情况下实现 API Key 的失效和审计保留。十、安全实现要点1. API Key 只展示一次生成成功后完整展示一次后续管理页面只展示脱敏内容例如ak-550e****00002. 脱敏只发生在展示层脱敏展示不修改数据库中的真实 API Key只在列表接口返回ApiKeyVO时处理。也就是说数据库存储完整 Key 列表返回脱敏 Key 接口鉴权完整 Key这样既能保证安全展示又不会影响后端通过完整 Key 查库鉴权。3. 不在日志中打印完整 Key避免如下日志log.warn(API Key {} 无效,apiKey);建议改为脱敏输出log.warn(API Key {} 无效,maskApiKey(apiKey));4. 支持过期和撤销通过expired_at控制过期时间通过is_deleted控制撤销状态。5. 可以扩展权限边界如果后续安全要求更高可以继续扩展绑定接口权限区分只读和读写限制来源 IP增加调用频率限制记录最近使用时间记录 API Key 调用日志。十一、完整调用链路用户点击生成 API Key ↓ 前端调用生成接口 ↓ 后端获取当前登录用户 ID ↓ 生成 ak- 无连字符 UUID ↓ 计算过期时间 ↓ 保存完整 Key 到 api_key 表 ↓ 前端弹窗展示完整 API Key ↓ 关闭弹窗后列表接口只返回脱敏 Key ↓ 外部系统通过 Authorization 请求头携带完整 Key ↓ 后端拦截器提取 Bearer Token ↓ 判断是否为 ak- 开头 ↓ 查询 api_key 表 ↓ 校验存在性、删除状态、过期状态 ↓ 切换到 API Key 所属用户身份 ↓ 写入请求上下文 ↓ 放行业务接口十二、总结这是一种典型的数据库查表型 API Key 机制。它的核心实现是生成阶段ak- 随机字符串保存完整 Key 到数据库 展示阶段生成时展示一次完整 Key列表页只返回脱敏 Key 鉴权阶段请求头携带完整 Key后端查库校验该方案的优点是实现简单、状态可控、撤销方便并且通过“只展示一次全文 列表脱敏展示”的方式降低了 Key 泄露风险。它适合用于第三方系统调用、自动化脚本、AI Agent、内部工具和轻量级服务集成等场景。