恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

SpringBoot统一对接钉钉小程序与H5微应用免密登录实战

  • 首页
  • 资讯中心
  • /
  • SpringBoot统一对接钉钉小程序与H5微应用免密登录实战

相关资讯

银河麒麟服务器LVM存储管理实战:从原理到运维全解析 2026/8/22 4:41:51
Axios 404根本不是错误,而是路径诊断信号 2026/8/22 4:41:51
华为交换机Hybrid接口原理与实战:实现灵活跨VLAN通信 2026/8/22 4:41:51

最新资讯

nginx-proxy-manager-zh 使用教程:10 分钟从零跑通 Nginx 反向代理与 SSL 自动续期
Python数据分析实战:从数学建模到Pandas核心操作与可视化
nctoolbox实战:MATLAB里用一套API读取NetCDF、GRIB等15+种数据格式
如何快速查询手机号码归属地:location-to-phone-number 完整指南
皮尔逊相关系数:从数学原理到建模实战的完整指南
ADIAS:AI自动化设计交互式智能体系统的原理与实践

今日推荐

markdown-it-vue 踩坑排障:从安装到渲染的 6 个高频问题快速讲清
多尺度智能体控制:从宏观密度场到微观决策的架构与实践
CUBE标准:统一AI智能体评测的度量衡与架构解析

本周热门

【文章复现】非线性值迭代自适应动态规划(ADP):离散时间非线性系统的策略迭代自适应动态规划算法研究附Matlab代码
【双层规划,节点出清价,绿证交易,CVaR方法】两级电力市场环境下计及风险的省间交易商最优购电模型附Matlab代码
隐式mpc+自适应mpc+时变mpc,线性时变模型预测控制附Simulink仿真

本月精选

如何用DamaiHelper实现演唱会门票的智能自动化抢购:完整技术解决方案指南
第4篇:59 倍性能差距的索引瓶颈定位——一次教科书级的全表扫描调优
终极歌词批量下载神器:5分钟解决离线音乐库歌词同步难题

SpringBoot统一对接钉钉小程序与H5微应用免密登录实战

发布时间:2026/8/22 4:41:51
SpringBoot统一对接钉钉小程序与H5微应用免密登录实战 1. 项目缘起与核心价值最近在做一个企业内部的管理工具前端既有钉钉小程序也有需要嵌入钉钉工作台的H5微应用。老板提了个很实在的需求员工在钉钉里打开应用能不能别让ta再输一遍账号密码最好一点就进体验要像用原生钉钉功能一样顺滑。这个需求背后其实就是我们常说的“免密登录”或“单点登录SSO”。乍一听这需求挺合理。现在大家手机里App一大堆每个都要记密码烦都烦死了。在企业场景下应用都跑在钉钉这个“超级App”里面如果每次切换还要登录不仅效率低下员工抱怨多从安全和管理角度也说不通——员工的身份在进入钉钉时就已经由企业认证过了内部的子应用理应继承这个信任。技术上说这就是典型的OAuth 2.0授权码模式的应用只不过平台换成了钉钉。但真动手做你会发现这里面的门道比想象的多。钉钉开放平台给小程序和H5微应用提供的免登流程虽然核心思想一致但在具体对接的API、参数、以及尤其是前端获取登录凭证的方式上有细微却关键的差异。很多团队第一次做照着某一种类型的文档做完换到另一种场景就发现走不通调不通又不知道问题出在哪非常折腾。我这次就把SpringBoot后端如何同时无缝对接钉钉小程序和H5微应用免密登录的完整方案包括每一步的代码、配置、还有我踩过的那些坑给大家彻底讲明白。无论你是要快速实现功能还是想深入理解钉钉生态的集成逻辑这篇内容都能给你一份可靠的“地图”。2. 钉钉免登原理深度剖析为什么可以“免密”在写一行代码之前我们必须把原理吃透。只有理解了钉钉设计的这套流程后面排查问题才能心里有数。钉钉的免密登录本质上是基于员工在钉钉客户端内已登录的企业账号为其访问第三方应用进行“背书”和“授权”。整个过程可以概括为三个核心步骤我把它比喻成“机场安检转机”获取临时登机牌前端获取code用户在你的小程序或H5里发起登录请求。此时钉钉客户端可以理解为机场安检系统会确认用户身份已登录企业钉钉然后给你的应用前端发放一个一次性的、短时效的“临时登机牌”这就是authCode。关键点小程序和H5获取这个code的API完全不同。换取正式机票后端用code换access_token和用户信息你的应用后端SpringBoot服务拿到前端送来的code带着你自己的应用身份信息AppKey和AppSecret去钉钉的“航空公司柜台”服务端API兑换。验证通过后钉钉会返回给你一个“正式机票”access_token代表你的应用有权限调用某些API和最重要的“乘客身份证”用户的唯一标识unionId和userId。建立自家会员体系后端绑定用户与生成自有会话拿到unionId/userId后你需要在自己的数据库里找到或创建对应的用户记录然后生成一个你自己系统内部的会话凭证比如JWT Token或Session ID返回给前端。从此前端就用这个自有凭证访问你的后端接口完成登录。这里最核心的安全基石在于code是由钉钉客户端生成的它确保了请求确实来自钉钉内部且用户已登录。AppSecret永远只存在于你的后端服务器用于向钉钉证明你是合法的应用所有者。整个过程用户的钉钉密码从未暴露给你的应用实现了“免密”。2.1 小程序与H5微应用的关键差异点这是最容易出错的地方务必分清特性钉钉小程序钉钉H5微应用嵌入工作台前端运行环境钉钉小程序容器钉钉内置浏览器WebView获取code的APIdd.getAuthCodedd.readydd.runtime.permissiondd.getAuthCode核心依赖小程序JSAPI钉钉JSAPI需引入特定版本脚本权限申请在小程序管理后台配置在H5微应用管理后台配置且需申请微应用个人身份信息授权等权限code用途换取小程序专用的用户身份换取微应用专用的用户身份后端接口服务端API不同但最终都能拿到用户唯一ID重要提示即使同一个钉钉应用从小程序端和从H5微应用端获取的code在钉钉服务端看来也是两种不同的授权类型必须调用对应的服务端API来换取用户信息。用错了接口一定会返回无效code的错误。3. 环境准备与钉钉应用配置3.1 创建钉钉应用首先你需要登录 钉钉开放平台 。根据你的需求创建对应类型的应用企业内部开发-小程序用于创建钉钉小程序。企业内部开发-H5微应用用于创建可添加到工作台的网页应用。如果你需要同时支持两种形态通常的做法是创建两个独立的应用一个类型为小程序一个类型为H5微应用。虽然管理上稍微麻烦一点但权限清晰不易混淆。当然钉钉也在演进有时一些配置可以复用但为了保险起见初期建议分开创建。创建成功后记下以下核心信息它们相当于你应用的“身份证”AgentId: 应用标识在某些老接口中会用到。AppKey: 应用的唯一Key。AppSecret: 最重要的密钥必须保密仅用于服务端。泄露它意味着你的应用权限完全暴露。3.2 配置安全域名与权限这一步是很多请求失败特别是H5的根源。H5微应用进入应用详情 -开发管理。服务器出口IP填写你的SpringBoot服务对外暴露的IP地址。钉钉服务端会校验请求来源IP是否在此白名单内。应用首页地址填写你的H5应用首页URL例如https://your-domain.com/index.html。PC端首页地址如果支持PC钉钉也需要填写。权限管理找到并申请“微应用个人身份信息授权”这个权限。没有它前端dd.getAuthCode会失败。小程序进入应用详情 -开发-开发设置。服务器域名在request合法域名中添加你的SpringBoot后端API域名如https://api.your-domain.com。小程序前端发起的网络请求域名必须在此列表中。业务域名可选如果你在小程序里通过WebView嵌入了自己的H5页面需要在这里配置该H5页面的域名。3.3 SpringBoot项目初始化创建一个标准的SpringBoot项目这里我选择常用的依赖组合!-- pom.xml 关键依赖 -- dependencies !-- SpringBoot Web -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- 用于简化HTTP调用 -- dependency groupIdorg.apache.httpcomponents/groupId artifactIdhttpclient/artifactId scopetest/scope /dependency !-- 推荐使用OkHttp或RestTemplate这里以OkHttp为例 -- dependency groupIdcom.squareup.okhttp3/groupId artifactIdokhttp/artifactId version4.10.0/version /dependency !-- JSON处理 -- dependency groupIdcom.fasterxml.jackson.core/groupId artifactIdjackson-databind/artifactId /dependency !-- 配置管理 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-configuration-processor/artifactId optionaltrue/optional /dependency !-- 工具类 -- dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependencies将钉钉应用的AppKey和AppSecret配置到application.yml中。强烈建议为小程序和H5微应用使用不同的配置前缀避免混淆。# application.yml dingtalk: # 小程序配置 mini-app: app-key: your_mini_app_key app-secret: your_mini_app_secret agent-id: your_agent_id # H5微应用配置 h5-app: app-key: your_h5_app_key app-secret: your_h5_app_secret agent-id: your_h5_agent_id使用ConfigurationProperties来优雅地加载配置Component ConfigurationProperties(prefix dingtalk.mini-app) Data public class DingTalkMiniAppConfig { private String appKey; private String appSecret; private String agentId; } Component ConfigurationProperties(prefix dingtalk.h5-app) Data public class DingTalkH5AppConfig { private String appKey; private String appSecret; private String agentId; }4. 后端核心服务实现一套代码两种适配后端需要提供两个核心接口1. 获取用户信息2. 生成自身系统令牌。为了优雅地处理小程序和H5的不同我设计了一个DingTalkService接口并为其提供两种实现。4.1 定义服务接口与通用DTO首先定义通用的请求和响应数据结构。Data public class DingTalkLoginRequest { /** 前端传来的临时授权码 */ private String authCode; /** 用于区分是小程序还是H5请求例如 mini 或 h5 */ private String clientType; } Data public class DingTalkUserInfo { /** 用户在钉钉企业的唯一标识 */ private String userId; /** 用户在钉钉开放平台的唯一标识跨应用唯一 */ private String unionId; /** 用户姓名 */ private String userName; /** 用户头像 */ private String avatar; } Data public class AppLoginResult { /** 本系统生成的Token如JWT */ private String token; /** 用户在本系统的ID */ private String systemUserId; private String userName; }定义服务接口public interface DingTalkService { /** * 根据authCode获取钉钉用户信息 * param authCode 临时授权码 * return 钉钉用户信息 */ DingTalkUserInfo getUserInfo(String authCode) throws Exception; /** * 获取访问令牌某些场景下可能需要 */ String getAccessToken() throws Exception; }4.2 实现通用钉钉API调用工具无论是小程序还是H5调用钉钉服务端API获取access_token和用户信息的逻辑是相似的只是URL和参数略有不同。我们抽象一个工具类。Component Slf4j public class DingTalkApiClient { Autowired private RestTemplate restTemplate; // 需自行配置 private static final String GET_TOKEN_URL https://oapi.dingtalk.com/gettoken; private static final String GET_USER_INFO_URL https://oapi.dingtalk.com/topapi/v2/user/getuserinfo; private static final String GET_USER_DETAIL_URL https://oapi.dingtalk.com/topapi/v2/user/get; /** * 获取微应用/H5应用的access_token */ public String getH5AppAccessToken(String appKey, String appSecret) throws Exception { MapString, String params new HashMap(); params.put(appkey, appKey); params.put(appsecret, appSecret); String response restTemplate.getForObject(GET_TOKEN_URL ?appkey{appkey}appsecret{appsecret}, String.class, params); JsonNode root objectMapper.readTree(response); if (root.get(errcode).asInt() 0) { return root.get(access_token).asText(); } else { log.error(获取H5应用AccessToken失败: {}, response); throw new RuntimeException(获取钉钉AccessToken失败: root.get(errmsg).asText()); } } /** * 获取小程序access_token (接口相同但逻辑上区分) */ public String getMiniAppAccessToken(String appKey, String appSecret) throws Exception { // 实际上钉钉小程序和H5微应用获取access_token是同一个接口 // 但为了逻辑清晰我们可以单独定义一个方法内部调用相同的逻辑 return getH5AppAccessToken(appKey, appSecret); } /** * 使用authCode获取用户信息 (H5微应用) */ public DingTalkUserInfo getUserInfoByH5Code(String authCode, String accessToken) throws Exception { MapString, Object requestBody new HashMap(); requestBody.put(code, authCode); HttpHeaders headers new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); HttpEntityMapString, Object request new HttpEntity(requestBody, headers); String url GET_USER_INFO_URL ?access_token accessToken; String response restTemplate.postForObject(url, request, String.class); JsonNode root objectMapper.readTree(response); if (root.get(errcode).asInt() 0) { String userId root.path(result).get(userid).asText(); // 注意H5微应用接口返回的是userid需要再调用详情接口获取unionid等信息 return getUserDetailByUserId(userId, accessToken); } else { log.error(H5应用通过code获取userid失败: {}, response); throw new RuntimeException(获取用户信息失败: root.get(errmsg).asText()); } } /** * 使用authCode获取用户信息 (小程序) */ public DingTalkUserInfo getUserInfoByMiniCode(String authCode, String accessToken) throws Exception { // 小程序专用接口 String url https://oapi.dingtalk.com/sns/getuserinfo_bycode?access_token accessToken; MapString, String requestBody new HashMap(); requestBody.put(tmp_auth_code, authCode); // 注意参数名不同 HttpHeaders headers new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); HttpEntityMapString, String request new HttpEntity(requestBody, headers); String response restTemplate.postForObject(url, request, String.class); JsonNode root objectMapper.readTree(response); if (root.get(errcode).asInt() 0) { DingTalkUserInfo userInfo new DingTalkUserInfo(); JsonNode userInfoNode root.path(user_info); userInfo.setUnionId(userInfoNode.get(unionid).asText()); // 小程序接口可能不直接返回userid需要根据unionid去关联 userInfo.setUserId(); // 可能需要额外查询 userInfo.setUserName(userInfoNode.path(nick).asText()); return userInfo; } else { log.error(小程序通过code获取用户信息失败: {}, response); throw new RuntimeException(小程序获取用户信息失败: root.get(errmsg).asText()); } } /** * 根据userid获取用户详情用于获取unionid等 */ private DingTalkUserInfo getUserDetailByUserId(String userId, String accessToken) throws Exception { MapString, Object requestBody new HashMap(); requestBody.put(userid, userId); requestBody.put(language, zh_CN); HttpHeaders headers new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); HttpEntityMapString, Object request new HttpEntity(requestBody, headers); String url GET_USER_DETAIL_URL ?access_token accessToken; String response restTemplate.postForObject(url, request, String.class); JsonNode root objectMapper.readTree(response); if (root.get(errcode).asInt() 0) { JsonNode result root.path(result); DingTalkUserInfo userInfo new DingTalkUserInfo(); userInfo.setUserId(userId); userInfo.setUnionId(result.get(unionid).asText()); userInfo.setUserName(result.get(name).asText()); userInfo.setAvatar(result.path(avatar).asText()); return userInfo; } else { throw new RuntimeException(获取用户详情失败: root.get(errmsg).asText()); } } }4.3 实现两种客户端的Service现在我们可以基于上面的工具类实现针对小程序和H5的Service。Service(dingTalkH5Service) Slf4j public class DingTalkH5ServiceImpl implements DingTalkService { Autowired private DingTalkH5AppConfig h5AppConfig; Autowired private DingTalkApiClient dingTalkApiClient; Override public DingTalkUserInfo getUserInfo(String authCode) throws Exception { log.info(H5应用开始处理免登authCode: {}, authCode); // 1. 获取微应用access_token String accessToken getAccessToken(); // 2. 用code换取用户信息 return dingTalkApiClient.getUserInfoByH5Code(authCode, accessToken); } Override public String getAccessToken() throws Exception { // 简单实现每次调用都获取。生产环境应加入缓存避免频繁调用。 return dingTalkApiClient.getH5AppAccessToken(h5AppConfig.getAppKey(), h5AppConfig.getAppSecret()); } }Service(dingTalkMiniService) Slf4j public class DingTalkMiniServiceImpl implements DingTalkService { Autowired private DingTalkMiniAppConfig miniAppConfig; Autowired private DingTalkApiClient dingTalkApiClient; Override public DingTalkUserInfo getUserInfo(String authCode) throws Exception { log.info(小程序开始处理免登authCode: {}, authCode); // 1. 获取小程序access_token String accessToken getAccessToken(); // 2. 用code换取用户信息 (调用小程序专用接口) return dingTalkApiClient.getUserInfoByMiniCode(authCode, accessToken); } Override public String getAccessToken() throws Exception { return dingTalkApiClient.getMiniAppAccessToken(miniAppConfig.getAppKey(), miniAppConfig.getAppSecret()); } }4.4 提供统一登录入口Controller最后我们提供一个统一的登录接口根据clientType参数来路由到不同的Service。RestController RequestMapping(/api/auth) Slf4j public class AuthController { Autowired Qualifier(dingTalkH5Service) private DingTalkService dingTalkH5Service; Autowired Qualifier(dingTalkMiniService) private DingTalkService dingTalkMiniService; Autowired private UserService userService; // 你自己的用户业务服务 PostMapping(/dingtalk/login) public ResultAppLoginResult dingTalkLogin(RequestBody DingTalkLoginRequest request) { try { DingTalkService dingTalkService; // 根据客户端类型选择服务 if (h5.equalsIgnoreCase(request.getClientType())) { dingTalkService dingTalkH5Service; } else if (mini.equalsIgnoreCase(request.getClientType())) { dingTalkService dingTalkMiniService; } else { return Result.fail(不支持的客户端类型); } // 1. 获取钉钉用户信息 DingTalkUserInfo dingTalkUserInfo dingTalkService.getUserInfo(request.getAuthCode()); // 2. 根据unionId/userId处理本地用户登录/注册 // 这里假设用unionId作为唯一关联标识 String unionId dingTalkUserInfo.getUnionId(); String userId dingTalkUserInfo.getUserId(); String systemUserId userService.findOrCreateUser(unionId, userId, dingTalkUserInfo.getUserName()); // 3. 生成本系统令牌例如JWT String token userService.generateToken(systemUserId); // 4. 返回结果 AppLoginResult result new AppLoginResult(); result.setToken(token); result.setSystemUserId(systemUserId); result.setUserName(dingTalkUserInfo.getUserName()); return Result.success(result); } catch (Exception e) { log.error(钉钉免登失败, e); return Result.fail(登录失败: e.getMessage()); } } }5. 前端实现小程序与H5的差异处理后端准备好了前端如何获取那个关键的authCode呢这是差异最大的部分。5.1 钉钉小程序前端实现小程序运行在钉钉的小程序容器中可以使用dd对象提供的JSAPI。// 在小程序的页面JS中 Page({ onLoad() { this.dingTalkLogin(); }, dingTalkLogin() { const that this; // 1. 获取免登授权码 dd.getAuthCode({ success: (res) { const authCode res.authCode; // 这就是我们需要的code console.log(获取到authCode:, authCode); // 2. 调用后端登录接口 dd.httpRequest({ url: https://your-api.com/api/auth/dingtalk/login, method: POST, headers: { Content-Type: application/json }, data: { authCode: authCode, clientType: mini // 明确告诉后端是小程序 }, success: (resp) { const result resp.data; if (result.success) { const loginResult result.data; // 3. 登录成功保存token到本地存储 dd.setStorageSync(token, loginResult.token); dd.setStorageSync(userInfo, { userId: loginResult.systemUserId, name: loginResult.userName }); // 4. 跳转到首页或进行其他操作 dd.reLaunch({ url: /pages/index/index }); } else { dd.showToast({ title: 登录失败 result.message }); } }, fail: (err) { console.error(请求后端失败, err); dd.showToast({ title: 网络请求失败 }); } }); }, fail: (err) { console.error(获取authCode失败, err); dd.showToast({ title: 获取登录授权失败 }); } }); } });5.2 钉钉H5微应用前端实现H5微应用运行在钉钉的WebView中需要先引入钉钉的JS-SDK并且流程上多了一个“鉴权”步骤。!DOCTYPE html html head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 titleH5微应用/title !-- 1. 引入钉钉JSAPI -- script srchttps://g.alicdn.com/dingding/dingtalk-jsapi/2.21.3/dingtalk.open.js/script /head body div idapp加载中.../div script document.addEventListener(DOMContentLoaded, function() { // 2. 钉钉环境初始化 dd.ready(function() { console.log(钉钉JSAPI准备就绪); // 3. 获取免登授权码 dd.runtime.permission.requestAuthCode({ corpId: _你的企业CorpId_, // 这里填写你企业的CorpId在钉钉管理后台可查 onSuccess: function(result) { const authCode result.code; console.log(获取到authCode:, authCode); // 4. 调用后端登录接口 fetch(https://your-api.com/api/auth/dingtalk/login, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ authCode: authCode, clientType: h5 // 明确告诉后端是H5 }) }) .then(response response.json()) .then(data { if (data.success) { const loginResult data.data; // 5. 登录成功保存token可用localStorage localStorage.setItem(token, loginResult.token); localStorage.setItem(userInfo, JSON.stringify({ userId: loginResult.systemUserId, name: loginResult.userName })); // 6. 更新UI或跳转 document.getElementById(app).innerHTML h1欢迎${loginResult.userName}/h1; // window.location.href /home.html; } else { alert(登录失败 data.message); } }) .catch(error { console.error(请求后端失败, error); alert(网络请求失败); }); }, onFail: function(err) { console.error(获取authCode失败, err); alert(钉钉授权失败请检查应用权限配置或网络。错误码 err.errorCode); } }); }); // dd.error用于处理JSAPI加载失败 dd.error(function(error) { console.error(钉钉JSAPI加载错误, error); alert(钉钉环境加载失败请确保在钉钉内打开。); }); }); /script /body /html关键提示H5微应用中的corpId是你的企业标识可以在钉钉管理后台 - 企业信息中找到。这个参数是必须的用于指定从哪个企业获取用户身份。6. 常见问题、避坑指南与实战心得做到这里基本功能应该能跑通了。但根据我的经验下面这些坑你大概率会遇到提前了解一下能省下大量调试时间。6.1 错误码大全与排查思路错误场景可能原因排查步骤前端获取code失败(H5常见)1. 未引入或未正确加载钉钉JSAPI。2. 未在dd.ready回调内调用API。3. H5微应用未申请“微应用个人身份信息授权”权限。4. 网页未在钉钉客户端内打开。1. 检查浏览器控制台有无JS加载错误。2. 确保所有钉钉API调用都在dd.ready内。3. 登录开放平台检查H5应用“权限管理”。4. 使用dd.env判断是否在钉钉环境。dd.getAuthCode返回errorCode: 11用户点击了“拒绝授权”。引导用户重新操作并解释需要授权才能使用。可以尝试用dd.runtime.permission.requestAuthCode的onFail回调处理。后端换用户信息失败返回code无效1.code已过期有效期约5分钟。2. 用错了接口最常见。用H5的code调了小程序的接口或反之。3.AppKey和AppSecret配置错误。4. 服务器出口IP未配置。1. 确保前端获取code后立即调用后端。2.仔细核对H5用/topapi/v2/user/getuserinfo小程序用/sns/getuserinfo_bycode。3. 检查application.yml配置确认对应环境的Key/Secret。4. 在开放平台配置服务器出口IP。后端换用户信息失败返回无效的AccessToken1.AccessToken已过期有效期2小时。2. 获取AccessToken的AppKey/Secret错误。1. 实现AccessToken的缓存机制避免频繁获取。2. 同上检查配置。H5应用在PC钉钉无法登录PC端首页地址未配置或PC端钉钉版本过低。在H5应用“开发管理”中填写“PC端首页地址”。并提示用户升级PC钉钉。小程序真机调试报错服务器域名未配置或配置错误。在小程序管理后台“开发设置”中将后端API域名加入request合法域名列表。6.2 性能与安全优化建议AccessToken缓存钉钉的AccessToken有调用频率限制默认2000次/小时和有效期2小时。务必在服务端缓存它。可以用Redis设置过期时间为7000秒比2小时稍短。Component public class DingTalkTokenManager { Autowired private RedisTemplateString, String redisTemplate; Autowired private DingTalkApiClient apiClient; Autowired private DingTalkH5AppConfig h5Config; private static final String H5_TOKEN_KEY dingtalk:token:h5; public String getH5AccessToken() throws Exception { String token redisTemplate.opsForValue().get(H5_TOKEN_KEY); if (StringUtils.hasText(token)) { return token; } // 重新获取并缓存 token apiClient.getH5AppAccessToken(h5Config.getAppKey(), h5Config.getAppSecret()); redisTemplate.opsForValue().set(H5_TOKEN_KEY, token, Duration.ofSeconds(7000)); return token; } }用户信息本地缓存通过unionId获取到的用户基本信息如姓名、部门不会频繁变动。可以在本地数据库关联存储避免每次登录都调用钉钉获取用户详情接口减少网络延迟和API调用次数。authCode一次性使用钉钉的authCode只能使用一次且有效期短。后端在成功换取用户信息后可以记录该code已使用防止重放攻击。AppSecret严格保密这是最高权限的密钥。绝不能放在前端代码、客户端配置或提交到Git仓库。生产环境建议使用配置中心或环境变量注入。网络超时与重试调用钉钉开放平台API时配置合理的超时时间如连接超时3秒读取超时5秒并考虑对可重试的错误如网络抖动加入简单的重试机制。6.3 关于UnionId与UserId的抉择unionId用户在钉钉开放平台下的唯一标识跨多个同一开发者的不同应用不变。如果你有多个钉钉应用如一个H5、一个小程序用unionId可以唯一识别同一个用户是实现“同一用户跨应用打通”的关键。userId用户在某个特定企业内的员工ID。同一个用户在不同企业内userId不同。userId更适合纯粹的企业内部应用。我的建议在你自己系统的用户表中同时存储unionId和userId以及对应的corpId。以unionId作为主要关联键userId作为辅助信息。这样既能满足跨应用统一身份也能获取到用户在企业内的具体信息。7. 扩展思考更复杂的场景如何处理基础的免登跑通后你可能会遇到更复杂的需求非钉钉环境下的登录你的应用可能既要在钉钉内使用也要提供独立的网页版或App。这就需要设计一个兼容的登录体系。通常做法是在用户表增加登录方式字段如dingtalk_unionid,username_password。钉钉登录走上述流程独立登录则走账号密码/手机验证码流程最终都映射到同一个系统用户ID。获取更多用户信息如部门、职位在用户首次登录或定期同步时可以使用/topapi/v2/user/get接口需access_token和userid拉取更完整的员工信息同步到本地数据库用于组织架构展示、权限控制等。扫码登录PC端Web应用这是一个非常常见的场景。流程是PC网页展示二维码内容是一个带唯一场景值的钉钉协议URL用户用手机钉钉扫码并确认PC网页通过轮询或WebSocket从后端获取登录状态。其核心依然是后端通过手机端带回来的临时授权码code换取用户信息并建立PC端会话。这需要用到钉钉的扫码登录能力涉及scanCodeAPI和sceneId的管理逻辑比免登稍复杂但原理相通。与现有SSO系统集成如果公司已有统一的单点登录系统如CAS, OAuth2 Server可以将钉钉免登作为其中一个身份提供商IdP。流程变为应用发起登录 - 跳转到公司SSO - SSO提供钉钉登录选项 - 用户通过钉钉授权 - SSO回调应用并传递令牌。这样应用只需对接公司SSO无需直接处理钉钉逻辑更利于统一管理。整个集成过程最需要耐心的是前期配置和调试阶段。一旦打通这套流程会非常稳定。记住一个核心原则前端拿code后端用codeAppSecret换用户信息。只要牢牢抓住这个主线区分好小程序和H5的差异点任何问题都能一步步定位解决。

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号