|
@@ -1,7 +1,5 @@
|
|
|
package com.ruoyi.web.controller.dingding;
|
|
|
|
|
|
-import com.alibaba.fastjson.JSON;
|
|
|
-import com.alibaba.fastjson.JSONObject;
|
|
|
import com.aliyun.dingtalkcontact_1_0.models.GetUserHeaders;
|
|
|
import com.aliyun.dingtalkcontact_1_0.models.GetUserResponse;
|
|
|
import com.aliyun.dingtalkcontact_1_0.Client;
|
|
@@ -13,36 +11,25 @@ import com.dingtalk.api.DefaultDingTalkClient;
|
|
|
import com.dingtalk.api.DingTalkClient;
|
|
|
import com.dingtalk.api.request.*;
|
|
|
import com.dingtalk.api.response.*;
|
|
|
-import com.dingtalk.oapi.lib.aes.DingTalkEncryptException;
|
|
|
-import com.dingtalk.oapi.lib.aes.DingTalkEncryptor;
|
|
|
-import com.dingtalk.open.app.api.GenericEventListener;
|
|
|
-import com.dingtalk.open.app.api.OpenDingTalkStreamClientBuilder;
|
|
|
-import com.dingtalk.open.app.api.message.GenericOpenDingTalkEvent;
|
|
|
-import com.dingtalk.open.app.api.security.AuthClientCredential;
|
|
|
-import com.dingtalk.open.app.stream.protocol.event.EventAckStatus;
|
|
|
-import com.fasterxml.jackson.databind.JsonNode;
|
|
|
import com.ruoyi.common.constant.Constants;
|
|
|
import com.ruoyi.common.core.domain.AjaxResult;
|
|
|
import com.ruoyi.common.core.domain.entity.SysRole;
|
|
|
import com.ruoyi.common.core.domain.entity.SysUser;
|
|
|
-import com.ruoyi.common.core.domain.model.LoginUser;
|
|
|
+import com.ruoyi.common.core.redis.RedisCache;
|
|
|
import com.ruoyi.common.utils.SecurityUtils;
|
|
|
import com.ruoyi.common.utils.StringUtils;
|
|
|
import com.ruoyi.common.utils.dingding.config.DingAppConfig;
|
|
|
import com.ruoyi.common.utils.dingding.config.DingUrlConstant;
|
|
|
-import com.ruoyi.framework.web.service.DingAuthenticationProvider;
|
|
|
-import com.ruoyi.framework.web.service.DingToken;
|
|
|
import com.ruoyi.framework.web.service.SysLoginService;
|
|
|
import com.ruoyi.system.domain.resume.CompanyInfo;
|
|
|
import com.ruoyi.system.domain.resume.CompanyUser;
|
|
|
import com.ruoyi.system.service.ISysRoleService;
|
|
|
import com.ruoyi.system.service.ISysUserService;
|
|
|
-import com.ruoyi.system.service.dingding.DingAuthService;
|
|
|
-import com.ruoyi.system.service.dingding.DingTokenService;
|
|
|
import com.ruoyi.system.service.resume.ICompanyInfoService;
|
|
|
import com.ruoyi.system.service.resume.ICompanyUserService;
|
|
|
+import com.ruoyi.web.controller.dingding.service.DingAuthServiceInfo;
|
|
|
+import com.ruoyi.web.controller.dingding.service.DingTokenService;
|
|
|
import com.taobao.api.ApiException;
|
|
|
-import com.taobao.api.TaobaoResponse;
|
|
|
import io.swagger.annotations.Api;
|
|
|
import io.swagger.annotations.ApiOperation;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
@@ -51,12 +38,8 @@ import org.springframework.stereotype.Controller;
|
|
|
import org.springframework.web.bind.annotation.*;
|
|
|
|
|
|
import javax.annotation.Resource;
|
|
|
-import javax.servlet.http.HttpServletRequest;
|
|
|
import java.util.*;
|
|
|
|
|
|
-import static com.ruoyi.framework.datasource.DynamicDataSourceContextHolder.log;
|
|
|
-import static com.ruoyi.web.controller.dingding.CallbackConstant.*;
|
|
|
-
|
|
|
/**
|
|
|
* <p>DingLoginController 此类用于:钉钉企业内部应用免登(H5微应用)</p>
|
|
|
* <p>@remark:钉钉企业内部微应用DEMO, 实现了身份验证(免登)功能</p>
|
|
@@ -65,6 +48,7 @@ import static com.ruoyi.web.controller.dingding.CallbackConstant.*;
|
|
|
@Controller
|
|
|
@RequestMapping(value = "/ding")
|
|
|
@Slf4j
|
|
|
+@CrossOrigin
|
|
|
public class DingAuthController {
|
|
|
@Resource
|
|
|
private DingAppConfig dingAppConfig;
|
|
@@ -73,7 +57,7 @@ public class DingAuthController {
|
|
|
private DingTokenService dingTokenService;
|
|
|
|
|
|
@Resource
|
|
|
- private DingAuthService dingAuthService;
|
|
|
+ private DingAuthServiceInfo dingAuthServiceInfo;
|
|
|
|
|
|
@Autowired
|
|
|
private SysLoginService loginService;
|
|
@@ -89,285 +73,13 @@ public class DingAuthController {
|
|
|
|
|
|
@Autowired
|
|
|
private ICompanyUserService companyUserService;
|
|
|
- @Autowired
|
|
|
- private DingAuthenticationProvider dingAuthenticationProvider;
|
|
|
+ @Resource
|
|
|
+ private RedisCache redisCache;
|
|
|
|
|
|
|
|
|
- /**
|
|
|
- * 创建应用,验证回调URL创建有效事件(第一次保存回调URL之前)
|
|
|
- */
|
|
|
- private static final String EVENT_CHECK_CREATE_SUITE_URL = "check_create_suite_url";
|
|
|
|
|
|
- /**
|
|
|
- * 创建应用,验证回调URL变更有效事件(第一次保存回调URL之后)
|
|
|
- */
|
|
|
- private static final String EVENT_CHECK_UPADTE_SUITE_URL = "check_update_suite_url";
|
|
|
|
|
|
- /**
|
|
|
- * suite_ticket推送事件
|
|
|
- */
|
|
|
- private static final String EVENT_SUITE_TICKET = "suite_ticket";
|
|
|
|
|
|
- /**
|
|
|
- * 企业授权开通应用事件
|
|
|
- */
|
|
|
- private static final String EVENT_TMP_AUTH_CODE = "tmp_auth_code";
|
|
|
-
|
|
|
- @PostMapping(value = "/callback1")
|
|
|
- public Map<String, String> callback1(@RequestParam String signature,
|
|
|
- @RequestParam String timestamp,
|
|
|
- @RequestParam String nonce,
|
|
|
- @RequestBody JsonNode encryptNode) {
|
|
|
- String encryptMsg = encryptNode.get("encrypt").textValue();
|
|
|
- String plainText = this.decryptText(signature, timestamp, nonce, encryptMsg);
|
|
|
- JSONObject callBackContent = JSON.parseObject(plainText);
|
|
|
-
|
|
|
- //进入回调事件分支选择
|
|
|
- Map<String, String> resultMap = caseProcess(callBackContent);
|
|
|
- return resultMap;
|
|
|
- }
|
|
|
- public String decryptText(String signature, String timestamp, String nonce, String encryptMsg) {
|
|
|
- String plainText = "";
|
|
|
- try {
|
|
|
- DingTalkEncryptor dingTalkEncryptor = new DingTalkEncryptor(Constant.TOKEN, Constant.ENCODING_AES_KEY, Constant.SUITE_KEY);
|
|
|
- plainText = dingTalkEncryptor.getDecryptMsg(signature, timestamp, nonce, encryptMsg);
|
|
|
- } catch (DingTalkEncryptException e) {
|
|
|
- log.error("钉钉消息体解密错误, signature: {}, timestamp: {}, nonce: {}, encryptMsg: {}, e: {}", signature, timestamp, nonce, encryptMsg, e);
|
|
|
- }
|
|
|
- log.debug("钉钉消息体解密, signature: {}, timestamp: {}, nonce: {}, encryptMsg: {}, 解密结果: {}", signature, timestamp, nonce, encryptMsg, plainText);
|
|
|
- return plainText;
|
|
|
- }
|
|
|
-
|
|
|
- private Map<String, String> caseProcess(JSONObject plainNode) {
|
|
|
- Map<String, String> resultMap = new LinkedHashMap<>();
|
|
|
- String eventType = plainNode.getString("EventType");
|
|
|
- switch (eventType) {
|
|
|
- case SUITE_TICKET_CALLBACK_URL_VALIDATE:
|
|
|
- log.info("[callback] 验证回调地址有效性质:{}", plainNode);
|
|
|
- resultMap = encryptText(CALLBACK_RETURN_SUCCESS);
|
|
|
- break;
|
|
|
- case TEMP_AUTH_CODE_ACTIVE:
|
|
|
- log.info("[callback] 企业开通授权:{}", plainNode);
|
|
|
-
|
|
|
- resultMap = encryptText(CALLBACK_RETURN_SUCCESS);
|
|
|
- break;
|
|
|
- case SUITE_RELIEVE:
|
|
|
- log.info("[callback] 企业解除授权:{}", plainNode);
|
|
|
- resultMap = encryptText(CALLBACK_RETURN_SUCCESS);
|
|
|
- break;
|
|
|
- case CHECK_UPDATE_SUITE_URL:
|
|
|
- log.info("[callback] 在开发者后台修改回调地址:" + plainNode);
|
|
|
- resultMap = encryptText(CALLBACK_RETURN_SUCCESS);
|
|
|
- break;
|
|
|
- case CHECK_CREATE_SUITE_URL:
|
|
|
- log.info("[callback] 检查钉钉向回调URL POST数据解密后是否成功:" + plainNode);
|
|
|
- resultMap = encryptText(CALLBACK_RETURN_SUCCESS);
|
|
|
- break;
|
|
|
- case CONTACT_CHANGE_AUTH:
|
|
|
- log.info("[callback] 通讯录授权范围变更事件:" + plainNode);
|
|
|
- break;
|
|
|
- case ORG_MICRO_APP_STOP:
|
|
|
- log.info("[callback] 停用应用:" + plainNode);
|
|
|
- break;
|
|
|
- case ORG_MICRO_APP_RESTORE:
|
|
|
- log.info("[callback] 启用应用:" + plainNode);
|
|
|
- break;
|
|
|
- case MARKET_BUY:
|
|
|
- log.info("[callback] 用户下单购买事件:" + plainNode);
|
|
|
- break;
|
|
|
- default:
|
|
|
- log.info("[callback] 未知事件: {} , 内容: {}", eventType, plainNode);
|
|
|
- resultMap = encryptText("事件类型未定义, 请联系应用提供方!" + eventType);
|
|
|
- break;
|
|
|
- }
|
|
|
- return resultMap;
|
|
|
- }
|
|
|
- public Map<String, String> encryptText(String text) {
|
|
|
- Map<String, String> resultMap = new LinkedHashMap<>();
|
|
|
- try {
|
|
|
- DingTalkEncryptor dingTalkEncryptor = new DingTalkEncryptor(Constant.TOKEN, Constant.ENCODING_AES_KEY, Constant.SUITE_KEY);
|
|
|
- resultMap = dingTalkEncryptor.getEncryptedMap(text, System.currentTimeMillis(),getRandomStr(8));
|
|
|
- } catch (DingTalkEncryptException e) {
|
|
|
- log.error("钉钉消息体加密,text: {}, e: {}", text, e);
|
|
|
- }
|
|
|
- log.debug("钉钉消息体加密,text: {}, resultMap: {}", text, resultMap);
|
|
|
- return resultMap;
|
|
|
- }
|
|
|
- public static String getRandomStr(int count) {
|
|
|
- String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
|
- Random random = new Random();
|
|
|
- StringBuffer sb = new StringBuffer();
|
|
|
-
|
|
|
- for(int i = 0; i < count; ++i) {
|
|
|
- int number = random.nextInt(base.length());
|
|
|
- sb.append(base.charAt(number));
|
|
|
- }
|
|
|
-
|
|
|
- return sb.toString();
|
|
|
- }
|
|
|
- @PostMapping(value = "callback2")
|
|
|
- public Object callback2(
|
|
|
- @RequestParam(value = "signature") String signature,
|
|
|
- @RequestParam(value = "timestamp") Long timestamp,
|
|
|
- @RequestParam(value = "nonce") String nonce,
|
|
|
- @RequestBody(required = false) JSONObject body
|
|
|
- ) {
|
|
|
- String params = "signature:" + signature + " timestamp:" + timestamp + " nonce:" + nonce + " body:" + body;
|
|
|
- try {
|
|
|
- log.info("begin callback:" + params);
|
|
|
- DingTalkEncryptor dingTalkEncryptor = new DingTalkEncryptor(Constant.TOKEN, Constant.ENCODING_AES_KEY, Constant.SUITE_KEY);
|
|
|
-
|
|
|
- // 从post请求的body中获取回调信息的加密数据进行解密处理
|
|
|
- String encrypt = body.getString("encrypt");
|
|
|
- String plainText = dingTalkEncryptor.getDecryptMsg(signature, timestamp.toString(), nonce, encrypt);
|
|
|
- JSONObject callBackContent = JSON.parseObject(plainText);
|
|
|
- log.info("begin callback encrypt:" + callBackContent.toString());
|
|
|
- // 根据回调事件类型做不同的业务处理
|
|
|
- String eventType = callBackContent.getString("EventType");
|
|
|
- if (EVENT_CHECK_CREATE_SUITE_URL.equals(eventType)) {
|
|
|
- log.info("验证新创建的回调URL有效性: " + plainText);
|
|
|
- } else if (EVENT_CHECK_UPADTE_SUITE_URL.equals(eventType)) {
|
|
|
- log.info("验证更新回调URL有效性: " + plainText);
|
|
|
- } else if (EVENT_SUITE_TICKET.equals(eventType)) {
|
|
|
- // suite_ticket用于用签名形式生成accessToken(访问钉钉服务端的凭证),需要保存到应用的db。
|
|
|
- // 钉钉会定期向本callback url推送suite_ticket新值用以提升安全性。
|
|
|
- // 应用在获取到新的时值时,保存db成功后,返回给钉钉success加密串(如本demo的return)
|
|
|
- log.info("应用suite_ticket数据推送: " + plainText);
|
|
|
- } else if (EVENT_TMP_AUTH_CODE.equals(eventType)) {
|
|
|
- // 本事件应用应该异步进行授权开通企业的初始化,目的是尽最大努力快速返回给钉钉服务端。用以提升企业管理员开通应用体验
|
|
|
- // 即使本接口没有收到数据或者收到事件后处理初始化失败都可以后续再用户试用应用时从前端获取到corpId并拉取授权企业信息,进而初始化开通及企业。
|
|
|
- log.info("企业授权开通应用事件: " + plainText);
|
|
|
- } else {
|
|
|
- // 其他类型事件处理
|
|
|
- }
|
|
|
- log.info("callback 成功");
|
|
|
- log.info("plainText:" + plainText);
|
|
|
- // 返回success的加密信息表示回调处理成功
|
|
|
- Map<String, String> success = dingTalkEncryptor.getEncryptedMap("success", timestamp, nonce);
|
|
|
- log.info("success:" + JSON.toJSONString(success));
|
|
|
- return success;
|
|
|
- } catch (Exception e) {
|
|
|
- //失败的情况,应用的开发者应该通过告警感知,并干预修复
|
|
|
- log.error("process callback fail." + params, e);
|
|
|
- return "fail";
|
|
|
- }
|
|
|
- }
|
|
|
- @PostMapping(value = "callback3")
|
|
|
- public Object callback3(
|
|
|
- @RequestParam(value = "signature") String signature,
|
|
|
- @RequestParam(value = "timestamp") Long timestamp,
|
|
|
- @RequestParam(value = "nonce") String nonce,
|
|
|
- @RequestBody(required = false) JSONObject body
|
|
|
- ) {
|
|
|
- String params = "signature:" + signature + " timestamp:" + timestamp + " nonce:" + nonce + " body:" + body;
|
|
|
- DingTalkEncryptor dingTalkEncryptor = null;
|
|
|
- try {
|
|
|
- dingTalkEncryptor = new DingTalkEncryptor(Constant.TOKEN, Constant.ENCODING_AES_KEY, Constant.SUITE_KEY);
|
|
|
- // 从post请求的body中获取回调信息的加密数据进行解密处理,消息加解密参见下文
|
|
|
- String encrypt = body.getString("encrypt");
|
|
|
- String plainText = dingTalkEncryptor.getDecryptMsg(signature, timestamp.toString(), nonce, encrypt);
|
|
|
- log.info("应用suite_ticket数据推送: " + plainText);
|
|
|
- JSONObject callBackContent = JSON.parseObject(plainText);
|
|
|
- log.info("begin callback encrypt:" + callBackContent.toString());
|
|
|
- // 根据回调事件类型做不同的业务处理
|
|
|
- String eventType = callBackContent.getString("EventType");
|
|
|
- //TODO 将数据持久化
|
|
|
- return plainText;
|
|
|
- } catch (Exception e) {
|
|
|
- log.error("process callback fail." + params, e);
|
|
|
- return "fail";
|
|
|
- }
|
|
|
- }
|
|
|
- /**
|
|
|
- * 钉钉用户登录,显示当前登录用户的userId和名称
|
|
|
- *
|
|
|
- * @param authCode 免登临时authCode
|
|
|
- * @return 当前用户
|
|
|
- */
|
|
|
-// @ApiOperation(value = "钉钉用户登录,显示当前登录用户的userId和名称")
|
|
|
-// @PostMapping("/avoidLogin")
|
|
|
-// @ResponseBody
|
|
|
-// public AjaxResult login(@RequestBody String authCode) {
|
|
|
-// String accessToken;
|
|
|
-//
|
|
|
-// // 获取accessToken
|
|
|
-// AjaxResult accessTokenSr = dingTokenService.getAccessToken();
|
|
|
-// if (!accessTokenSr.isSuccess()) {
|
|
|
-// return accessTokenSr;
|
|
|
-// }
|
|
|
-// accessToken = accessTokenSr.get(accessTokenSr.MSG_TAG).toString();
|
|
|
-//
|
|
|
-// // 获取用户userId
|
|
|
-// AjaxResult userIdSr = getUserInfo(accessToken, authCode);
|
|
|
-// if (!userIdSr.isSuccess()) {
|
|
|
-// return userIdSr;
|
|
|
-// }
|
|
|
-//
|
|
|
-// // 获取用户详情
|
|
|
-// AjaxResult userInfo = this.getUser(accessToken, userIdSr.get(userIdSr.MSG_TAG).toString());
|
|
|
-//
|
|
|
-// return userInfo;
|
|
|
-// }
|
|
|
-
|
|
|
-// @ApiOperation(value = "11111")
|
|
|
-// @PostMapping("/avoidLogin")
|
|
|
-// @ResponseBody
|
|
|
-// public AjaxResult login2() {
|
|
|
-// String accessToken;
|
|
|
-
|
|
|
- // 获取accessToken
|
|
|
-// AjaxResult accessTokenSr = dingTokenService.getAccessToken();
|
|
|
-// if (!accessTokenSr.isSuccess()) {
|
|
|
-// return accessTokenSr;
|
|
|
-// }
|
|
|
-// accessToken = accessTokenSr.get(accessTokenSr.MSG_TAG).toString();
|
|
|
-// AjaxResult userIdSr = getUserInfo(accessToken, "authCode");
|
|
|
-// if (!userIdSr.isSuccess()) {
|
|
|
-// return userIdSr;
|
|
|
-// }
|
|
|
-// loginService.authSingleSignIn2(accessToken, authCode);
|
|
|
-// String token = loginService.authSingleSignIn2("123", "123");
|
|
|
-//
|
|
|
-// if (StringUtils.isNotEmpty(token)){
|
|
|
-// userIdSr.put(Constants.TOKEN, token);
|
|
|
-// }
|
|
|
-// return userIdSr;
|
|
|
-// }
|
|
|
-
|
|
|
- /**
|
|
|
- * 钉钉用户登录,显示当前登录用户的userId和名称
|
|
|
- *
|
|
|
- * @param authCode 免登临时authCode
|
|
|
- * @return 当前用户
|
|
|
- */
|
|
|
-
|
|
|
- @ApiOperation(value = "钉钉用户登录第三方企业应用,显示当前登录用户的userId和名称")
|
|
|
- @PostMapping("/authLogin")
|
|
|
- @ResponseBody
|
|
|
- public AjaxResult authLogin(@RequestBody String authCode) {
|
|
|
- String accessToken = dingTokenService.getCorpAccessToken();
|
|
|
- if (StringUtils.isNotEmpty(accessToken)) {
|
|
|
- // 获取用户userId
|
|
|
- AjaxResult userIdSr = getUserInfo(accessToken, authCode);
|
|
|
- if (!userIdSr.isSuccess()) {
|
|
|
- return userIdSr;
|
|
|
- }
|
|
|
- Map<String, Object> stringObjectMap = loginService.authSingleSignIn2(accessToken, userIdSr.get(userIdSr.MSG_TAG).toString());
|
|
|
- DingToken dingToken = (DingToken) stringObjectMap.get("authentication");
|
|
|
- if (StringUtils.isNull(dingToken)) {
|
|
|
- OapiV2UserGetResponse oapiV2UserGetResponse = (OapiV2UserGetResponse) dingToken.getTaobaoResponse();
|
|
|
- SysUser sysUser = dingAuthService.getUserByUsername(oapiV2UserGetResponse.getResult().getMobile());
|
|
|
- if (StringUtils.isNull(sysUser)) {
|
|
|
- // todo 生成用户信息进行登录
|
|
|
- addUserAndOrg(oapiV2UserGetResponse);
|
|
|
- }
|
|
|
- }
|
|
|
- String token = stringObjectMap.get("token").toString();
|
|
|
- return userIdSr;
|
|
|
- } else {
|
|
|
- return AjaxResult.error("getAccessToken failed");
|
|
|
- }
|
|
|
- }
|
|
|
|
|
|
|
|
|
/**
|
|
@@ -452,8 +164,8 @@ public class DingAuthController {
|
|
|
String userid = response.getUserid();
|
|
|
String mobile = response.getMobile();
|
|
|
try {
|
|
|
- SysUser sysUser = Optional.ofNullable(dingAuthService.getUserByUserId(userid))
|
|
|
- .orElse(dingAuthService.getUserByUsername(mobile));
|
|
|
+ SysUser sysUser = Optional.ofNullable(dingAuthServiceInfo.getUserByUserId(userid))
|
|
|
+ .orElse(dingAuthServiceInfo.getUserByUsername(mobile));
|
|
|
sysUser.setAccessToken(accessToken);
|
|
|
sysUser.setNickName(response.getName());
|
|
|
sysUser.setAvatar(response.getAvatar());
|
|
@@ -534,7 +246,7 @@ public class DingAuthController {
|
|
|
GetUserResponse response = client.getUserWithOptions("me", getUserHeaders, new RuntimeOptions());
|
|
|
accessTokenSr.put("sysUser", response.getBody());
|
|
|
|
|
|
- SysUser sysUser = dingAuthService.getUserByUsername(response.getBody().getMobile());
|
|
|
+ SysUser sysUser = dingAuthServiceInfo.getUserByUsername(response.getBody().getMobile());
|
|
|
|
|
|
if (StringUtils.isNull(sysUser)) {
|
|
|
// todo 生成用户信息进行登录
|