SpingBoot-Vue 前后端分离—实现钉钉免登功能(2025)
一、需求分析
要实现钉钉免登功能,需要按照钉钉开放平台所提供步骤,进一步可以细分成以下操作:
A [用户访问应用] --> B [获取临时授权码(code)]
B --> C [应用服务器请求access_token]
C --> D [钉钉服务器返回access_token]
D --> E [应用服务器请求用户信息]
E --> F [钉钉服务器返回用户信息]
F --> G [应用服务器处理用户信息]
G --> H [用户登录成功]
总结过后,具体免登流程如下:
- 调用本接口获取免登授权码。
- 调用获取应用的 Access Token接口,获取应用访问凭证。
- 调用通过免登码获取用户信息接口,获取用户userid。
- 调用查询用户详情接口,获取用户信息。
二、具体实现
①、前端实现
以某信息确认系统首页为例,若是钉钉环境下,直接实现免登操作,若不是钉钉环境,则实现账号和密码的方式进行登录:
<template>
<div>
<div class="login">
<p>XXX信息确认系统</p>
<div class="loginContent">
<div class="fromuseruser">
<input type="text" v-model="user_name" placeholder="请输入账号" />
</div>
<div class="frompassword">
<input
type="password"
v-model="user_password"
placeholder="请输入密码"
/>
</div>
</div>
<div class="submit">
<van-button
type="primary"
:round="true"
loading-text="加载中..."
size="normal"
style="width:4.6rem"
text="登 录"
@click="submit"
></van-button>
</div>
</div>
</div>
</template>
<script>
import * as dd from 'dingtalk-jsapi'
import { Notify } from 'vant'
import {UserLogin} from "@/api/request.js";
export default {
data() {
return {
user_name:"",
user_password: "",
code: null,
};
},
mounted(){
// this.ddFun();
window.addEventListener('keydown',this.keyDown)
},
methods:{
ddFun(){
let _this = this
// corpId 在后台 -基本信息-开发信息-企业自用账户信息 下查看
let corpId = ''
dd.ready(()=> {
// dd.ready参数为回调函数,在环境准备就绪时触发,jsapi的调用需要保证在该回调函数触发后调用,否则无效。
dd.runtime.permission.requestAuthCode({
corpId:corpId,
onSuccess: result => {
_this.code = result.code;
let param = {
code: _this.code,
};
_this.login(param);
},
onFail: err => {
alert("dd error: " + JSON.stringify(err));
},
});
});
},
submit(){
if(this.user_name == '' || this.user_password == ''){
Notify({
type: "danger",
message: `账号或密码不能为空`,
});
return;
}
let u_pass = this.$encruption(this.user_password) //对密码进行签名加密
let param = {
'uN':this.user_name,
'pW':u_pass
// 'pW':this.user_password
};
// this.login(param)
UserLogin(param).then((res) => {
console.log('登录Res',res);
if (res.result == 1) {
sessionStorage.setItem('tel',JSON.stringify(res.data.tel))
sessionStorage.setItem('token',JSON.stringify(res.data.token))
if(res.data.auth == 2){
this.$router.push("/sensorjxpro/web/jxensure");
}else if(res.data.auth == 1){
this.$router.push("/sensorjxpro/web/staff/mobileweb");
}
} else {
Notify({
type: "danger",
message: `${res.data.msg}`+ ','+`${res.data.data}`,
});
}
})
},
// 回车登录
keyDown(e){
if(e.keyCode === 13){
this.submit()
}
}
},
deactivated() {
this.user_name = "";
this.user_password = "";
this.code = null;
},
destroyed(){
window.removeEventListener('keydown',this.keyDown,false)
}
}
</script>
<style lang="scss" scoped>
@import '@/style/login/login.scss'
</style>
关键钉钉免登的实现步骤主要是,通过 dd.runtime.permission.requestAuthCode 将code 传递给后端,后端进行解析,至此第一步,调用本接口获取免登授权码完成(前端也主要负责这一部分)
ddFun(){
let _this = this
// corpId 在后台 -基本信息-开发信息-企业自用账户信息 下查看
let corpId = ''
dd.ready(()=> {
// dd.ready参数为回调函数,在环境准备就绪时触发,jsapi的调用需要保证在该回调函数触发后调用,否则无效。
dd.runtime.permission.requestAuthCode({
corpId:corpId,
onSuccess: result => {
_this.code = result.code;
let param = {
code: _this.code,
};
_this.login(param);
},
onFail: err => {
alert("dd error: " + JSON.stringify(err));
},
});
});
},
②、后端实现
将前端传来的code,调用获取应用的 Access Token接口,获取应用访问凭证。调用通过免登码获取用户信息接口,获取用户userid。调用查询用户详情接口,获取用户信息。并最终在数据库中根据name查询,二次确认,并将得到的name信息返回给前端。至此完成钉钉免登的全部流程。
subgraph 钉钉服务器
D[返回access_token]
F[返回用户信息]
endsubgraph 应用服务器
C[请求access_token]
E[请求用户信息]
G[处理用户信息]
end
/**
* 用户登陆
* http://localhost:8080/user/login
* 0、判断是否包含code,包含进行钉钉免登陆方法;
*
* 1、依据姓名密码查找user;
* 2、使用jwtUtils生成token,token载体内容为 name、tel
* 3、将LoginVo 赋值作为响应实体类的响应数据(data)
*
* @return RespondDto
*/
@Override
public RespondDto userLogin(InParam inParam) {
User user;
if(inParam.getCode() == null){
log.info("userLogin方法成功调用 userName:{}", inParam.getUN());
inParam.setPW(rsaUtils.priKeyDecode(inParam.getPW()));
user = loginMapper.selectUser(inParam.getUN(), inParam.getPW());
log.info("userLogin查找方法调用完成,user:{}", user);
}else{
user = ddLogin(inParam);
}
if (user != null) {
LoginVo loginVo = new LoginVo();
loginVo.setName(user.getUserName());
loginVo.setTel(user.getTel());
loginVo.setToken(jwtUtils.tokenMaker(user));
loginVo.setAuth(user.getAuth());
return new RespondDto(ResultCode.OK,"登录成功",loginVo);
} else {
return new RespondDto(ResultCode.USER_NONE, "登陆失败");
}
}
/**钉钉免登陆
* 1、获取 accessToken;
* 2、获取用户信息,截取到name;
* 3、用name查找 user;*/
private User ddLogin(InParam inParam) {
String code = inParam.getCode();
// 获取access_token
String accessToken = HttpHelper.getAccess_Token(appkey, appsecret);
//不要获取userid, 不可与以下代码同用;
DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/v2/user/getuserinfo");
OapiV2UserGetuserinfoRequest req = new OapiV2UserGetuserinfoRequest();
req.setCode(code);
OapiV2UserGetuserinfoResponse rsp = null;
try {
rsp = client.execute(req, accessToken);
} catch (ApiException e) {
e.printStackTrace();
}
System.out.println(rsp.getBody());
String body = rsp.getBody();
String name = body.substring(body.indexOf("name\":\""),body.indexOf("\",\"sys")).substring(7);
System.out.println(name);
User user = loginMapper.selectUserByName(name);
return user;
}