package tech.powerjob.server.auth.login.impl; import com.aliyun.dingtalkcontact_1_0.models.GetUserHeaders; import com.aliyun.dingtalkcontact_1_0.models.GetUserResponseBody; import com.aliyun.dingtalkoauth2_1_0.models.GetUserTokenRequest; import com.aliyun.dingtalkoauth2_1_0.models.GetUserTokenResponse; import com.aliyun.teaopenapi.models.Config; import com.aliyun.teautil.models.RuntimeOptions; import lombok.SneakyThrows; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import tech.powerjob.common.exception.PowerJobException; import tech.powerjob.server.auth.common.AuthConstants; import tech.powerjob.server.auth.login.*; import tech.powerjob.server.common.Loggers; import javax.servlet.http.HttpServletRequest; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; /** * 钉钉账号体系登录第三方网站 * PowerJob 官方支持钉钉账号体系登录原因: * 1. 钉钉作为当下用户体量最大的企业级办公软件,覆盖率足够高,提供钉钉支持能让更多开发者开箱即用 * 2. 钉钉的 API 设计和 PowerJob 设想一致,算是个最佳实践,其他企业内部的账号体系可参考这套流程进行接入 * - PowerJob 重定向到第三方账号体系登陆页 -> 第三方完成登陆 -> 跳转回调 PowerJob auth 接口 -> PowerJob 解析回调登陆信息,完整用户关联 * * @author tjq * @since 2023/3/26 */ @Service public class DingTalkLoginService implements ThirdPartyLoginService { /* 配置示例 oms.auth.dingtalk.appkey=dinggzqqzqqzqqzqq oms.auth.dingtalk.appSecret=iY-FS8mzqqzqq_xEizqqzqqzqqzqqzqqzqqYEbkZOal oms.auth.dingtalk.callbackUrl=http://localhost:7700 */ /** * 钉钉应用 AppKey */ @Value("${oms.auth.dingtalk.appkey:#{null}}") private String dingTalkAppKey; /** * 钉钉应用 AppSecret */ @Value("${oms.auth.dingtalk.appSecret:#{null}}") private String dingTalkAppSecret; /** * 回调地址,powerjob 前端控制台地址,即 powerjob-console 地址 * 比如本地调试时为 LocalDemoCallbackUrl * 部署后则为 demoCallBackUrl */ @Value("${oms.auth.dingtalk.callbackUrl:#{null}}") private String dingTalkCallbackUrl; @Override public LoginTypeInfo loginType() { return new LoginTypeInfo() .setType(AuthConstants.ACCOUNT_TYPE_DING) .setName("DingTalk") ; } @Override @SneakyThrows public String generateLoginUrl(HttpServletRequest httpServletRequest) { if (StringUtils.isAnyEmpty(dingTalkAppKey, dingTalkAppSecret, dingTalkCallbackUrl)) { throw new IllegalArgumentException("please config 'oms.auth.dingtalk.appkey', 'oms.auth.dingtalk.appSecret' and 'oms.auth.dingtalk.callbackUrl' in properties!"); } String urlString = URLEncoder.encode(dingTalkCallbackUrl, StandardCharsets.UTF_8.name()); String url = "https://login.dingtalk.com/oauth2/auth?" + "redirect_uri=" + urlString + "&response_type=code" + "&client_id=" + dingTalkAppKey + "&scope=openid" + "&state=" + AuthConstants.ACCOUNT_TYPE_DING + "&prompt=consent"; Loggers.WEB.info("[DingTalkBizLoginService] login url: {}", url); return url; } @Override @SneakyThrows public ThirdPartyUser login(ThirdPartyLoginRequest loginRequest) { try { com.aliyun.dingtalkoauth2_1_0.Client client = authClient(); GetUserTokenRequest getUserTokenRequest = new GetUserTokenRequest() //应用基础信息-应用信息的AppKey,请务必替换为开发的应用AppKey .setClientId(dingTalkAppKey) //应用基础信息-应用信息的AppSecret,,请务必替换为开发的应用AppSecret .setClientSecret(dingTalkAppSecret) .setCode(loginRequest.getHttpServletRequest().getParameter("authCode")) .setGrantType("authorization_code"); GetUserTokenResponse getUserTokenResponse = client.getUserToken(getUserTokenRequest); //获取用户个人 token String accessToken = getUserTokenResponse.getBody().getAccessToken(); // 查询钉钉用户 final GetUserResponseBody dingUser = getUserinfo(accessToken); // 将钉钉用户的唯一ID 和 PowerJob 账户体系的唯一键 username 关联 if (dingUser != null) { ThirdPartyUser bizUser = new ThirdPartyUser(); bizUser.setUsername(dingUser.getUnionId()); bizUser.setNick(dingUser.getNick()); bizUser.setPhone(dingUser.getMobile()); bizUser.setEmail(dingUser.getEmail()); return bizUser; } } catch (Exception e) { Loggers.WEB.error("[DingTalkBizLoginService] login by dingTalk failed!", e); throw e; } throw new PowerJobException("login from dingTalk failed!"); } /* 以下代码均拷自钉钉官网示例 */ private static com.aliyun.dingtalkoauth2_1_0.Client authClient() throws Exception { Config config = new Config(); config.protocol = "https"; config.regionId = "central"; return new com.aliyun.dingtalkoauth2_1_0.Client(config); } private static com.aliyun.dingtalkcontact_1_0.Client contactClient() throws Exception { Config config = new Config(); config.protocol = "https"; config.regionId = "central"; return new com.aliyun.dingtalkcontact_1_0.Client(config); } private GetUserResponseBody getUserinfo(String accessToken) throws Exception { com.aliyun.dingtalkcontact_1_0.Client client = contactClient(); GetUserHeaders getUserHeaders = new GetUserHeaders(); getUserHeaders.xAcsDingtalkAccessToken = accessToken; //获取用户个人信息,如需获取当前授权人的信息,unionId参数必须传me return client.getUserWithOptions("me", getUserHeaders, new RuntimeOptions()).getBody(); } }