DingTalk QRコード認証のシステム連携ガイド

DingTalk開発者コンソールの設定

DingTalkのQRコード認証をWebアプリケーションに組み込むには、まず開発者コンソールでアプリケーションを作成し、必要な設定を行う必要があります。

  1. アプリケーションの作成とキーの取得

    DingTalkオープンプラットフォームで「H5マイクロアプリケーション」または「企業内部アプリ」を作成します。作成後、AppKey(またはSuiteKey)とAppSecretを確認してください。

  2. コールバック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; }
}

タグ: DingTalk Java Spring Boot vue.js OAuth2

5月14日 22:23 投稿