DingTalk開発者コンソールの設定
DingTalkのQRコード認証をWebアプリケーションに組み込むには、まず開発者コンソールでアプリケーションを作成し、必要な設定を行う必要があります。
- アプリケーションの作成とキーの取得
DingTalkオープンプラットフォームで「H5マイクロアプリケーション」または「企業内部アプリ」を作成します。作成後、
AppKey(またはSuiteKey)とAppSecretを確認してください。 - コールバックURLの設定
アプリケーションの詳細画面にある「開発管理」または「ログインと共有」セクションへ移動し、認証後にリダイレクトされるコールバックURLを登録します。このURLは開発中はngrokやfrpなどのローカルトンネリングツールを用いて公開し、設定することをお勧めします。
フロントエンド実装(Vue.js)
フロントエンドでは、公式提供のJavaScript SDKを使用してQRコードを生成し、認証コードを受け取る処理を実装します。ここではVue.jsを使用した実装例を示します。
認証モジュールの実装
以下は、QRコードの生成と認証処理を担当するコンポーネントの例です。変数名や構造を変更し、コードの可読性を向上させています。
<template>
<div class="auth-wrapper">
<!-- QRコード表示コンテナ -->
<div id="dingtalk-qr-container" v-show="!isProcessing"></div>
<!-- ログイン処理中表示 -->
<div v-if="isProcessing" class="loading-overlay">
<p>認証処理中...</p>
</div>
</div>
</template>
<script>
import { fetchAppConfig } from '@/api/config'
export default {
name: 'DingTalkAuth',
data() {
return {
clientId: '', // AppKey
redirectUri: '', // リダイレクト先
isProcessing: false,
isQrInitialized: false
}
},
mounted() {
// URLパラメータに認証コードが含まれている場合の処理
const authCode = this.$route.query.authCode
if (authCode) {
this.handleAuthentication(authCode)
} else {
this.initializeAuth()
}
},
methods: {
async initializeAuth() {
try {
const config = await fetchAppConfig()
this.clientId = config.appKey
// 現在のホストをベースにリダイレクトURIを構築
this.redirectUri = `${window.location.protocol}//${window.location.host}/callback`
this.$nextTick(() => {
if (!this.isQrInitialized) {
this.renderQrCode()
}
})
} catch (error) {
console.error('設定の取得に失敗しました', error)
}
},
renderQrCode() {
const targetUrl = encodeURIComponent(
`https://oapi.dingtalk.com/connect/oauth2/sns_authorize?appid=${this.clientId}&response_type=code&scope=snsapi_login&state=STATE&redirect_uri=${this.redirectUri}`
)
// DDLoginオブジェクトを使用してQRコードを埋め込み
const dtInstance = window.DTFrameLogin({
id: 'dingtalk-qr-container',
width: 300,
height: 300,
}, {
redirect_uri: encodeURIComponent(this.redirectUri),
client_id: this.clientId,
scope: 'openid',
response_type: 'code',
state: 'STATE',
prompt: 'consent',
}, (loginResult) => {
// コールバック処理
const { redirectUrl, authCode, state } = loginResult
// authCodeを使用してバックエンドへリクエスト
if (authCode) {
this.handleAuthentication(authCode)
}
}, (errorMsg) => {
console.error('ログインエラー:', errorMsg)
})
// ※ 旧バージョンのSDK(ddLogin.js)を使用する場合の実装例
// window.DDLogin({
// id: 'dingtalk-qr-container',
// goto: targetUrl,
// style: 'border:none;background-color:#FFFFFF;',
// width: '300',
// height: '300'
// })
this.setupMessageListener()
this.isQrInitialized = true
},
setupMessageListener() {
// postMessageによる通信の監視
const messageHandler = (event) => {
const origin = event.origin
if (origin === 'https://login.dingtalk.com') {
const tempAuthCode = event.data
// 一時コードを取得した後、正式な認証URLへ遷移
window.location.href = `https://oapi.dingtalk.com/connect/oauth2/sns_authorize?appid=${this.clientId}&response_type=code&scope=snsapi_login&state=STATE&redirect_uri=${this.redirectUri}&loginTmpCode=${tempAuthCode}`
}
}
if (window.addEventListener) {
window.addEventListener('message', messageHandler, false)
} else {
window.attachEvent('onmessage', messageHandler)
}
},
async handleAuthentication(code) {
this.isProcessing = true
try {
// VuexストアまたはAPIサービスを通じてログイン処理を実行
await this.$store.dispatch('dingTalkLogin', code)
this.$router.push({ path: '/dashboard' })
} catch (error) {
this.isProcessing = false
alert('ログインに失敗しました')
}
}
}
}
</script>
<style scoped>
.auth-wrapper {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
#dingtalk-qr-container {
background: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.loading-overlay {
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
background: rgba(255,255,255,0.8);
display: flex;
justify-content: center;
align-items: center;
z-index: 10;
}
</style>
バックエンド実装(Java Spring Boot)
バックエンドでは、フロントエンドから受け取った認証コード(authCode)を使用して、DingTalkのAPI経由でユーザー情報を取得し、自社システムのユーザーと照合してトークンを発行します。
認証サービスクラス
import com.dingtalk.api.DefaultDingTalkClient;
import com.dingtalk.api.DingTalkClient;
import com.dingtalk.api.request.OapiGettokenRequest;
import com.dingtalk.api.request.OapiSnsGetuserinfoBycodeRequest;
import com.dingtalk.api.request.OapiUserGetbyunionidRequest;
import com.dingtalk.api.request.OapiUserGetRequest;
import com.dingtalk.api.response.OapiGettokenResponse;
import com.dingtalk.api.response.OapiSnsGetuserinfoBycodeResponse;
import com.dingtalk.api.response.OapiUserGetbyunionidResponse;
import com.dingtalk.api.response.OapiUserGetResponse;
import com.taobao.api.ApiException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
@Service
public class DingTalkAuthService {
@Value("${dingtalk.appkey}")
private String appKey;
@Value("${dingtalk.appsecret}")
private String appSecret;
@Value("${dingtalk.sns.appkey}")
private String snsAppKey;
@Value("${dingtalk.sns.appsecret}")
private String snsAppSecret;
/**
* アプリケーションのアクセストークンを取得
*/
public String getAppAccessToken() throws ApiException {
DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/gettoken");
OapiGettokenRequest req = new OapiGettokenRequest();
req.setAppkey(appKey);
req.setAppsecret(appSecret);
req.setHttpMethod("GET");
OapiGettokenResponse response = client.execute(req);
return response.getAccessToken();
}
/**
* 認証コードを使用してユーザー情報を取得し、システム内ユーザーとして処理する
*/
public UserProfileDetails authenticateUser(String authCode) {
try {
// 1. 一時コードからユニオンIDを取得
String unionId = retrieveUnionId(authCode);
// 2. ユニオンIDからDingTalkユーザーIDを取得
String appAccessToken = getAppAccessToken();
String userId = retrieveUserIdByUnionId(unionId, appAccessToken);
// 3. ユーザーIDから詳細情報を取得
OapiUserGetResponse userResponse = fetchUserDetails(userId, appAccessToken);
return mapToUserProfile(userResponse);
} catch (ApiException e) {
throw new RuntimeException("DingTalk API呼び出しエラー: " + e.getErrMsg(), e);
}
}
private String retrieveUnionId(String authCode) throws ApiException {
DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/sns/getuserinfo_bycode");
OapiSnsGetuserinfoBycodeRequest req = new OapiSnsGetuserinfoBycodeRequest();
req.setTmpAuthCode(authCode);
//snsアプリのキーを使用する点に注意
OapiSnsGetuserinfoBycodeResponse response = client.execute(req, snsAppKey, snsAppSecret);
if (response.getUserInfo() == null) {
throw new RuntimeException("ユーザー情報の取得に失敗しました");
}
return response.getUserInfo().getUnionid();
}
private String retrieveUserIdByUnionId(String unionId, String accessToken) throws ApiException {
DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/user/getbyunionid");
OapiUserGetbyunionidRequest req = new OapiUserGetbyunionidRequest();
req.setUnionid(unionId);
OapiUserGetbyunionidResponse response = client.execute(req, accessToken);
return response.getResult().getUserid();
}
private OapiUserGetResponse fetchUserDetails(String userId, String accessToken) throws ApiException {
DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/user/get");
OapiUserGetRequest req = new OapiUserGetRequest();
req.setUserid(userId);
req.setHttpMethod("GET");
return client.execute(req, accessToken);
}
private UserProfileDetails mapToUserProfile(OapiUserGetResponse response) {
UserProfileDetails profile = new UserProfileDetails();
profile.setUserId(response.getUserid());
profile.setName(response.getName());
profile.setAvatar(response.getAvatar());
profile.setEmail(response.getEmail());
profile.setMobile(response.getMobile());
return profile;
}
}
認証コントローラ
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/auth")
public class AuthController {
@Autowired
private DingTalkAuthService dingTalkAuthService;
@Autowired
private TokenProvider tokenProvider;
@Autowired
private UserService userService;
@PostMapping("/dingtalk/callback")
public ApiResponse<LoginResult> handleDingTalkCallback(@RequestParam String authCode) {
try {
// DingTalk APIを通じてユーザー情報を取得
UserProfileDetails remoteUser = dingTalkAuthService.authenticateUser(authCode);
// 自社データベースでユーザーを照合(DingTalkのUserIDで検索)
SystemUser localUser = userService.findOrCreateByDingTalkId(remoteUser.getUserId());
// JWTトークンの生成
String jwtToken = tokenProvider.generateToken(localUser);
LoginResult result = new LoginResult();
result.setToken(jwtToken);
result.setUser(localUser);
return ApiResponse.success(result);
} catch (Exception e) {
return ApiResponse.error("認証処理中にエラーが発生しました: " + e.getMessage());
}
}
}
データ転送オブジェクト
public class UserProfileDetails {
private String userId;
private String name;
private String avatar;
private String email;
private String mobile;
// ゲッターおよびセッター
public String getUserId() { return userId; }
public void setUserId(String userId) { this.userId = userId; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getAvatar() { return avatar; }
public void setAvatar(String avatar) { this.avatar = avatar; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getMobile() { return mobile; }
public void setMobile(String mobile) { this.mobile = mobile; }
}