最近公司有钉钉扫码登录第三方应用的需求,稍微研究了一下,发现新的钉钉官方文档教程有很多步骤涉及到后端知识比如内网穿透和java相关知识,如果是前端开发可能会有点懵逼,所以在此记录一下身为前端如何从容应对(这里也会使用nestjs搭建一个简单的后端程序)
扫码登录第三方应用的基本原理
其实不论是钉钉扫码登录或者微信扫码登录等乱七八糟的扫码登录,其原理基本相同,下面是钉钉开放平台的一张图非常清晰的描述了扫码登录的全过程
具体操作步骤
步骤一步骤二参考钉钉官方文档,这里不再赘述(不是钉钉所在团队管理员可以自己创建一个,不要钱)
步骤三:设置第三方网站的回调域名
这里需要配置一下第三方应用的域名,单击开发配置 > 安全设置, 填写重定向 URL(回调域名):
这里的设置通俗点讲就是把你的第三方应用的域名、路径配置到这里,因为需要保证嵌入二维码页面所在的域名与这里配置的保持一致,不然扫码时会出现错误。比如你本地开发时的登录页路径是
http://127.0.0.1:5173/login,你就把这个填上去,测试环境开发环境同理,都可以填上去,用分号分开即可。
步骤四:嵌入二维码到第三方网站
这里官方提供了两种方式:一种是使用官方提供的登录页面,样式什么的都已经写好;还有一种是生成二维码内嵌到第三方应用的登录页,两种方法都可以,我们这里举例适用性更强的内嵌二维码方式,使用钉钉提供的页面授权较为简单可以参考官方文档。
- 在页面中引入钉钉扫码登录JSSDK。
<script src="https://g.alicdn.com/dingding/h5-dingtalk-login/0.21.0/ddlogin.js"></script>
- 在需要引入扫码登录的地方,调用如下方法。
<!-- STEP1:在HTML中添加包裹容器元素 -->
<div id="self_defined_element" class="self-defined-classname"></div>
<style>
/* STEP2:指定这个包裹容器元素的CSS样式,尤其注意宽高的设置 */
.self-defined-classname {
width: 300px;
height: 300px;
}
</style>
<script>
// STEP3:在需要的时候,调用 window.DTFrameLogin 方法构造登录二维码,并处理登录成功或失败的回调。
window.DTFrameLogin(
{
id: 'self_defined_element',
width: 300,
height: 300,
},
{
redirect_uri: encodeURIComponent('http://127.0.0.1:5173/login'), // 刚才配置的重定向地址
client_id: 'dingxxxxxxxxxxxx', // client_id在创建的应用基本信息->凭证与基础信息中可以找到
scope: 'openid', // 固定值
response_type: 'code', // 固定值
state: 'xxxxxxxxx', // 这里的state可以不传,如果传了会在loginResult中拿到一样的值
prompt: 'consent', // 固定值
},
(loginResult) => {
const {redirectUrl, authCode, state} = loginResult;
// 这里可以直接进行重定向
// window.location.href = redirectUrl;
// 也可以在不跳转页面的情况下,使用code进行授权
// 根据authCode获取用户信息和token 这里可以让你们的后端提供auth接口,也可以参考下面我使用nestjs搭建的一个简单的服务端示例
axios.get('http://localhost:3000/auth', { params: { authCode } }).then((res) => {
console.log(res)
// router.push('/index')
})
},
(errorMsg) => {
// 这里一般需要展示登录失败的具体原因,可以使用toast等轻提示
console.error(`errorMsg of errorCbk: ${errorMsg}`);
},
);
</script>
步骤五:使用nestjs搭建后端服务
- 使用脚手架快速生成nestjs项目
$ npm i -g @nestjs/cli
$ nest new project-name
- 处理跨域 src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { NestExpressApplication } from '@nestjs/platform-express';
async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule);
app.enableCors();
await app.listen(3000);
}
bootstrap();
- 启动项目,实现/auth接口
- 启动项目
pnpm start:dev - 修改 src/app.controller.ts,定义/auth接口
import { Controller, Get, Query } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get('auth')
async auth(@Query('authCode') authCode: string) {
// 获取accessToken
const userAccessToken = await this.appService.userAccessToken(authCode);
const { accessToken } = userAccessToken.data;
// 根据accessToken获取用户信息
const res = await this.appService.userInfo(accessToken);
return res.data;
}
}
- 修改 src/app.service.ts
import { Injectable } from '@nestjs/common';
import axios from 'axios';
@Injectable()
export class AppService {
async userAccessToken(authCode: string) {
return await axios.post<{ accessToken: string }>(
'https://api.dingtalk.com/v1.0/oauth2/userAccessToken',
{
clientId: 'dingxxxxxxxx',
clientSecret:
'xxxxxxxx', // clientId和clientSecret都在应用基础信息->凭证与基础信息里可以找到
code: authCode,
refreshToken: authCode,
grantType: 'authorization_code',
},
);
}
async userInfo(accessToken: string) {
return await axios.get('https://api.dingtalk.com/v1.0/contact/users/me', {
headers: {
'x-acs-dingtalk-access-token': accessToken,
},
});
}
}
至此服务端开发完成,在客户端扫码回调中调用/auth接口即可拿到扫码者的个人信息
axios.get('http://localhost:3000/auth', { params: { authCode } }).then((res) => { console.log(res) })
补充
- 通过clientId、clientSecret、authCode获取accessToken的接口文档:open.dingtalk.com/document/or…
- 通过accessToken获取用户信息接口文档:open.dingtalk.com/document/or…