Browse Source

钉钉第三方企业应用登录

hanzhuoyue 10 months ago
parent
commit
976f986ce0
24 changed files with 937 additions and 876 deletions
  1. 10 298
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/dingding/DingAuthController.java
  2. 0 50
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/dingding/DingTalkEventListener.java
  3. 87 0
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/dingding/DingThirdAuthController.java
  4. 4 1
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/dingding/CallbackConstant.java
  5. 2 2
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/dingding/Constant.java
  6. 16 0
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/dingding/request/login/AuthLoginRequest.java
  7. 14 0
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/dingding/request/login/BaseRequest.java
  8. 42 0
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/dingding/service/DingAuthServiceInfo.java
  9. 265 0
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/dingding/service/DingAuthServiceInfoImpl.java
  10. 164 0
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/dingding/service/DingThirdTokenService.java
  11. 26 56
      ruoyi-system/src/main/java/com/ruoyi/system/service/dingding/DingTokenService.java
  12. 15 9
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/tool/ResumeController.java
  13. 1 1
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/tool/mobile/ResumeMobileController.java
  14. 184 181
      ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java
  15. 38 43
      ruoyi-common/src/main/java/com/ruoyi/common/utils/SecurityUtils.java
  16. 2 2
      ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java
  17. 1 1
      ruoyi-framework/src/main/java/com/ruoyi/framework/security/filter/JwtAuthenticationTokenFilter.java
  18. 21 21
      ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/DingAuthenticationProvider.java
  19. 0 85
      ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/DingDingAuthService.java
  20. 21 29
      ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/DingToken.java
  21. 23 21
      ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysLoginService.java
  22. 1 0
      ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/UserDetailsServiceImpl.java
  23. 0 27
      ruoyi-system/src/main/java/com/ruoyi/system/service/dingding/DingAuthService.java
  24. 0 49
      ruoyi-system/src/main/java/com/ruoyi/system/service/dingding/impl/DingAuthServiceImpl.java

+ 10 - 298
ruoyi-admin/src/main/java/com/ruoyi/web/controller/dingding/DingAuthController.java

@@ -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 生成用户信息进行登录

+ 0 - 50
ruoyi-admin/src/main/java/com/ruoyi/web/controller/dingding/DingTalkEventListener.java

@@ -1,50 +0,0 @@
-package com.ruoyi.web.controller.dingding;
-
-import com.alibaba.fastjson2.JSONObject;
-import com.dingtalk.open.app.api.GenericEventListener;
-import com.dingtalk.open.app.api.message.GenericOpenDingTalkEvent;
-import com.dingtalk.open.app.stream.protocol.event.EventAckStatus;
-import com.ruoyi.common.constant.Constants;
-import com.ruoyi.common.core.redis.RedisCache;
-import com.ruoyi.common.utils.StringUtils;
-import com.ruoyi.common.utils.spring.SpringUtils;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.context.ApplicationEvent;
-import org.springframework.context.ApplicationListener;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.event.EventListener;
-import org.springframework.stereotype.Component;
-
-import javax.annotation.Resource;
-
-import java.util.concurrent.TimeUnit;
-
-import static com.ruoyi.framework.datasource.DynamicDataSourceContextHolder.log;
-
-@Component
-@Slf4j
-public class DingTalkEventListener implements GenericEventListener {
-
-    @Override
-    public EventAckStatus onEvent(GenericOpenDingTalkEvent event) {
-        log.info("收到事件: " + event);
-        try {
-            //事件的唯一Id
-            String eventId = event.getEventId();
-            //事件类型
-            String eventType = event.getEventType();
-            //事件产生时间
-            Long bornTime = event.getEventBornTime();
-            //获取事件体
-            log.info("eventId: " + eventId + ", eventType: " + eventType + ", bornTime: " + bornTime);
-            SpringUtils.getBean(RedisCache.class).setCacheObject(Constants.DAILY_DING_SUITE_TICKET, event.getData().get("suiteTicket"), 5, TimeUnit.HOURS);
-            //处理事件
-
-            //消费成功
-            return EventAckStatus.SUCCESS;
-        } catch (Exception e) {
-            //消费失败
-            return EventAckStatus.LATER;
-        }
-    }
-}

+ 87 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/controller/dingding/DingThirdAuthController.java

@@ -0,0 +1,87 @@
+package com.ruoyi.web.controller.dingding;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.dingtalk.api.response.*;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.domain.entity.SysUser;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.framework.web.service.SysLoginService;
+import com.ruoyi.web.controller.dingding.request.login.AuthLoginRequest;
+import com.ruoyi.web.controller.dingding.service.DingAuthServiceInfo;
+import com.ruoyi.web.controller.dingding.service.DingThirdTokenService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import java.util.*;
+
+/**
+ * <p>DingLoginController 此类用于:钉钉企业内部应用免登(H5微应用)</p>
+ * <p>@remark:钉钉企业内部微应用DEMO, 实现了身份验证(免登)功能</p>
+ */
+@Api(value = "dingAuthController", tags = "钉钉第三方企业应用免登(H5微应用)")
+@Controller
+@RequestMapping(value = "/auth/ding")
+@Slf4j
+public class DingThirdAuthController {
+
+    @Resource
+    private DingThirdTokenService dingAuthTokenService;
+
+    @Resource
+    private DingAuthServiceInfo dingAuthServiceInfo;
+
+    @Autowired
+    private SysLoginService loginService;
+
+
+    @PostMapping(value = "/callback")
+    @ResponseBody
+    public Map<String, String> callback(@RequestParam String signature,
+                                        @RequestParam String timestamp,
+                                        @RequestParam String nonce,
+                                        @RequestBody JsonNode encryptNode) {
+        String encryptMsg = encryptNode.get("encrypt").textValue();
+        String plainText = dingAuthServiceInfo.decryptText(signature, timestamp, nonce, encryptMsg);
+        JSONObject callBackContent = JSON.parseObject(plainText);
+
+        //进入回调事件分支选择
+        Map<String, String> resultMap = dingAuthServiceInfo.caseProcess(callBackContent);
+        return resultMap;
+    }
+
+
+    @ApiOperation(value = "钉钉用户登录第三方企业应用,显示当前登录用户的userId和名称")
+    @PostMapping("/authLogin")
+    @ResponseBody
+    public AjaxResult authLogin(@RequestBody AuthLoginRequest authLoginRequest) {
+        log.info("钉钉用户登录第三方企业应用code:{},corpId:{}",authLoginRequest.getCode(),authLoginRequest.getCorpId());
+
+        //调用接口获取第三方企业应用的access_token,详情请参考获取第三方应用授权企业的accessToken。
+        String corpAccessToken = dingAuthTokenService.getCorpAccessToken(authLoginRequest.getCorpId());
+        log.info("用户登录信息corpAccessToken" + corpAccessToken);
+        //获取用户userid。
+        OapiV2UserGetuserinfoResponse userUnfo = dingAuthTokenService.getUserUnfo(authLoginRequest.getCode(), corpAccessToken);
+        log.info("用户登录信息" + JSONObject.toJSONString(userUnfo));
+        //调用接口获取用户的userid,详情请参考通过免登码获取用户信息。
+        OapiV2UserGetResponse authUser = dingAuthTokenService.getAuthUser(corpAccessToken, userUnfo.getResult().getUserid());
+
+        log.info("用户登录详细信息" + JSONObject.toJSONString(authUser.getResult()));
+        //获取用户详情。
+        SysUser sysUser = dingAuthServiceInfo.getUserByUserId(userUnfo.getResult().getUserid());
+
+        if (StringUtils.isNull(sysUser)) {
+            // todo 生成用户信息进行登录
+            dingAuthServiceInfo.addUserAndOrg2(authUser,authLoginRequest.getCorpId());
+        }
+        String token = loginService.authDingThirdLogin(userUnfo.getResult().getUserid(), authLoginRequest.getCorpId());
+        return AjaxResult.success(token);
+    }
+
+}

+ 4 - 1
ruoyi-admin/src/main/java/com/ruoyi/web/controller/dingding/CallbackConstant.java

@@ -1,4 +1,4 @@
-package com.ruoyi.web.controller.dingding;
+package com.ruoyi.web.controller.dingding.config;
 
 /**
  * 钉钉回调相关事件类型
@@ -57,5 +57,8 @@ public class CallbackConstant {
      */
     public static final String MARKET_BUY = "market_buy";
 
+    public static final String SYNC_HTTP_PUSH_HIGH="SYNC_HTTP_PUSH_HIGH";
+    public static final String CHECK_URL="check_url";
+
 
 }

+ 2 - 2
ruoyi-admin/src/main/java/com/ruoyi/web/controller/dingding/Constant.java

@@ -1,9 +1,9 @@
-package com.ruoyi.web.controller.dingding;
+package com.ruoyi.web.controller.dingding.config;
 
 /**
  * 项目中的常量定义类
  */
-public class Constant {
+public class SyncHttpConstant {
     /**
      * 应用的SuiteKey,登录开发者后台,点击应用管理,进入应用详情可见
      */

+ 16 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/controller/dingding/request/login/AuthLoginRequest.java

@@ -0,0 +1,16 @@
+package com.ruoyi.web.controller.dingding.request.login;
+
+import lombok.Data;
+
+import java.util.Base64;
+
+/**
+ * @author hanzy
+ * @description 第三方企业应用登录
+ * @date 2024年05月09日 17:27
+ */
+@Data
+public class AuthLoginRequest  extends BaseRequest {
+    //用户企业id
+    private String corpId;
+}

+ 14 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/controller/dingding/request/login/BaseRequest.java

@@ -0,0 +1,14 @@
+package com.ruoyi.web.controller.dingding.request.login;
+
+import lombok.Data;
+
+/**
+ * @author hanzy
+ * @description
+ * @date 2024年05月10日 17:28
+ */
+@Data
+public class BaseRequest {
+    //用户登录code
+    private String code;
+}

+ 42 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/controller/dingding/service/DingAuthServiceInfo.java

@@ -0,0 +1,42 @@
+package com.ruoyi.web.controller.dingding.service;
+
+import com.alibaba.fastjson.JSONObject;
+import com.dingtalk.api.response.OapiV2UserGetResponse;
+import com.ruoyi.common.core.domain.entity.SysUser;
+
+import java.util.Map;
+
+/**
+ * <p>DingAuthService 此接口用于:</p>
+ * <p>@remark:</p>
+ */
+public interface DingAuthServiceInfo {
+
+    /**
+     * 根据用户手机号查询是否是公司员工
+     *
+     * @param userId 钉钉用户id
+     * @return UIOT员工信息
+     */
+    SysUser getUserByUserId(String userId);
+
+    /**
+     * 根据用户手机号查询是否是公司员工
+     *
+     * @param mobile 钉钉用户手机号
+     * @return UIOT员工信息
+     */
+    SysUser getUserByUsername(String mobile);
+
+    SysUser addUserAndOrg(OapiV2UserGetResponse response);
+
+    SysUser addUserAndOrg2(OapiV2UserGetResponse response,String corpId);
+
+    String decryptText(String signature, String timestamp, String nonce, String encryptMsg);
+
+    Map<String, String> caseProcess(JSONObject plainNode);
+
+    Map<String, String> encryptText(String text);
+
+
+}

+ 265 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/controller/dingding/service/DingAuthServiceInfoImpl.java

@@ -0,0 +1,265 @@
+package com.ruoyi.web.controller.dingding.service;
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.dingtalk.api.response.OapiV2UserGetResponse;
+import com.dingtalk.oapi.lib.aes.DingTalkEncryptException;
+import com.dingtalk.oapi.lib.aes.DingTalkEncryptor;
+import com.ruoyi.common.constant.Constants;
+import com.ruoyi.common.core.domain.entity.SysRole;
+import com.ruoyi.common.core.domain.entity.SysUser;
+import com.ruoyi.common.core.redis.RedisCache;
+import com.ruoyi.common.utils.SecurityUtils;
+import com.ruoyi.system.domain.resume.CompanyInfo;
+import com.ruoyi.system.domain.resume.CompanyUser;
+import com.ruoyi.system.mapper.SysMenuMapper;
+import com.ruoyi.system.mapper.SysUserMapper;
+import com.ruoyi.system.service.ISysRoleService;
+import com.ruoyi.system.service.ISysUserService;
+import com.ruoyi.system.service.resume.ICompanyInfoService;
+import com.ruoyi.system.service.resume.ICompanyUserService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.ruoyi.web.controller.dingding.config.SyncHttpConstant;
+
+import javax.annotation.Resource;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.concurrent.TimeUnit;
+
+import static com.ruoyi.web.controller.dingding.config.CallbackConstant.*;
+
+/**
+ * <p>DingAuthServiceImpl 此类用于:</p>
+ * <p>@author:hujm</p>
+ * <p>@date:2021年05月20日 15:56</p>
+ * <p>@remark:</p>
+ */
+@Slf4j
+@Service
+public class DingAuthServiceInfoImpl implements DingAuthServiceInfo {
+
+    @Resource
+    private SysUserMapper sysUserMapper;
+
+    @Autowired
+    private ISysUserService userService;
+
+    @Autowired
+    private ISysRoleService roleService;
+
+    @Autowired
+    private ICompanyInfoService companyInfoService;
+
+    @Autowired
+    private ICompanyUserService companyUserService;
+    @Resource
+    private RedisCache redisCache;
+    @Resource
+    private SysMenuMapper sysMenuMapper;
+
+    @Override
+    public SysUser getUserByUserId(String userId) {
+
+        if (userId == null) {
+            log.error("根据用户手机号查询UIOT用户时,当前钉钉用户id为空!");
+            return new SysUser();
+        }
+
+        SysUser sysUser = sysUserMapper.selectUserByUserName(userId);
+
+        return sysUser;
+    }
+
+    @Override
+    public SysUser getUserByUsername(String mobile) {
+
+        if (mobile == null) {
+            log.error("根据用户手机号查询UIOT用户时,当前钉钉用户id为空!");
+            return new SysUser();
+        }
+
+        SysUser sysUser = sysUserMapper.selectUserByUserName(mobile);
+
+        return sysUser;
+    }
+
+    @Override
+    public SysUser addUserAndOrg(OapiV2UserGetResponse response) {
+        SysUser newUser = new SysUser();
+        newUser.setUserName(response.getResult().getUserid());
+        newUser.setAvatar(response.getResult().getAvatar());
+        newUser.setNickName(response.getResult().getName());
+        newUser.setEmail(response.getResult().getEmail());
+        newUser.setCreateBy("admin");
+        newUser.setPassword(SecurityUtils.encryptPassword("admin123"));
+        //角色自动赋值为简历管理角色
+        Long[] roleIds = new Long[1];
+        SysRole role = roleService.selectRoleByKey("resume");
+        if (role != null) {
+            roleIds[0] = role.getRoleId();
+        }
+        List<Long> menuIds = sysMenuMapper.selectMenuListByRoleId(role.getRoleId(), role.isMenuCheckStrictly());
+        newUser.setMenuIds(menuIds.toArray(new Long[0]));
+        newUser.setRoleIds(roleIds);
+        newUser.setPhonenumber(newUser.getUserName());
+        userService.insertUser(newUser);
+        //关联企业信息
+        String corpId = response.getResult().getUnionEmpExt().getCorpId();
+        CompanyInfo companyInfo = new CompanyInfo();
+        companyInfo.setCorpId(corpId);
+        List<CompanyInfo> companyInfoList = companyInfoService.selectCompanyInfoList(companyInfo);
+        if (companyInfoList != null && !companyInfoList.isEmpty()) {
+            CompanyUser companyUser = new CompanyUser();
+            companyUser.setCompanyId(companyInfoList.get(0).getId());
+            companyUser.setUserId(newUser.getUserId());
+            companyUserService.insertCompanyUser(companyUser);
+        }
+        return newUser;
+    }
+
+    @Override
+    public SysUser addUserAndOrg2(OapiV2UserGetResponse response,String corpId) {
+        SysUser newUser = new SysUser();
+        newUser.setUserName(response.getResult().getUserid());
+        newUser.setAvatar(response.getResult().getAvatar());
+        newUser.setNickName(response.getResult().getName());
+        newUser.setEmail(response.getResult().getEmail());
+        newUser.setCreateBy("admin");
+        newUser.setPassword(SecurityUtils.encryptPassword("admin123"));
+        //角色自动赋值为简历管理角色
+        Long[] roleIds = new Long[1];
+        SysRole role = roleService.selectRoleByKey("resume");
+        if (role != null) {
+            roleIds[0] = role.getRoleId();
+        }
+        List<Long> menuIds = sysMenuMapper.selectMenuListByRoleId(role.getRoleId(), role.isMenuCheckStrictly());
+        newUser.setMenuIds(menuIds.toArray(new Long[0]));
+        newUser.setRoleIds(roleIds);
+        newUser.setPhonenumber(newUser.getUserName());
+        userService.insertUser(newUser);
+        //关联企业信息
+        CompanyInfo companyInfo = new CompanyInfo();
+        companyInfo.setCorpId(corpId);
+        List<CompanyInfo> companyInfoList = companyInfoService.selectCompanyInfoList(companyInfo);
+        if (companyInfoList != null && !companyInfoList.isEmpty()) {
+            CompanyUser companyUser = new CompanyUser();
+            companyUser.setCompanyId(companyInfoList.get(0).getId());
+            companyUser.setUserId(newUser.getUserId());
+            companyUserService.insertCompanyUser(companyUser);
+        }
+        return newUser;
+    }
+
+    @Override
+    public String decryptText(String signature, String timestamp, String nonce, String encryptMsg) {
+        String plainText = "";
+        try {
+            DingTalkEncryptor dingTalkEncryptor = new DingTalkEncryptor(SyncHttpConstant.TOKEN, SyncHttpConstant.ENCODING_AES_KEY, SyncHttpConstant.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;
+    }
+    @Override
+    public 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;
+            case SYNC_HTTP_PUSH_HIGH:
+                log.info("[callback] 套件票据事件:" + plainNode);
+                JSONArray bizData = plainNode.getJSONArray("bizData");
+                log.info("[callback] 套件票据事件123:" + bizData);
+                for (Object bizDatum : bizData) {
+                    JSONObject bizDataJson = (JSONObject) bizDatum;
+                    log.info("[callback] 获取到套件票据bizDataJson:" + bizDataJson);
+                    Integer bizType = bizDataJson.getInteger("biz_type");
+                    log.info("[callback] 获取到套件票据bizType:" + bizType);
+                    if (bizType == 2) {
+                        String biz_data_str = bizDataJson.getString("biz_data");
+                        JSONObject biz_data = JSONObject.parseObject(biz_data_str);
+                        log.info("[callback] 获取到套件票据biz_data:" + biz_data);
+                        String suiteTicket = biz_data.getString("suiteTicket");
+                        String corp_id = bizDataJson.getString("corp_id");
+                        String redisKeyPrefix = Constants.DAILY_DING_AUTH + corp_id + ":";
+                        log.info("[callback] 获取到套件票据corp_id:{}", corp_id);
+                        redisCache.setCacheObject(redisKeyPrefix + Constants.DAILY_DING_SUITE_TICKET, suiteTicket, 5, TimeUnit.HOURS);
+                    }
+                }
+                resultMap = encryptText(CALLBACK_RETURN_SUCCESS);
+                break;
+            case CHECK_URL:
+                log.info("[callback] 检查钉钉向回调URL POST数据解密后是否成功:" + plainNode);
+                resultMap = encryptText(CALLBACK_RETURN_SUCCESS);
+                break;
+            default:
+                log.info("[callback] 未知事件: {} , 内容: {}", eventType, plainNode);
+                resultMap = encryptText("事件类型未定义, 请联系应用提供方!" + eventType);
+                break;
+        }
+        return resultMap;
+    }
+    @Override
+    public Map<String, String> encryptText(String text) {
+        Map<String, String> resultMap = new LinkedHashMap<>();
+        try {
+            DingTalkEncryptor dingTalkEncryptor = new DingTalkEncryptor(SyncHttpConstant.TOKEN, SyncHttpConstant.ENCODING_AES_KEY, SyncHttpConstant.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();
+    }
+
+}

+ 164 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/controller/dingding/service/DingThirdTokenService.java

@@ -0,0 +1,164 @@
+package com.ruoyi.web.controller.dingding.service;
+
+import com.aliyun.dingtalkoauth2_1_0.Client;
+import com.aliyun.dingtalkoauth2_1_0.models.*;
+import com.aliyun.tea.TeaException;
+import com.aliyun.teaopenapi.models.Config;
+import com.aliyun.teautil.models.RuntimeOptions;
+import com.dingtalk.api.DefaultDingTalkClient;
+import com.dingtalk.api.DingTalkClient;
+import com.dingtalk.api.request.OapiV2UserGetRequest;
+import com.dingtalk.api.request.OapiV2UserGetuserinfoRequest;
+import com.dingtalk.api.response.OapiV2UserGetResponse;
+import com.dingtalk.api.response.OapiV2UserGetuserinfoResponse;
+import com.ruoyi.common.constant.Constants;
+import com.ruoyi.common.core.redis.RedisCache;
+import com.ruoyi.common.utils.dingding.config.DingAppConfig;
+import com.ruoyi.common.utils.dingding.config.DingUrlConstant;
+import com.taobao.api.ApiException;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author hzy
+ * @description: 钉钉第三方应用授权
+ * @param: null
+ * @return:
+ * @date: 2024/5/9 14:29
+ */
+@Service
+@Slf4j
+public class DingThirdTokenService {
+    @Resource
+    private DingAppConfig dingAppConfig;
+    @Resource
+    private RedisCache redisCache;
+
+    public static Client createClient() throws Exception {
+        Config config = new Config();
+        config.protocol = "https";
+        config.regionId = "central";
+        return new Client(config);
+    }
+
+    public GetUserTokenResponse getUserAccessToken(String code) {
+        GetUserTokenResponse userToken = null;
+        try {
+            Client client = this.createClient();
+            GetUserTokenRequest request = new GetUserTokenRequest()
+                    .setClientSecret(dingAppConfig.getAppSecret())
+                    .setClientId(dingAppConfig.getAppKey())
+                    .setCode(code).setGrantType(Constants.AUTHORIZATION_CODE);
+            userToken = client.getUserToken(request);
+        } catch (ApiException e) {
+            log.error("getAccessToken failed", e);
+        } catch (Exception e) {
+            log.error("getAccessToken failed", e);
+        }
+        return userToken;
+    }
+
+    /**
+     * 在此方法中,为了避免频繁获取access_token,
+     * 在距离上一次获取access_token时间在两个小时之内的情况,
+     * 将直接从持久化存储中读取access_token
+     * <p>
+     * 因为access_token和jsapi_ticket的过期时间都是7200秒
+     * 所以在获取access_token的同时也去获取了jsapi_ticket
+     * 注:jsapi_ticket是在前端页面JSAPI做权限验证配置的时候需要使用的
+     * 具体信息请查看开发者文档--权限验证配置
+     *
+     * @return accessToken 或错误信息
+     */
+    public String getCorpAccessToken(String corpId) {
+        String redisKeyPrefix = Constants.DAILY_DING_AUTH + corpId + ":";
+        // 从持久化存储中读取
+        String corpAccessToken = redisCache.getCacheObject(redisKeyPrefix + Constants.DAILY_DING_CORP_ACCESS_TOKEN);
+        log.info("从Redis缓存中获取到的第三方企业{},corpAccessToken = {}", corpAccessToken);
+        if (corpAccessToken != null) {
+            return corpAccessToken;
+        }
+        try {
+            com.aliyun.dingtalkoauth2_1_0.Client client = this.createClient();
+            String suiteTicket = redisCache.getCacheObject(redisKeyPrefix + Constants.DAILY_DING_SUITE_TICKET);
+            log.info("从Redis缓存中获取到的第三方企业{},suiteTicket = {}", corpId, suiteTicket);
+            com.aliyun.dingtalkoauth2_1_0.models.GetCorpAccessTokenRequest getCorpAccessTokenRequest = new com.aliyun.dingtalkoauth2_1_0.models.GetCorpAccessTokenRequest()
+                    .setSuiteKey(dingAppConfig.getAppKey())
+                    .setSuiteSecret(dingAppConfig.getAppSecret())
+                    .setAuthCorpId(corpId)
+                    .setSuiteTicket(suiteTicket);
+            GetCorpAccessTokenResponse response = client.getCorpAccessToken(getCorpAccessTokenRequest);
+            corpAccessToken = response.getBody().getAccessToken();
+            Long expireIn = response.getBody().getExpireIn();
+            log.info("从Redis缓存中获取到的第三方企业{},corpAccessToken = {}", corpAccessToken);
+            redisCache.setCacheObject(Constants.DAILY_DING_CORP_ACCESS_TOKEN, corpAccessToken, expireIn.intValue(), TimeUnit.MINUTES);
+        } catch (TeaException err) {
+            if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {
+                // err 中含有 code 和 message 属性,可帮助开发定位问题
+                log.error("getAccessToken failed", err.message);
+            }
+            return corpAccessToken;
+        } catch (Exception _err) {
+            TeaException err = new TeaException(_err.getMessage(), _err);
+            if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {
+                // err 中含有 code 和 message 属性,可帮助开发定位问题
+                log.error("getAccessToken failed", err.message);
+            }
+            return corpAccessToken;
+        }
+        return corpAccessToken;
+    }
+
+    public OapiV2UserGetuserinfoResponse getUserUnfo(String code,String access_token) {
+        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, access_token);
+        } catch (ApiException e) {
+            throw new RuntimeException(e);
+        }
+        return rsp;
+    }
+    public OapiV2UserGetResponse getAuthUser(String accessToken, String userId) {
+        DingTalkClient client = new DefaultDingTalkClient(DingUrlConstant.URL_USER_GET_V2);
+        OapiV2UserGetResponse response;
+        OapiV2UserGetRequest request = new OapiV2UserGetRequest();
+        request.setUserid(userId);
+        request.setLanguage("en_US");
+        try {
+            response = client.execute(request, accessToken);
+        } catch (ApiException e) {
+            log.error("Failed to getUserName: " + e.getErrMsg());
+            return null;
+        }
+        return response;
+    }
+    //获取应用管理后台免登的用户信息
+    public Object getSsoUserInfo(String xAcsDingtalkAccessToken, String code) {
+        GetSsoUserInfoResponse ssoUserInfoWithOptions = null;
+        try {
+            com.aliyun.dingtalkoauth2_1_0.Client client = this.createClient();
+            GetSsoUserInfoHeaders getSsoUserInfoHeaders = new GetSsoUserInfoHeaders();
+            getSsoUserInfoHeaders.xAcsDingtalkAccessToken = xAcsDingtalkAccessToken;
+            GetSsoUserInfoRequest getSsoUserInfoRequest = new GetSsoUserInfoRequest()
+                    .setCode(code);
+            ssoUserInfoWithOptions = client.getSsoUserInfoWithOptions(getSsoUserInfoRequest, getSsoUserInfoHeaders, new RuntimeOptions());
+        } catch (TeaException err) {
+            if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {
+                // err 中含有 code 和 message 属性,可帮助开发定位问题
+            }
+        } catch (Exception _err) {
+            TeaException err = new TeaException(_err.getMessage(), _err);
+            if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {
+                // err 中含有 code 和 message 属性,可帮助开发定位问题
+            }
+        }
+        return ssoUserInfoWithOptions;
+    }
+
+}

+ 26 - 56
ruoyi-system/src/main/java/com/ruoyi/system/service/dingding/DingTokenService.java

@@ -1,4 +1,4 @@
-package com.ruoyi.system.service.dingding;
+package com.ruoyi.web.controller.dingding.service;
 
 import com.aliyun.dingtalkoauth2_1_0.Client;
 import com.aliyun.dingtalkoauth2_1_0.models.*;
@@ -83,56 +83,11 @@ public class DingTokenService {
         return AjaxResult.success(accessToken);
     }
 
-    /**
-     * 在此方法中,为了避免频繁获取access_token,
-     * 在距离上一次获取access_token时间在两个小时之内的情况,
-     * 将直接从持久化存储中读取access_token
-     * <p>
-     * 因为access_token和jsapi_ticket的过期时间都是7200秒
-     * 所以在获取access_token的同时也去获取了jsapi_ticket
-     * 注:jsapi_ticket是在前端页面JSAPI做权限验证配置的时候需要使用的
-     * 具体信息请查看开发者文档--权限验证配置
-     *
-     * @return accessToken 或错误信息
-     */
-    public String getCorpAccessToken() {
-        // 从持久化存储中读取
-        String corpAccessToken = redisCache.getCacheObject(Constants.DAILY_DING_CORP_ACCESS_TOKEN);
-        log.info("从Redis缓存中获取到的corpAccessToken = {}", corpAccessToken);
-        if (corpAccessToken != null) {
-            return corpAccessToken;
-        }
-        try {
-            com.aliyun.dingtalkoauth2_1_0.Client client = this.createClientNew();
-            String suiteTicket = redisCache.getCacheObject(Constants.DAILY_DING_SUITE_TICKET);
-            com.aliyun.dingtalkoauth2_1_0.models.GetCorpAccessTokenRequest getCorpAccessTokenRequest = new com.aliyun.dingtalkoauth2_1_0.models.GetCorpAccessTokenRequest()
-                    .setSuiteKey(dingAppConfig.getAppKey())
-                    .setSuiteSecret(dingAppConfig.getAppSecret())
-                    .setAuthCorpId(dingAppConfig.getCorpId())
-                    .setSuiteTicket(suiteTicket);
-            GetCorpAccessTokenResponse response = client.getCorpAccessToken(getCorpAccessTokenRequest);
-            corpAccessToken = response.getBody().getAccessToken();
-            log.info("向Redis缓存中存取corpAccessToken = {}", corpAccessToken);
-            redisCache.setCacheObject(Constants.DAILY_DING_CORP_ACCESS_TOKEN, corpAccessToken, CACHE_TTL, TimeUnit.MINUTES);
-        } catch (TeaException err) {
-            if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {
-                // err 中含有 code 和 message 属性,可帮助开发定位问题
-                log.error("getAccessToken failed",err.message);
-            }
-            return corpAccessToken;
-        } catch (Exception _err) {
-            TeaException err = new TeaException(_err.getMessage(), _err);
-            if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {
-                // err 中含有 code 和 message 属性,可帮助开发定位问题
-                log.error("getAccessToken failed",err.message);
-            }
-            return corpAccessToken;
-        }
-        return corpAccessToken;
-    }
+
 
     /**
      * 使用 Token 初始化账号Client
+     *
      * @return Client
      * @throws Exception
      */
@@ -144,10 +99,11 @@ public class DingTokenService {
     }
 
     /**
-     *  获取用户toKen
+     * 获取用户toKen
+     *
      * @return
      */
-    public AjaxResult getUserAccessToken(String code){
+    public AjaxResult getUserAccessToken(String code) {
         try {
             Client client = this.createClient();
             GetUserTokenRequest request = new GetUserTokenRequest()
@@ -159,7 +115,7 @@ public class DingTokenService {
 
             response = client.getUserToken(request);
 
-            return AjaxResult.success(response.getBody().getAccessToken(),response.getBody());
+            return AjaxResult.success(response.getBody().getAccessToken(), response.getBody());
 
         } catch (ApiException e) {
             log.error("getAccessToken failed", e);
@@ -171,6 +127,22 @@ public class DingTokenService {
 
     }
 
+    public GetUserTokenResponse getUserAccessToken1(String code) {
+        try {
+            Client client = this.createClient();
+            GetUserTokenRequest request = new GetUserTokenRequest()
+                    .setClientSecret(dingAppConfig.getAppSecret())
+                    .setClientId(dingAppConfig.getAppKey())
+                    .setCode(code).setGrantType(Constants.AUTHORIZATION_CODE);
+            return client.getUserToken(request);
+        } catch (ApiException e) {
+            log.error("getAccessToken failed", e);
+        } catch (Exception e) {
+            log.error("getAccessToken failed", e);
+        }
+        return null;
+    }
+
     /**
      * 获取JSTicket, 用于js的签名计算
      * 正常的情况下,jsapi_ticket的有效期为7200秒,所以开发者需要在某个地方设计一个定时器,定期去更新jsapi_ticket
@@ -221,12 +193,12 @@ public class DingTokenService {
     }
 
 
-
     /**
-     *  获取用户toKen
+     * 获取用户toKen
+     *
      * @return
      */
-    public String getAdminUserAccessToken(){
+    public String getAdminUserAccessToken() {
         String accessToken = null;
         try {
             com.aliyun.dingtalkoauth2_1_0.Client client = this.createClientNew();
@@ -246,6 +218,4 @@ public class DingTokenService {
     }
 
 
-
-
 }

+ 15 - 9
ruoyi-admin/src/main/java/com/ruoyi/web/controller/tool/ResumeController.java

@@ -4,10 +4,7 @@ import java.text.SimpleDateFormat;
 import java.util.*;
 import javax.servlet.http.HttpServletResponse;
 
-import com.alibaba.druid.support.json.JSONUtils;
-import com.alibaba.fastjson2.JSON;
 import com.alibaba.fastjson2.JSONObject;
-import com.dingtalk.api.response.OapiUserAssociatedUnionidTransferResponse;
 import com.ruoyi.common.config.RuoYiConfig;
 import com.ruoyi.common.utils.SecurityUtils;
 import com.ruoyi.common.utils.StringUtils;
@@ -63,7 +60,9 @@ public class ResumeController extends BaseController
     public TableDataInfo list(Resume resume)
     {
         startPage();
-        resume.setCorpId(SecurityUtils.getLoginUser().getCorpid());
+        if (StringUtils.isNotEmpty( SecurityUtils.getLoginUser().getCorpid())){
+            resume.setCorpId(SecurityUtils.getLoginUser().getCorpid());
+        }
         List<Resume> list = resumeService.selectResumeList(resume);
         return getDataTable(list);
     }
@@ -76,7 +75,9 @@ public class ResumeController extends BaseController
     @PostMapping("/export")
     public void export(HttpServletResponse response, Resume resume)
     {
-        resume.setCorpId(SecurityUtils.getLoginUser().getCorpid());
+        if (StringUtils.isNotEmpty(SecurityUtils.getLoginUser().getCorpid())) {
+            resume.setCorpId(SecurityUtils.getLoginUser().getCorpid());
+        }
         List<Resume> list = resumeService.selectResumeList(resume);
         ExcelUtil<Resume> util = new ExcelUtil<Resume>(Resume.class);
         util.exportExcel(response, list, "简历管理数据");
@@ -90,7 +91,7 @@ public class ResumeController extends BaseController
     @GetMapping(value = "/{resumeId}")
     public AjaxResult getInfo(@PathVariable("resumeId") Long resumeId)
     {
-        String corpid = SecurityUtils.getLoginUser().getCorpid();
+        String corpid = SecurityUtils.getPrincipal();
         return success(resumeService.selectResumeByResumeId(resumeId,corpid));
     }
 
@@ -103,7 +104,9 @@ public class ResumeController extends BaseController
     @PostMapping
     public AjaxResult add(@RequestBody Resume resume)
     {
-        resume.setCorpId(SecurityUtils.getLoginUser().getCorpid());
+        if (StringUtils.isNotEmpty(SecurityUtils.getLoginUser().getCorpid())) {
+            resume.setCorpId(SecurityUtils.getLoginUser().getCorpid());
+        }
         return toAjax(resumeService.insertResume(resume));
     }
 
@@ -115,7 +118,10 @@ public class ResumeController extends BaseController
     @ApiOperation(value = "上传简历并进行解析")
     @PostMapping(value = "/uploadResumeSimilarity",consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
     public AjaxResult uploadResumeSimilarity(MultipartFile file) {
-        String corpid = SecurityUtils.getLoginUser().getCorpid();
+        String corpId = "";
+        if (StringUtils.isNotEmpty(SecurityUtils.getLoginUser().getCorpid())) {
+            corpId = SecurityUtils.getLoginUser().getCorpid();
+        }
         try{
             if (file == null || StringUtils.isBlank(file.getOriginalFilename())) {
                 return AjaxResult.error("请上传简历");
@@ -166,7 +172,7 @@ public class ResumeController extends BaseController
                 }
                 //处理标识分段,并判断内容情况
                 Resume resume = ResumeAnalysisUtil.toResumeAnalysis(content);
-                resume.setCorpId(SecurityUtils.getLoginUser().getCorpid());
+                resume.setCorpId(corpId);
                 //处理文件信息
                 String url = serverConfig.getUrl() + fileName;
                 ResumeFile resumeFile = new ResumeFile();

+ 1 - 1
ruoyi-admin/src/main/java/com/ruoyi/web/controller/tool/mobile/ResumeMobileController.java

@@ -63,7 +63,7 @@ public class ResumeMobileController extends BaseController
     public AjaxResult getInfo(@PathVariable("resumeId") Long resumeId)
     {
 
-        return success(resumeService.selectResumeByResumeId(resumeId,SecurityUtils.getLoginUser().getCorpid()));
+        return success(resumeService.selectResumeByResumeId(resumeId,SecurityUtils.getLoginUser().getPrincipal()));
     }
 
     /**

+ 184 - 181
ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java

@@ -1,181 +1,184 @@
-package com.ruoyi.common.constant;
-
-import io.jsonwebtoken.Claims;
-
-/**
- * 通用常量信息
- * 
- * @author ruoyi
- */
-public class Constants
-{
-    /**
-     * UTF-8 字符集
-     */
-    public static final String UTF8 = "UTF-8";
-
-    /**
-     * GBK 字符集
-     */
-    public static final String GBK = "GBK";
-
-    /**
-     * www主域
-     */
-    public static final String WWW = "www.";
-
-    /**
-     * http请求
-     */
-    public static final String HTTP = "http://";
-
-    /**
-     * https请求
-     */
-    public static final String HTTPS = "https://";
-
-    /**
-     * 通用成功标识
-     */
-    public static final String SUCCESS = "0";
-
-    /**
-     * 通用失败标识
-     */
-    public static final String FAIL = "1";
-
-    /**
-     * 登录成功
-     */
-    public static final String LOGIN_SUCCESS = "Success";
-
-    /**
-     * 注销
-     */
-    public static final String LOGOUT = "Logout";
-
-    /**
-     * 注册
-     */
-    public static final String REGISTER = "Register";
-
-    /**
-     * 登录失败
-     */
-    public static final String LOGIN_FAIL = "Error";
- 
-    /**
-     * 验证码有效期(分钟)
-     */
-    public static final Integer CAPTCHA_EXPIRATION = 2;
-
-    /**
-     * 令牌
-     */
-    public static final String TOKEN = "token";
-
-    /**
-     * 令牌前缀
-     */
-    public static final String TOKEN_PREFIX = "Bearer ";
-
-    /**
-     * 令牌前缀
-     */
-    public static final String LOGIN_USER_KEY = "login_user_key";
-
-    /**
-     * 用户ID
-     */
-    public static final String JWT_USERID = "userid";
-
-    /**
-     * 用户名称
-     */
-    public static final String JWT_USERNAME = Claims.SUBJECT;
-
-    /**
-     * 用户头像
-     */
-    public static final String JWT_AVATAR = "avatar";
-
-    /**
-     * 创建时间
-     */
-    public static final String JWT_CREATED = "created";
-
-    /**
-     * 用户权限
-     */
-    public static final String JWT_AUTHORITIES = "authorities";
-
-    /**
-     * 资源映射路径 前缀
-     */
-    public static final String RESOURCE_PREFIX = "/profile";
-
-    /**
-     * RMI 远程方法调用
-     */
-    public static final String LOOKUP_RMI = "rmi:";
-
-    /**
-     * LDAP 远程方法调用
-     */
-    public static final String LOOKUP_LDAP = "ldap:";
-
-    /**
-     * LDAPS 远程方法调用
-     */
-    public static final String LOOKUP_LDAPS = "ldaps:";
-
-    /**
-     * 自动识别json对象白名单配置(仅允许解析的包名,范围越小越安全)
-     */
-    public static final String[] JSON_WHITELIST_STR = { "org.springframework", "com.ruoyi" };
-
-    /**
-     * 定时任务白名单配置(仅允许访问的包名,如其他需要可以自行添加)
-     */
-    public static final String[] JOB_WHITELIST_STR = { "com.ruoyi" };
-
-    /**
-     * 定时任务违规的字符
-     */
-    public static final String[] JOB_ERROR_STR = { "java.net.URL", "javax.naming.InitialContext", "org.yaml.snakeyaml",
-            "org.springframework", "org.apache", "com.ruoyi.common.utils.file", "com.ruoyi.common.config" };
-
-    /**
-     *  钉钉redis缓存key
-     */
-    public static final String DAILY_DING_ACCESS_TOKEN = "DAILY_DING_ACCESS_TOKEN";
-
-    /**
-     *  钉钉redis缓存第三方应用token
-     */
-    public static final String DAILY_DING_CORP_ACCESS_TOKEN = "DAILY_DING_CORP_ACCESS_TOKEN";
-
-    /**
-     *  钉钉redis缓存第三方应用suiteTicket
-     */
-    public static final String DAILY_DING_SUITE_TICKET = "DAILY_DING_SUITE_TICKET";
-
-    /**
-     * 钉钉redis缓存key
-     */
-    public static final String DAILY_DING_JS_TICKET = "DAILY_DING_JS_TICKET";
-
-    /**
-     * 钉钉 GrantType
-     */
-    public static final String AUTHORIZATION_CODE = "authorization_code";
-
-    /**
-     * 钉钉 GrantType
-     */
-    public static final String REFRESH_TOKEN = "refresh_token";
-
-
-
-
-}
+package com.ruoyi.common.constant;
+
+import io.jsonwebtoken.Claims;
+
+/**
+ * 通用常量信息
+ * 
+ * @author ruoyi
+ */
+public class Constants
+{
+    /**
+     * UTF-8 字符集
+     */
+    public static final String UTF8 = "UTF-8";
+
+    /**
+     * GBK 字符集
+     */
+    public static final String GBK = "GBK";
+
+    /**
+     * www主域
+     */
+    public static final String WWW = "www.";
+
+    /**
+     * http请求
+     */
+    public static final String HTTP = "http://";
+
+    /**
+     * https请求
+     */
+    public static final String HTTPS = "https://";
+
+    /**
+     * 通用成功标识
+     */
+    public static final String SUCCESS = "0";
+
+    /**
+     * 通用失败标识
+     */
+    public static final String FAIL = "1";
+
+    /**
+     * 登录成功
+     */
+    public static final String LOGIN_SUCCESS = "Success";
+
+    /**
+     * 注销
+     */
+    public static final String LOGOUT = "Logout";
+
+    /**
+     * 注册
+     */
+    public static final String REGISTER = "Register";
+
+    /**
+     * 登录失败
+     */
+    public static final String LOGIN_FAIL = "Error";
+ 
+    /**
+     * 验证码有效期(分钟)
+     */
+    public static final Integer CAPTCHA_EXPIRATION = 2;
+
+    /**
+     * 令牌
+     */
+    public static final String TOKEN = "token";
+
+    /**
+     * 令牌前缀
+     */
+    public static final String TOKEN_PREFIX = "Bearer ";
+
+    /**
+     * 令牌前缀
+     */
+    public static final String LOGIN_USER_KEY = "login_user_key";
+
+    /**
+     * 用户ID
+     */
+    public static final String JWT_USERID = "userid";
+
+    /**
+     * 用户名称
+     */
+    public static final String JWT_USERNAME = Claims.SUBJECT;
+
+    /**
+     * 用户头像
+     */
+    public static final String JWT_AVATAR = "avatar";
+
+    /**
+     * 创建时间
+     */
+    public static final String JWT_CREATED = "created";
+
+    /**
+     * 用户权限
+     */
+    public static final String JWT_AUTHORITIES = "authorities";
+
+    /**
+     * 资源映射路径 前缀
+     */
+    public static final String RESOURCE_PREFIX = "/profile";
+
+    /**
+     * RMI 远程方法调用
+     */
+    public static final String LOOKUP_RMI = "rmi:";
+
+    /**
+     * LDAP 远程方法调用
+     */
+    public static final String LOOKUP_LDAP = "ldap:";
+
+    /**
+     * LDAPS 远程方法调用
+     */
+    public static final String LOOKUP_LDAPS = "ldaps:";
+
+    /**
+     * 自动识别json对象白名单配置(仅允许解析的包名,范围越小越安全)
+     */
+    public static final String[] JSON_WHITELIST_STR = { "org.springframework", "com.ruoyi" };
+
+    /**
+     * 定时任务白名单配置(仅允许访问的包名,如其他需要可以自行添加)
+     */
+    public static final String[] JOB_WHITELIST_STR = { "com.ruoyi" };
+
+    /**
+     * 定时任务违规的字符
+     */
+    public static final String[] JOB_ERROR_STR = { "java.net.URL", "javax.naming.InitialContext", "org.yaml.snakeyaml",
+            "org.springframework", "org.apache", "com.ruoyi.common.utils.file", "com.ruoyi.common.config" };
+
+    /**
+     *  钉钉redis缓存key
+     */
+    public static final String DAILY_DING_ACCESS_TOKEN = "DAILY_DING_ACCESS_TOKEN";
+
+    /**
+     *  钉钉redis缓存第三方应用token
+     */
+    public static final String DAILY_DING_CORP_ACCESS_TOKEN = "CORP_ACCESS_TOKEN";
+
+    /**
+     *  钉钉redis缓存第三方应用suiteTicket
+     */
+    public static final String DAILY_DING_SUITE_TICKET = "SUITE_TICKET";
+    public static final String DAILY_DING_CORP_ID = "CORP_ID";
+    public static final String DAILY_DING_AUTH = "DAILY_DING_AUTH:";
+
+
+    /**
+     * 钉钉redis缓存key
+     */
+    public static final String DAILY_DING_JS_TICKET = "DAILY_DING_JS_TICKET";
+
+    /**
+     * 钉钉 GrantType
+     */
+    public static final String AUTHORIZATION_CODE = "authorization_code";
+
+    /**
+     * 钉钉 GrantType
+     */
+    public static final String REFRESH_TOKEN = "refresh_token";
+
+
+
+
+}

+ 38 - 43
ruoyi-common/src/main/java/com/ruoyi/common/utils/SecurityUtils.java

@@ -9,22 +9,17 @@ import com.ruoyi.common.exception.ServiceException;
 
 /**
  * 安全服务工具类
- * 
+ *
  * @author ruoyi
  */
-public class SecurityUtils
-{
+public class SecurityUtils {
     /**
      * 用户ID
      **/
-    public static Long getUserId()
-    {
-        try
-        {
+    public static Long getUserId() {
+        try {
             return getLoginUser().getUserId();
-        }
-        catch (Exception e)
-        {
+        } catch (Exception e) {
             throw new ServiceException("获取用户ID异常", HttpStatus.UNAUTHORIZED);
         }
     }
@@ -32,29 +27,21 @@ public class SecurityUtils
     /**
      * 获取部门ID
      **/
-    public static Long getDeptId()
-    {
-        try
-        {
+    public static Long getDeptId() {
+        try {
             return getLoginUser().getDeptId();
-        }
-        catch (Exception e)
-        {
+        } catch (Exception e) {
             throw new ServiceException("获取部门ID异常", HttpStatus.UNAUTHORIZED);
         }
     }
-    
+
     /**
      * 获取用户账户
      **/
-    public static String getUsername()
-    {
-        try
-        {
+    public static String getUsername() {
+        try {
             return getLoginUser().getUsername();
-        }
-        catch (Exception e)
-        {
+        } catch (Exception e) {
             throw new ServiceException("获取用户账户异常", HttpStatus.UNAUTHORIZED);
         }
     }
@@ -62,16 +49,28 @@ public class SecurityUtils
     /**
      * 获取用户
      **/
-    public static LoginUser getLoginUser()
-    {
-        LoginUser loginUser;
+    public static LoginUser getLoginUser() {
         try {
-            loginUser = (LoginUser) getAuthentication().getDetails();
-
-            return loginUser;
+            Object principal = getAuthentication().getPrincipal();
+            if (principal instanceof LoginUser) {
+                return (LoginUser) principal;
+            } else if (principal instanceof String) {
+                return (LoginUser) getAuthentication().getDetails();
+            } else {
+                throw new ServiceException("获取用户信息异常", HttpStatus.UNAUTHORIZED);
+            }
+        } catch (Exception e) {
+            throw new ServiceException("获取用户信息异常", HttpStatus.UNAUTHORIZED);
         }
-        catch (Exception e)
-        {
+    }
+
+    /**
+     * 获取用户
+     **/
+    public static String getPrincipal() {
+        try {
+            return getAuthentication().getPrincipal().toString();
+        } catch (Exception e) {
             throw new ServiceException("获取用户信息异常", HttpStatus.UNAUTHORIZED);
         }
     }
@@ -79,8 +78,7 @@ public class SecurityUtils
     /**
      * 获取Authentication
      */
-    public static Authentication getAuthentication()
-    {
+    public static Authentication getAuthentication() {
         return SecurityContextHolder.getContext().getAuthentication();
     }
 
@@ -90,8 +88,7 @@ public class SecurityUtils
      * @param password 密码
      * @return 加密字符串
      */
-    public static String encryptPassword(String password)
-    {
+    public static String encryptPassword(String password) {
         BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
         return passwordEncoder.encode(password);
     }
@@ -99,24 +96,22 @@ public class SecurityUtils
     /**
      * 判断密码是否相同
      *
-     * @param rawPassword 真实密码
+     * @param rawPassword     真实密码
      * @param encodedPassword 加密后字符
      * @return 结果
      */
-    public static boolean matchesPassword(String rawPassword, String encodedPassword)
-    {
+    public static boolean matchesPassword(String rawPassword, String encodedPassword) {
         BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
         return passwordEncoder.matches(rawPassword, encodedPassword);
     }
 
     /**
      * 是否为管理员
-     * 
+     *
      * @param userId 用户ID
      * @return 结果
      */
-    public static boolean isAdmin(Long userId)
-    {
+    public static boolean isAdmin(Long userId) {
         return userId != null && 1L == userId;
     }
 }

+ 2 - 2
ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java

@@ -114,7 +114,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter
                 // 过滤请求
                 .authorizeRequests()
                 // 对于登录login 单点登录authSingleSignIn 注册register 验证码captchaImage 允许匿名访问
-                .antMatchers("/login","/authSingleSignIn","/prod-api/ding/callback1","/ding/**","/mobile/**","/system/resume/**","/fg/**", "/register", "/captchaImage").permitAll()
+                .antMatchers("/login","/authSingleSignIn","/auth/ding/**","/ding/**","/mobile/**","/system/resume/**","/fg/**", "/register", "/captchaImage").permitAll()
                 // 静态资源,可匿名访问
                 .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
                 .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()
@@ -146,7 +146,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter
     @Override
     protected void configure(AuthenticationManagerBuilder auth) throws Exception
     {
-//        auth.authenticationProvider(dingAuthenticationProvider);
+        auth.authenticationProvider(dingAuthenticationProvider);
         auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
     }
 }

+ 1 - 1
ruoyi-framework/src/main/java/com/ruoyi/framework/security/filter/JwtAuthenticationTokenFilter.java

@@ -39,7 +39,7 @@ public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
             String authType = loginUser.getAuthType();
             AbstractAuthenticationToken authRequest = null;
             switch (authType) {
-                case "dingtalk":
+                case "AuthDingtalk":
                     authRequest = new DingToken(loginUser.getPrincipal(), loginUser.getCredentials(),loginUser);
                     authRequest.setDetails(loginUser);
                     break;

+ 21 - 21
ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/DingAuthenticationProvider.java

@@ -4,7 +4,9 @@ import com.alibaba.fastjson2.JSONObject;
 import com.dingtalk.api.response.OapiUserGetuserinfoResponse;
 import com.dingtalk.api.response.OapiV2UserGetResponse;
 import com.dingtalk.api.response.OapiV2UserGetuserinfoResponse;
+import com.ruoyi.common.core.domain.entity.SysUser;
 import com.ruoyi.common.core.domain.model.LoginUser;
+import com.ruoyi.system.service.ISysUserService;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -25,35 +27,29 @@ import java.util.Objects;
 public class DingAuthenticationProvider implements AuthenticationProvider {
 
     @Autowired
-    private UserDetailsService userDetailsService;
-    @Autowired
-    private DingDingAuthService dingAuthService;
+    private ISysUserService userService;
 
+    @Autowired
+    private SysPermissionService permissionService;
 
     @Override
     public Authentication authenticate(Authentication authentication) throws AuthenticationException {
         DingToken token = (DingToken) authentication;
-        String accessToken = (String) token.getPrincipal();
-        String userId = (String) token.getCredentials();
-        //根据accessToken 获取用户信息
-        OapiV2UserGetResponse response = dingAuthService.getAuthUser(accessToken, userId);
-        JSONObject parse = JSONObject.parseObject(response.getBody());
-        JSONObject user_info = parse.getJSONObject("user_info");
-        JSONObject corp_info = parse.getJSONObject("corp_info");
-        //然后,根据github用户,查询对应系统用户信息
-        LoginUser user = (LoginUser) userDetailsService.loadUserByUsername(user_info.getString("userid"));
-//        LoginUser user = (LoginUser) userDetailsService.loadUserByUsername("13621761774");
-        if (Objects.isNull(user)) {
-            throw new InternalAuthenticationServiceException("根据accessToken:" + accessToken + ",无法获取对应的用户信息!");
+        String userId = (String) token.getPrincipal();
+        String corpId = (String) token.getCredentials();
+
+        SysUser sysUser = userService.selectUserByUserName(userId);
+        if (Objects.isNull(sysUser)) {
+            throw new InternalAuthenticationServiceException("根据userId:" + userId + ",无法获取对应的用户信息!");
         }
-        user.setCorpid(corp_info.getString("corpid"));
-        user.setAuthType("dingtalk");
-        user.setPrincipal(accessToken);
-        user.setCredentials(userId);
+        LoginUser user = (LoginUser) createLoginUser(sysUser);
+        user.setAuthType("AuthDingtalk");
+        user.setPrincipal(userId);
+        user.setCredentials(corpId);
+        user.setCorpid(corpId);
         token.setDetails(user);
-        DingToken authenticationResult = new DingToken(user.getAuthorities(), accessToken, userId);
+        DingToken authenticationResult = new DingToken(user.getAuthorities(), userId, corpId);
         authenticationResult.setDetails(token.getDetails());
-        authenticationResult.setTaobaoResponse(response);
         return authenticationResult;
     }
 
@@ -62,4 +58,8 @@ public class DingAuthenticationProvider implements AuthenticationProvider {
         return DingToken.class.isAssignableFrom(authentication);
     }
 
+
+    public UserDetails createLoginUser(SysUser user) {
+        return new LoginUser(user.getUserId(), user.getDeptId(), user, permissionService.getMenuPermission(user));
+    }
 }

+ 0 - 85
ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/DingDingAuthService.java

@@ -1,85 +0,0 @@
-package com.ruoyi.framework.web.service;
-
-import com.dingtalk.api.DefaultDingTalkClient;
-import com.dingtalk.api.DingTalkClient;
-import com.dingtalk.api.request.OapiUserGetRequest;
-import com.dingtalk.api.request.OapiUserGetuserinfoRequest;
-import com.dingtalk.api.request.OapiV2UserGetRequest;
-import com.dingtalk.api.response.OapiUserGetResponse;
-import com.dingtalk.api.response.OapiUserGetuserinfoResponse;
-import com.dingtalk.api.response.OapiV2UserGetResponse;
-import com.ruoyi.common.core.domain.AjaxResult;
-import com.ruoyi.common.core.domain.entity.SysUser;
-import com.ruoyi.common.exception.ServiceException;
-import com.ruoyi.common.utils.dingding.config.DingUrlConstant;
-import com.ruoyi.system.service.dingding.DingAuthService;
-import com.taobao.api.ApiException;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Service;
-
-import java.util.Optional;
-
-import static com.ruoyi.framework.datasource.DynamicDataSourceContextHolder.log;
-
-/**
- * @author hanzy
- * @description
- * @date 2024年04月29日 14:01
- */
-@Service
-public class DingDingAuthService {
-    @Autowired
-    private DingAuthService dingAuthService;
-    public OapiV2UserGetResponse getAuthUser(String accessToken, String userId) {
-        DingTalkClient client = new DefaultDingTalkClient(DingUrlConstant.URL_USER_GET_V2);
-        OapiV2UserGetResponse response;
-        OapiV2UserGetRequest request = new OapiV2UserGetRequest();
-        request.setUserid(userId);
-        request.setLanguage("en_US");
-        try {
-            response = client.execute(request, accessToken);
-        } catch (ApiException e) {
-            log.error("Failed to getUserName: " + e.getErrMsg());
-            return null;
-        }
-        return response;
-    }
-    /**
-     * 访问/user/getuserinfo接口获取用户userId
-     *
-     * @param accessToken access_token
-     * @param authCode    临时授权码
-     * @return 用户userId或错误信息
-     */
-    public OapiUserGetuserinfoResponse getUserInfo(String accessToken, String authCode) {
-        DingTalkClient client = new DefaultDingTalkClient(DingUrlConstant.URL_GET_USER_INFO);
-        OapiUserGetuserinfoRequest request = new OapiUserGetuserinfoRequest();
-        request.setCode(authCode);
-        request.setHttpMethod("GET");
-        OapiUserGetuserinfoResponse response;
-        try {
-            response = client.execute(request, accessToken);
-        } catch (ApiException e) {
-            throw new ServiceException("Failed to getUserInfo: " + e.getMessage());
-        }
-        if (!response.isSuccess()) {
-            throw new ServiceException("Failed to getUserInfo: " + response.getMessage());
-        }
-        return response;
-    }
-    private SysUser assembleUserDTO(OapiUserGetResponse response, String accessToken) {
-        String userid = response.getUserid();
-        String mobile = response.getMobile();
-        try {
-            SysUser sysUser = Optional.ofNullable(dingAuthService.getUserByUserId(userid))
-                    .orElse(dingAuthService.getUserByUsername(mobile));
-            sysUser.setAccessToken(accessToken);
-            sysUser.setNickName(response.getName());
-            sysUser.setAvatar(response.getAvatar());
-            return sysUser;
-        } catch (Exception e) {
-            return new SysUser();
-        }
-    }
-
-}

+ 21 - 29
ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/DingToken.java

@@ -1,6 +1,5 @@
 package com.ruoyi.framework.web.service;
 
-import com.taobao.api.TaobaoResponse;
 import org.springframework.security.authentication.AbstractAuthenticationToken;
 import org.springframework.security.core.GrantedAuthority;
 
@@ -13,62 +12,55 @@ import java.util.Collection;
  */
 
 public class DingToken extends AbstractAuthenticationToken {
-    public DingToken(Collection<? extends GrantedAuthority> authorities, String accessToken, String code) {
+    public DingToken(Collection<? extends GrantedAuthority> authorities, String userId, String corpId) {
         super(authorities);
-        this.accessToken = accessToken;
-        this.code = code;
+        this.userId = userId;
+        this.corpId = corpId;
     }
 
-    public DingToken(String accessToken, String code) {
+    public DingToken(String userId, String corpId) {
         super(null);
-        this.accessToken = accessToken;
-        this.code = code;
+        this.userId = userId;
+        this.corpId = corpId;
     }
 
-    public DingToken(String accessToken, String code,Object details) {
+    public DingToken(String userId, String corpId,Object details) {
         super(null);
-        this.accessToken = accessToken;
-        this.code = code;
+        this.userId = userId;
+        this.corpId = corpId;
         super.setDetails(details);
     }
 
-    private String accessToken;
+    private String userId;
 
-    private String code;
+    private String corpId;
 
-    private TaobaoResponse taobaoResponse;
 
-    public String getAccessToken() {
-        return accessToken;
+    public String getUserId() {
+        return userId;
     }
 
-    public void setAccessToken(String accessToken) {
-        this.accessToken = accessToken;
+    public void setUserId(String userId) {
+        this.userId = userId;
     }
 
-    public String getCode() {
-        return code;
+    public String getCorpId() {
+        return corpId;
     }
 
-    public void setCode(String code) {
-        this.code = code;
+    public void setCorpId(String corpId) {
+        this.corpId = corpId;
     }
 
     @Override
     public Object getCredentials() {
-        return this.code;
+        return this.corpId;
     }
 
     @Override
     public Object getPrincipal() {
-        return this.accessToken;
+        return this.userId;
     }
 
-    public TaobaoResponse getTaobaoResponse() {
-        return taobaoResponse;
-    }
 
-    public void setTaobaoResponse(TaobaoResponse taobaoResponse) {
-        this.taobaoResponse = taobaoResponse;
-    }
 }

+ 23 - 21
ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysLoginService.java

@@ -1,6 +1,9 @@
 package com.ruoyi.framework.web.service;
 
 import javax.annotation.Resource;
+
+import com.alibaba.fastjson.JSON;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.authentication.BadCredentialsException;
@@ -29,15 +32,13 @@ import com.ruoyi.framework.security.context.AuthenticationContextHolder;
 import com.ruoyi.system.service.ISysConfigService;
 import com.ruoyi.system.service.ISysUserService;
 
-import java.util.HashMap;
-import java.util.Map;
-
 /**
  * 登录校验方法
  * 
  * @author ruoyi
  */
 @Component
+@Slf4j
 public class SysLoginService
 {
     @Autowired
@@ -100,6 +101,7 @@ public class SysLoginService
         }
         AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
         LoginUser loginUser = (LoginUser) authentication.getPrincipal();
+        loginUser.setAuthType("password");
         recordLoginInfo(loginUser.getUserId());
         // 生成token
         return tokenService.createToken(loginUser);
@@ -232,24 +234,25 @@ public class SysLoginService
         }
         AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
         LoginUser loginUser = (LoginUser) authentication.getPrincipal();
-
+        log.info("password用户信息:{}", JSON.toJSONString(loginUser));
         loginUser.setAuthType("password");
         recordLoginInfo(loginUser.getUserId());
         // 生成token
         return tokenService.createToken(loginUser);
     }
 
-    /**
-     * 单点登录验证
-     *
-     * @param username 用户名
-     * @return 结果
-     */
-    public Map<String,Object> authSingleSignIn2(String accessToken, String userId)
+   /**
+    * @description:  钉钉第三方企业登录
+    * @param: userId 用户id
+    * @param: corpId 企业id
+    * @return: java.lang.String
+    * @date: 2024/5/10 17:35
+    */
+    public String authDingThirdLogin(String userId, String corpId)
     {
-        if (StringUtils.isEmpty(accessToken))
+        if (StringUtils.isEmpty(userId))
         {
-            AsyncManager.me().execute(AsyncFactory.recordLogininfor(accessToken, Constants.LOGIN_FAIL, MessageUtils.message("not.null")));
+            AsyncManager.me().execute(AsyncFactory.recordLogininfor(userId, Constants.LOGIN_FAIL, MessageUtils.message("not.null")));
             throw new UserNotExistsException();
         }
 
@@ -257,18 +260,18 @@ public class SysLoginService
         Authentication authentication = null;
         try
         {
-            authentication =  dingAuthenticationProvider.authenticate(new DingToken(accessToken, userId));
+            authentication =  dingAuthenticationProvider.authenticate(new DingToken(userId, corpId));
         }
         catch (Exception e)
         {
             if (e instanceof BadCredentialsException)
             {
-                AsyncManager.me().execute(AsyncFactory.recordLogininfor(accessToken, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
+                AsyncManager.me().execute(AsyncFactory.recordLogininfor(userId, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
                 throw new UserPasswordNotMatchException();
             }
             else
             {
-                AsyncManager.me().execute(AsyncFactory.recordLogininfor(accessToken, Constants.LOGIN_FAIL, e.getMessage()));
+                AsyncManager.me().execute(AsyncFactory.recordLogininfor(userId, Constants.LOGIN_FAIL, e.getMessage()));
                 throw new ServiceException(e.getMessage());
             }
         }
@@ -276,14 +279,13 @@ public class SysLoginService
         {
             AuthenticationContextHolder.clearContext();
         }
-        AsyncManager.me().execute(AsyncFactory.recordLogininfor(accessToken, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
+        AsyncManager.me().execute(AsyncFactory.recordLogininfor(userId, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
         LoginUser loginUser = (LoginUser) authentication.getDetails();
+        log.info("钉钉第三方企业登录用户信息:{}", JSON.toJSONString(loginUser));
         recordLoginInfo(loginUser.getUserId());
-        Map <String,Object> result = new HashMap<>();
-        result.put("token", tokenService.createToken(loginUser));
-        result.put("authentication", authentication);
         // 生成token
-        return result;
+        String token = tokenService.createToken(loginUser);
+        return token;
     }
 
 }

+ 1 - 0
ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/UserDetailsServiceImpl.java

@@ -37,6 +37,7 @@ public class UserDetailsServiceImpl implements UserDetailsService
     @Override
     public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
     {
+        log.info("1用户userId{}",username);
         SysUser user = userService.selectUserByUserName(username);
         if (StringUtils.isNull(user))
         {

+ 0 - 27
ruoyi-system/src/main/java/com/ruoyi/system/service/dingding/DingAuthService.java

@@ -1,27 +0,0 @@
-package com.ruoyi.system.service.dingding;
-
-import com.ruoyi.common.core.domain.entity.SysUser;
-
-/**
- * <p>DingAuthService 此接口用于:</p>
- * <p>@remark:</p>
- */
-public interface DingAuthService {
-
-    /**
-     * 根据用户手机号查询是否是公司员工
-     *
-     * @param userId 钉钉用户id
-     * @return UIOT员工信息
-     */
-    SysUser getUserByUserId(String userId);
-
-    /**
-     * 根据用户手机号查询是否是公司员工
-     *
-     * @param mobile 钉钉用户手机号
-     * @return UIOT员工信息
-     */
-    SysUser getUserByUsername(String mobile);
-
-}

+ 0 - 49
ruoyi-system/src/main/java/com/ruoyi/system/service/dingding/impl/DingAuthServiceImpl.java

@@ -1,49 +0,0 @@
-package com.ruoyi.system.service.dingding.impl;
-
-import com.ruoyi.common.core.domain.entity.SysUser;
-import com.ruoyi.system.mapper.SysUserMapper;
-import com.ruoyi.system.service.dingding.DingAuthService;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.stereotype.Service;
-
-import javax.annotation.Resource;
-
-/**
- * <p>DingAuthServiceImpl 此类用于:</p>
- * <p>@author:hujm</p>
- * <p>@date:2021年05月20日 15:56</p>
- * <p>@remark:</p>
- */
-@Slf4j
-@Service
-public class DingAuthServiceImpl implements DingAuthService {
-
-    @Resource
-    private SysUserMapper sysUserMapper;
-
-    @Override
-    public SysUser getUserByUserId(String userId) {
-
-        if (userId == null) {
-            log.error("根据用户手机号查询UIOT用户时,当前钉钉用户id为空!");
-            return new SysUser();
-        }
-
-        SysUser sysUser = sysUserMapper.selectUserByUserName(userId);
-
-        return sysUser;
-    }
-
-    @Override
-    public SysUser getUserByUsername(String mobile) {
-
-        if (mobile == null) {
-            log.error("根据用户手机号查询UIOT用户时,当前钉钉用户id为空!");
-            return new SysUser();
-        }
-
-        SysUser sysUser = sysUserMapper.selectUserByUserName(mobile);
-
-        return sysUser;
-    }
-}