# scaffold 项目之第三方登陆

# 三方登陆

系统支持对接国内多个第三方平台,实现三方登陆功能。快捷登陆方式。例如:

  • 管理后台:企业微信、阿里钉钉
  • 用户 App:微信公众号、微信小程序、qq

# 表结构

  1. 三方登陆完成时,系统会将三方用户存储到 system_social_user 表中,通过 type 标记对应的第三方平台。
  2. 未关联本系统 User 的第三方用户,需要在三方登陆完成后,使用账号密码进行【绑定登陆】,成功后记录到 system_social_user_bind 表中。
  3. 已关联本系统 User 的第三方用户,在三方登陆完成后,直接进入系统,既 快捷登陆

# 绑定登陆

  1. 访问登陆页面,点击【钉钉】或者【企业微信】进行第三方登陆。就会调用 /admin-api/system/auth/social-auth-redirect 接口,获取第三方平台的登陆地址,并进行跳转。

    然后会出现一个二维码,使用【钉钉】或者【企业微信】扫码进行第三方登录。

  2. 第三方登录成功后,跳转回 项目访问地址+/social-login 地址。此时,会调用 admin-api/system/auth/social-login 接口,尝试【快捷登陆】。由于该三方用户【未关联】管理后台的 AdminUser 用户,此时会看到 未绑定账号 的提示,会让用户绑定。

  3. 接着输入账号密码,提交后进行第三方用户的绑定。此时会调用 admin-api/system/auth/login 接口(在用户名密码的基础上额外带上 socialType + socialCode + socialState 参数)。成功后,即可进入系统主页。

  4. 具体实现:

    @Override
    public String getAuthorizeUrl(Integer socialType, Integer userType, String redirectUri) {
        // 获得对应的 AuthRequest 实现
        AuthRequest authRequest = buildAuthRequest(socialType, userType);
        // 生成跳转地址
        String authorizeUri = authRequest.authorize(AuthStateUtils.createState());
        return HttpUtils.replaceUrlQuery(authorizeUri, "redirect_uri", redirectUri);
    }

    提示:使用的是 justauth 组件

# 快捷登陆

退出登陆重新登陆

  1. 访问登陆页面,点击【钉钉】或者【企业微信】进行第三方登陆。就会调用 /admin-api/system/auth/social-auth-redirect 接口,获取第三方平台的登陆地址,并进行跳转。

    然后会出现一个二维码,使用【钉钉】或者【企业微信】扫码进行第三方登录。

  2. 这个时候会不同(因为已经绑定了用户)第三方登录成功后,跳转回 项目访问地址+/social-login 地址。此时,会调用 admin-api/system/auth/social-login 接口,尝试【快捷登陆】。由于该三方用户【已关联】管理后台的 AdminUser 用户,所以会直接进入系统。

# 绑定与解绑

在个人中心进行解除绑定,调用解除的接口删除社交用户

@Override
public void deleteSocialClient(Long id) {
    // 校验存在
    validateSocialClientExists(id);
    // 删除
    socialClientMapper.deleteById(id);
}

# 配置管理

# 配置文件

application-{env}.yml{env} 根据情况使用的是生产环境还是测试环境的配置)配置文件中,对应 justauth 配置项,填写第三方相关的配置信息(appKey 之类的)。

justauth:
  enabled: true
  type:
    DINGTALK: # 钉钉
      client-id: dingvrnreajea3yqvzhxg
      client-secret: i8E6iZyDvZj51JIb0tYsYfVQYOaks9Cq1lgryEjFRqC79P3iJcrxEwT6Qk2QvLrLI
      ignore-check-redirect-uri: true
    WECHAT_ENTERPRISE: # 企业微信
      client-id: wwd411c69a39ada2e54
      client-secret: 1wTb7hYxanpT2TUbIeHGXGo7T0odav1ic10mLdyyATOw
      agent-id: 1000004
      ignore-check-redirect-uri: true
    WECHAT_MINI_APP: # 微信小程序
      client-id: ${wx.miniapp.appid}
      client-secret: ${wx.miniapp.secret}
      ignore-check-redirect-uri: true
      ignore-check-state: true # 微信小程序,不会使用到 state,所以不进行校验
    WECHAT_MP: # 微信公众号
      client-id: ${wx.mp.app-id}
      client-secret: ${wx.mp.secret}
      ignore-check-redirect-uri: true
      ignore-check-state: true

# 数据库配置

system_social_client 表,它本质是 justauth组件 配置的存储。

主要有以下几个目的:

  1. 方便 client-id 变动,动态修改管理

  2. 在需要 SaaS 多租户项目中,通过 tenant_id 字段隔离,实现不同租户对应不同的第三方平台的配置

  3. 在多用户类型的项目中,通过 user_type 字段区分,实现不同用户类型对应不同的第三方平台配置

    注意: system_social_client 表的优先级,比 application-{env}.yml 配置文件高!

具体实现:

/**
 * 构建 AuthRequest 对象,支持多租户配置
 *
 * @param socialType 社交类型
 * @param userType 用户类型
 * @return AuthRequest 对象
 */
private AuthRequest buildAuthRequest(Integer socialType, Integer userType) {
    // 1. 先查找默认的配置项,从 application-*.yaml 中读取
    AuthRequest request = authRequestFactory.get(SocialTypeEnum.valueOfType(socialType).getSource());
    Assert.notNull(request, String.format("社交平台(%d) 不存在", socialType));
    // 2. 查询 DB 的配置项,如果存在则进行覆盖
    SocialClientDO client = socialClientMapper.selectBySocialTypeAndUserType(socialType, userType);
    if (client != null && Objects.equals(client.getStatus(), CommonStatusEnum.ENABLE.getStatus())) {
        // 2.1 构造新的 AuthConfig 对象
        AuthConfig authConfig = (AuthConfig) ReflectUtil.getFieldValue(request, "config");
        AuthConfig newAuthConfig = ReflectUtil.newInstance(authConfig.getClass());
        BeanUtil.copyProperties(authConfig, newAuthConfig);
        // 2.2 修改对应的 clientId + clientSecret 密钥
        newAuthConfig.setClientId(client.getClientId());
        newAuthConfig.setClientSecret(client.getClientSecret());
        // 如果有 agentId 则修改 agentId
        if (client.getAgentId() != null) {
            newAuthConfig.setAgentId(client.getAgentId());
        }
        // 2.3 设置会 request 里,进行后续使用
        ReflectUtil.setFieldValue(request, "config", newAuthConfig);
    }
    return request;
}

系统暂不支持 多租户 + 用户类型 + 社交平台,对应配置,需要进一步扩展实现。

实现方法:

  1. 加多一个字段 application 存储项目名称,进一步隔离
  2. 前端调用三方登陆额外带 application 参数

在【系统管理】-> 【三方登陆】 -> 【三方应用】 菜单下,可以进行当前租户的 system_soclial_client 表的配置管理。

在【系统管理】-> 【三方登陆】 -> 【三方用户】 菜单下,可以进行当前租户的 system_social_user 三方用户表的查看。

# 第三方平台的申请

可以统一在 justauth 中间件中跳转到各平台进行申请

注意,如果第三方平台如果需要配置具体的授信地址,需要添加 /social-login 用于三方登录回调页、 /user/profile 用于三方用户的绑定与解绑。