钉钉开放平台地址:https://open.dingtalk.com
我们的后端采用SpringBoot的方式
三方登录框架JustAuth
<dependency>
<groupId>com.xkcoding.justauthgroupId>
<artifactId>justauth-spring-boot-starterartifactId>
<version>1.4.0version>
dependency>
justauth:
enabled: true
cache:
type: default
type:
DINGTALK:
corp-id: {你自己的}
client-id: {你自己的}
client-secret: {你自己的}
redirect-uri: {你自己的重定向地址,和钉钉开放平台设置的一致}
# 获取企业的token接口
gettoken-url: https://oapi.dingtalk.com/gettoken
# 获取用户信息的接口
get-user-info-url: https://api.dingtalk.com/v1.0/oauth2/ssoUserInfo
# 根据unionid查询用户id
get-userid-by-unionid: https://oapi.dingtalk.com/user/getUseridByUnionid
# 根据用户id查询用户信息的接口
get-userinfo-by-userid: https://oapi.dingtalk.com/user/get
@Autowired
private AuthRequestFactory factory;
@ApiOperation("获取钉钉登录二维码")
@GetMapping("/getDingtalkQrcode")
public Result<Map> getDingtalkQrcode() {
AuthRequest authRequest = factory.get("DINGTALK");
// 唯一标识
String state = AuthStateUtils.createState();
String url = authRequest.authorize(state);
return Result.success(Map.of("url", url, "uuid", state));
}
@ApiOperation("钉钉登录")
@PostMapping("/dingtalkLogin")
public Result<String> dingtalkLogin(AuthCallback callback) {
// 获取钉钉用户信息
DingtalkUserDO dingtalkUser = getDingtalkUser(callback);
// 获取钉钉用户的邮箱 也可以拿到手机号
String mEmail = dingtalkUser.getEmail();
// 这里处理业务逻辑啥的,然后生成token
return Result.success("token" );
}
private DingtalkUserDO getDingtalkUser(AuthCallback callback) {
// 获取用户信息 v1
AuthRequest authRequest = factory.get(DingtalkRequestUtil.TYPE);
AuthResponse authResponse = authRequest.login(callback);
dingtalkRequestUtil.checkRequestIsSuccess(authResponse, "钉钉登录异常");
AuthUser authUser = (AuthUser) authResponse.getData();
String unionId = authUser.getToken().getUnionId();
String token = dingtalkRequestUtil.getToken();
String userId = dingtalkRequestUtil.getUserId(token, unionId);
return dingtalkRequestUtil.getUserInfo(token, userId);
}
/**
登录请求工具类
*/
@Component
@Slf4j
public class DingtalkRequestUtil {
// 类型为 钉钉
public static final String TYPE = "DINGTALK";
// 钉钉token
public static final String TOKEN = "TOKEN";
// 成功状态
public static final Integer SUCCESS_STATUS_1 = 2000;
public static final Integer SUCCESS_STATUS_2 = 0;
private final RestTemplate restTemplate;
private final MongCacheUtil mongCacheUtil;
@Value("${justauth.type.DINGTALK.client-id}")
private String appkey;
@Value("${justauth.type.DINGTALK.client-secret}")
private String appsecret;
@Value("${justauth.type.DINGTALK.gettoken-url}")
private String gettokenUrl;
@Value("${justauth.type.DINGTALK.get-user-info-url}")
private String getUserInfoUrl;
@Value("${justauth.type.DINGTALK.get-userid-by-unionid}")
private String getUseridByUnionid;
@Value("${justauth.type.DINGTALK.get-userinfo-by-userid}")
private String getUserinfoByUserid;
@Autowired
public DingtalkRequestUtil(RestTemplate restTemplate, MongCacheUtil mongCacheUtil) {
this.restTemplate = restTemplate;
this.mongCacheUtil = mongCacheUtil;
}
/**
* token 的过期时间,默认为两个小时
*
* @return
*/
public String getToken() {
// 从缓存中查询当前的token
String cacheKey = TYPE + "_" + TOKEN;
Result<String> cache = mongCacheUtil.get(cacheKey);
if (Objects.equals(cache.getCode(), ResultCode.SUCCESS.getCode())) {
return cache.getData();
} else {
String url = gettokenUrl + "?appkey=" + appkey + "&appsecret=" + appsecret;
Map forObject = restTemplate.getForObject(url, Map.class);
checkRequestIsSuccess(forObject, "获取钉钉token异常", forObject);
String accessToken = forObject.get("access_token").toString();
// 设置缓存
mongCacheUtil.put(cacheKey, accessToken, Integer.parseInt(forObject.get("expires_in").toString()), 900);
return accessToken;
}
}
/**
* 检查请求是否成功
*
* @param resp
*/
public void checkRequestIsSuccess(Object resp, String errMessage, Object... errObjects) {
boolean error = false;
if (resp instanceof AuthResponse) {
AuthResponse authResponse = (AuthResponse) resp;
if (!Objects.equals(authResponse.getCode(), SUCCESS_STATUS_1)) {
error = true;
}
} else if (resp instanceof Map) {
Map<String, String> resMap = (Map<String, String>) resp;
if (!resMap.containsKey("errcode") || !Objects.equals(String.valueOf(resMap.get("errcode")), SUCCESS_STATUS_2.toString())) {
error = true;
errMessage = resMap.get("errmsg");
}
}
if (error) {
log.error(JSON.toJSONString(errObjects));
throw new DingtalkRequestException(errMessage);
}
}
public String getUserId(String token, String unionId) {
String url = getUseridByUnionid + "?access_token=" + token + "&unionid=" + unionId;
Map respMap = restTemplate.getForObject(url, Map.class);
System.out.println(respMap);
checkRequestIsSuccess(respMap, "获取用户id异常", respMap);
// 校验是否成功
return respMap.get("userid").toString();
}
public DingtalkUserDO getUserInfo(String token, String userId) {
String url = getUserinfoByUserid + "?access_token=" + token + "&userid=" + userId;
Map respMap = restTemplate.getForObject(url, Map.class);
checkRequestIsSuccess(respMap, "获取用户信息异常");
// 转换为自己的实体 DingtalkUserDO
return com.alibaba.fastjson.JSON.parseObject(JSON.toJSONString(respMap), DingtalkUserDO.class);
}
/**
* 钉钉异常内部类
*/
class DingtalkRequestException extends RuntimeException {
public DingtalkRequestException(String message) {
super(message);
}
}
}
/**
* 钉钉用户
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class DingtalkUserDO {
private Boolean active;
private String avatar;
private List<Long> department;
private String email;
private Long errcode;
private String errmsg;
private Boolean exclusiveAccount;
private Boolean isAdmin;
private Boolean isBoss;
private Boolean isHide;
private String isLeaderInDepts;
private Boolean isSenior;
private String jobnumber;
private String managerUserid;
private String name;
private String openId;
private String orderInDepts;
private String position;
private Boolean realAuthed;
private String remark;
private List<DingtalkRoleDO> roles;
private String tel;
private String unionid;
private String userid;
private String workPlace;
}
/**
* 钉钉角色
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class DingtalkRoleDO {
private String groupName;
private Long id;
private String name;
private Long type;
}
后端部分完成
前端我们使用的VUE
# 引入方式,ddLogin 名字自己定义
<script src="<%= BASE_URL %>js/ddLogin.js"></script>
!function (window, document) {
function d(a) {
var e, c = document.createElement("iframe"),
d = "https://login.dingtalk.com/login/qrcode.htm?goto=" + a.goto ;
d += a.style ? "&style=" + encodeURIComponent(a.style) : "",
d += a.href ? "&href=" + a.href : "",
c.src = d,
c.frameBorder = "0",
c.allowTransparency = "true",
c.scrolling = "no",
c.width = a.width ? a.width + 'px' : "365px",
c.height = a.height ? a.height + 'px' : "400px",
e = document.getElementById(a.id),
e.innerHTML = "",
e.appendChild(c)
}
window.DDLogin = d
}(window, document);
定义一个div
<div id="login_container">div>
编写初始化二维码的js代码
ddLoginInit() {
// 获取钉钉登录验证码
getDingtalkOauthLoginQrcode()
.then(res => {
this.ddLoginUrl = res.data.url
this.state = res.data.uuid
// 钉钉自己的url 修改上面俩,不需要动这个
let goto = encodeURIComponent(this.ddLoginUrl)
let obj = DDLogin({
id: 'login_container',//这里需要你在自己的页面定义一个HTML标签并设置id,例如或
goto: goto, //请参考注释里的方式
style: 'border:none;background-color:#FFFFFF;',
width: '100%',//官方参数 365
height: '300'//官方参数 400
})
let that = this
var handleMessage = function(event) {
var origin = event.origin
if (origin == 'https://login.dingtalk.com') { //判断是否来自ddLogin扫码事件。
var loginTmpCode = event.data
//获取到loginTmpCode后就可以在这里构造跳转链接进行跳转了
let url = 'https://oapi.dingtalk.com/connect/oauth2/sns_authorize?appid=' + that.appid + '&response_type=code&scope=snsapi_login&state=' + that.state + '&redirect_uri=' + that.redirect_uri + '&loginTmpCode=' + loginTmpCode
window.location.href = url
/* window.location.href = url
if (this.timer !== undefined) {
clearInterval(that.timer)
}
// 启动定时任务
that.timer = setInterval(function() {
console.log('定时任务')
}, 1000)*/
}
}
if (typeof window.addEventListener != 'undefined') {
window.addEventListener('message', handleMessage, false)
} else if (typeof window.attachEvent != 'undefined') {
window.attachEvent('onmessage', handleMessage)
}
})
.catch(err => {
this.$message.error('跳转钉钉登录异常')
})
},
vue created事件的定义代码
created() {
// 获取路径是否携带参数
let code = this.$route.query.code
let state = this.$route.query.state
if (code && state) {
let codeForm = {
code: code,
state:state
}
// 调用钉钉登录 ,这个接口就是我们自己定义的登录接口,需要传入两个参数
this.dingtalkLogin(codeForm)
} else {
// 这里执行的是其他方式的登录逻辑
}
}
前端代码构建也完成